在閘道方案上,Spring 5 自己推出了 Spring Cloud Gateway,支援 Java 8、Reactor API,可在 Spring Boot 2 使用,看到了 Reactor,可以理解到這個閘道方案目標之一,是能夠採用 Reactive 來實現高效率的閘道。
想要建立一個 Spring Cloud Gateway 的話,在 Spring Tool Suite 上可以選擇「Gateway」這個 Starter,為了能註冊到服務發現伺服器,也為了能開放 gateway/routes 端點,以便觀察路由資訊,就順便加入 Eureka 與 Actuator 的 Starter,也就是 build.gradle 中可以包含:
implementation('org.springframework.boot:spring-boot-starter-actuator')
implementation('org.springframework.cloud:spring-cloud-starter-gateway')
implementation('org.springframework.cloud:spring-cloud-starter-netflix-eureka-client')
Spring Cloud Gateway 可以從服務發現伺服器上註冊的服務 ID,自動建立路由資訊,為此,可以如下設定 bootstrap.properties:
server.port=5555
spring.cloud.gateway.discovery.locator.enabled=true
spring.cloud.gateway.discovery.locator.lowerCaseServiceId=true
eureka.instance.preferIpAddress=true
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/
management.endpoints.web.exposure.include: gateway
spring.cloud.gateway.discovery.locator.enabled
啟用了自動從服務 ID 建立路由,然而,路由的路徑對應,預設會使用大寫 ID,若想要使用小寫 ID,可將 spring.cloud.gateway.discovery.locator.lowerCaseServiceId
設為 true
;在設定中也開放了 gateway
端點。
必要時,可以自行實作 RouteLocator
來自定義路由方式。
接下來啟動相關服務,並且啟動 Spring Cloud Gateway 專案,預設會跑在 Netty 上,請求 http://localhost:5555/actuator/gateway/routes
的話,就可以看到以下資訊:
[
{
"route_id": "CompositeDiscoveryClient_ACCTSVI",
"route_definition": {
"id": "CompositeDiscoveryClient_ACCTSVI",
"predicates": [
{
"name": "Path",
"args": {
"pattern": "/acctsvi/**"
}
}
],
"filters": [
{
"name": "RewritePath",
"args": {
"regexp": "/acctsvi/(?<remaining>.*)",
"replacement": "/${remaining}"
}
}
],
"uri": "lb://ACCTSVI",
"order": 0
},
"order": 0
},
...
]
每個路由設定會有個 route_id
作為識別,在路由定義的 predicates
中,可以看到設定了 Path
,這是 Spring Cloud Gateway 內建的斷言器工廠 Bean 名稱,pattern
,這表示對於 http://localhost:5555/acctsvi/xxxx
的請求,會轉給 uri
設定的對象,lb://ACCTSVI
表示轉給服務 ID 為 ACCTSVI
的服務。
filters
中設定了 RewritePath
,這是個過濾器工廠 Bean 名稱,依照 regexp
的規則,會捕捉請求中的 /acctsvi/
之後的部份,套用至服務的 URI 上,也就是 http://localhost:5555/acctsvi/xxxx
的請求,將會轉發至 http://acctsvi-uri/xxxx
。
predicates
與 filters
是 Spring Cloud Gateway 的重要特性,predicates
斷言哪些路徑符合路由定義,filters
設定哪些過濾器要套用在上頭,除了透過設定檔之外,必要時,都可以透過程式碼來自訂。
Spring Cloud Gateway 也內建了一些斷言器工廠與過濾器工廠,這些工廠類別,是可以透過屬性檔來定義的,必要時,也可以自訂工廠類別。
就以上的設定來說,請求 http://localhost:5555/acctsvi/accountByName?username=caterpillar
就可以得到以下回應:
{
"name": "caterpillar",
"email": "caterpillar@openhome.cc",
"password": "$2a$10$CEkPOmd.Uid2FpIOHA6Cme1G.mvhWfelv2hPu7cxZ/vq2drnXaVo.",
"_links": {
"self": {
"href": "http://Justin-2017:8084/accountByNameEmail?username=caterpillar"
}
}
}
如果想要自訂路由,可以寫個 application.yml(若不想自動建立路由,可以將 spring.cloud.gateway.discovery.locator.enabled
與 spring.cloud.gateway.discovery.locator.lowerCaseServiceId
註解掉):
spring:
application:
name: gateway
cloud:
gateway:
routes:
- predicates:
- Path=/acct/**
filters:
- StripPrefix=1
uri: lb://acctsvi
- predicates:
- Path=/msg/**
filters:
- StripPrefix=1
uri: lb://msgsvi
- predicates:
- Path=/email/**
filters:
- StripPrefix=1
uri: lb://email
StripPrefix
也是內建的過濾器工廠 Bean 名稱,設定值為 1 表示將路徑中的第一個階層去除,其餘保留用來轉發請求,請求 http://localhost:5555/actuator/gateway/routes
的話,就可以看到以下資訊:
[
{
"route_id": "545d278b-192b-4370-8156-161815957f91",
"route_definition": {
"id": "545d278b-192b-4370-8156-161815957f91",
"predicates": [
{
"name": "Path",
"args": {
"_genkey_0": "/acct/**"
}
}
],
"filters": [
{
"name": "StripPrefix",
"args": {
"_genkey_0": "1"
}
}
],
"uri": "lb://acctsvi",
"order": 0
},
"order": 0
},
...
]
也就是對 http://localhost:5555/acct/accountByName?username=caterpillar
的請求,會轉給 http://acctsvi-url/accountByName?username=caterpillar
。
如果想要設定 api 前置路徑,就是修改一下 StripPrefix=1
為 StripPrefix=2
:
spring:
application:
name: gateway
cloud:
gateway:
default-filters:
- StripPrefix=2
routes:
- predicates:
- Path=/api/acct/**
uri: lb://acctsvi
- predicates:
- Path=/api/msg/**
uri: lb://msgsvi
- predicates:
- Path=/api/email/**
uri: lb://email
對於每個路由都要套用的過濾器,可以使用 default-filters
來設置,就以上設定來說,可以請求 http://localhost:5555/api/acct/accountByName?username=caterpillar
來取得使用者資訊。
一開始自動根據服務 ID 建立路由時,可以看到 RewritePath
,它也是內建的過濾器工廠,可以運用規則表示式來進行路徑重寫,因此,也可以這麼設置 api 前置:
spring:
application:
name: gateway
cloud:
gateway:
default-filters:
- RewritePath=/api/.*?/(?<remaining>.*), /$\{remaining}
routes:
- predicates:
- Path=/api/acct/**
uri: lb://acctsvi
- predicates:
- Path=/api/msg/**
uri: lb://msgsvi
- predicates:
- Path=/api/email/**
uri: lb://email
就目前的設定來說,在客戶端的部份,〈使用 Zuul〉中的 gossip 就可以了,畢竟溝通的介面沒有改變,因為 spring.application.gateway
設為 gateway
,記得改一下 @FeignClient
中設定的服務 ID 為 gateway
。
你可以在 Gateway 中找到以上的範例專案。