書接上文, 前一篇介紹了 Spring Cloud Gateway 的基本組成、工作原理和路由, 本篇為
spring cloud gateway 第二小節, 主要介紹斷言和過濾器斷言
基本概念
Predicate 來自於 Java8 的接口, 其接受一個輸入參數(ServerWebExchange),返回一個布爾值結果。該接口包含多種默認方法來將 Predicate 組合成其他複雜的邏輯(比如:與,或,非)。可以用於接口請求參數校驗、判斷新老數據是否有變化需要進行更新操作。 支持的邏輯運算有:
- and, 與操作, 即兩個 Predicate 組成一個, 需要同時滿足;
- negate, 取反操作, 即對 Predicate 匹配結果取反;
- or, 或操作, 即兩個 Predicate 組成一個, 只需要滿足其中之一即可
Spring Cloud Gateway 中 Predicate 為 Route 所定義的部分,用於條件匹配, 當定義多個 Predicate 時, 其邏輯連接運算符為 and.
Spring Cloud Gateway 內置了許多 Predicate, 這些 Predicate 的源碼位於 org.springframework.cloud.gateway.handler.predicate 包下, 具體作用如下:
每一種 Predicate 都會對當前的客戶端請求進行判斷, 是否滿足當前的要求, 如果滿足則交給當前請求, 如果有很多個 Predicate, 並且一個請求滿足這些 Predicate, 則按照配置的順序第一個生效。
應用
官方提供的 Predicate 應用舉例:
- After。 接受一個時間類型的參數, 當請求在這個設置的時間後發生, 就會被轉發到此路由上. 以下示例表示匹配上海時間2019-09-20 12:12:12後所有的請求
<code>spring: cloud: gateway: routes: - id: after_route uri: http://example.org predicates: - After=2019-09-20T12:12:12.789+08:00[China/Shanghai] /<code>
- Before。 與 After 相反, 接受一個時間類型的參數, 當請求在這個設置的時間前發生, 就會被轉發到此路由上. 以下示例表示匹配上海時間2019-09-20 12:12:12前所有的請求
<code>spring: cloud: gateway: routes: - id: after_route uri: http://example.org predicates: - Before=2019-09-20T12:12:12.789+08:00[China/Shanghai] /<code>
- Between。 接受兩個時間類型的參數, 當請求在這個設置的時間區間發生, 就會被轉發到此路由上. 以下示例表示匹配上海時間2019-09-20 12:12:12 ~ 2019-09-30 12:12:12之間所有的請求
<code>spring: cloud: gateway: routes: - id: after_route uri: http://example.org predicates: - Between=2019-09-20T12:12:12.789+08:00[China/Shanghai], 2019-09-30T12:12:12.789+08:00[China/Shanghai] /<code>
- Cookie。 接受兩個參數, 分別表示 cookie 的 name 和 cookie 的 值, 當請求信息中 cookie 帶有配置的 name, 且其值為匹配的配置的值(正則匹配), 則通過此路由。 以下示例表示匹配請求信息中的 Cookie 中存在foo=bar的所有請求。
<code>spring: cloud: gateway: routes: - id: after_route uri: http://example.org predicates: - Cookie=foo, bar /<code>
- Header。 接受兩個參數,分別表示請求頭名稱,對應值的正則表達式。 當請求頭中能有匹配的對應的鍵值對時, 則通過此路由, 以下示例表示匹配請求頭中存在foo=bar的所有請求。
<code>spring: cloud: gateway: routes: - id: after_route uri: http://example.org predicates: - Header=foo, bar /<code>
- Host。 接受一個參數,為 host 列表,多個 host 之間用英文,隔開。 host 是通過Ant風格的模式匹配的。 滿足列表中的任一 host, 則通過此路由。 以下示例表示匹配請求頭中 hostAnt匹配所有已foo.com結尾或者www.bar.開頭的請求
<code>spring: cloud: gateway: routes: - id: after_route uri: http://example.org predicates: - Host=**.foo.com, www.bar.** /<code>
- Method. 接受一個參數, 為請求方法。即GET/POST/PUT/DETELE/OPTION/HEADER等, 以下示例表示匹配請求方式為GET的所有請求
<code>spring: cloud: gateway: routes: - id: after_route uri: http://example.org predicates: - Method=GET /<code>
- Path. 接受兩個參數。 分別為 Spring 的PathMatcher列表和matchOptionalTrailingSeparator(可選)。 表示請求的路徑匹配列表中的任一一個, 則通過此路由。
<code>spring: cloud: gateway: routes: - id: after_route uri: http://example.org predicates: - Path=/foo/{segment},/bar/{segment} /<code>
- Query。 接受兩個參數, 分別為請求的參數和請求參數值的正則(可選)。
<code>spring: cloud: gateway: routes: - id: after_route uri: http://example.org predicates: - Query=foo # 請求參數中有 foo,即通過此路由 # - Query=foo, ba. # 請求參數中有 foo,且其值是以 ba 開頭, 即通過此路由 /<code>
- RemoteAddr。 接受一個參數。 該參數為一個IPV4/IPV6網段的列表。注意,此處列表的大小不能小於1。
<code>spring: cloud: gateway: routes: - id: after_route uri: http://example.org predicates: - RemoteAddr=192.168.1.1/24 /<code>
一般情況下, 官方提供的 Predicate, 已經能滿足我們的需求了。 為了可擴展性, 我們也可以自定義 Predicate。 主要的方法是繼承 AbstractRoutePredicateFactory, 主要實現其中的兩個方法:
- shortcutFieldOrder(). Config 對應的字段
- Predicate apply(Config config)。 具體的斷言邏輯。
同時需要注意以下兩點:
- 自定義 Predicate 的構造方法用來裝配置的類程序會自動把配置的 value 傳入 apply 中的入參;
- 自定義 Predicate 類需要加載到 Spring 容器中, 方式不限。
原理
spring cloud gateway 使用的 Spring WebFlux, 其入口的請求分發處理器為 org.springframework.web.reactive.DispatcherHandler (類似於 Spring MVC 中 DispatcherServlet)。 DispatcherHandler 在處理請求分發時, 通過 HandlerMapping#getHandler(ServerWebExchange) 獲取 Handler。 如果匹配不到 WebHandler, 則會返回 HANDLER_NOT_FOUND_EXCEPTION。 若存在匹配的 WebHandler, 則執行其 handler() 方法(通過調用 HandlerAdapter#handle(ServerWebExchange, Object) 方法)。
在以上的過程中, 在獲取 Handler 時, 是通過 RoutePredicateHandlerMapping#getHandlerInternal, 查找已配置的 Route 中,是否有匹配當前請求的 Route, 若能匹配到 Route, 則返回處理 Route 的 FilteringWebHandler, 否則不返回處理器。 由此可見, 我們配置的 Route 中的 Predicate 的具體應用到的地方就在此方法中, 有興趣的小夥伴,可以跟蹤下源代碼。
過濾器
基本概念
Gateway 中的 Filter 與其他框架一樣, 都是用於實現可擴展的切面邏輯。 Filter 最終是通過 filter chain 來形成鏈式調用的。
filter 的主要作用是將統一處理通過網關請求的服務的共性需求。 在 pre filter 可以做參數校驗、 權限校驗、 流量控制、 日誌輸出、 協議轉換等, 在 post filter 可以做響應內容、 響應頭的修改, 日誌的修改, 流量控制等。
PS: 此處需要注意與 spring cloud netflex zuul 的對比, zuul 支持的過濾器類型有 pre/routing/post/error.
Spring Cloud Gateway 中從作用域來區分, 有兩種, 一種是針對單個路由的 gateway filter, 它在配置文件中進行配置, 與 Predicate 類似。 另外一種是針對於所有路由的 global gateway filter。
應用
針對 Spring Cloud Gateway 2.1.1.RELEASE, 官方提供的常見的 Global Filter 有:
常見的 Route Filter 有:
配置時, 有默認的過濾器和路由指定的過濾器之分, 針對默認的過濾器的配置, 在spring.cloud.gateway.default-filters屬性下配置, 示例如下:
<code>spring cloud: gateway: default-filters: - name: Hystrix args: name: fallbackcmd fallbackUri: forward:/fallback?service=default/<code>
具體配置,與Predicate一樣, 在 spring.cloud.gateway.routes[i].filters[i] 屬性下配置, 示例如下
<code>spring: cloud: gateway: routes: - id: add_request_header_route uri: lb://test-service filters: - AddRequestHeader=X-Request-Foo, Bar/<code>
關於自定義過濾器, 其實很簡單, 只需要繼承 GlobalFilter/GatewayFilter 和 Ordered 接口, 實現其中的 filter() 和 getOrder() 方法即可。 在 filter() 方法中定義需要實現的功能邏輯, getOrder() 方法確定當前過濾器執行的順序。
PS:
- filter() 方法中不能斷了過濾器鏈的處理,即需要通過 chain.filter(exchange, chain) 向下傳遞過濾器鏈;
- 官方聲明, GlobalFilter 接口的定義以及用法在未來的版本里可能會發生變化。但是, 就目前來看, 是可用於生產的, 如果有自定義 GlobalFilter 的需求,理論上也可放心使用——未來即使接口定義以及使用方式發生變化,應該也是平滑過渡的(比如 Zuul 的 Fallback,原先叫ZuulFallbackProvider,後來改叫 FallbackProvider,中間就有段時間新舊使用方式都支持,後面才逐步廢棄老的使用方式)
原理
常見全局過濾器
- ForwardRoutingFilter。 該 Filter 會查看 exchange 的屬性 ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR 的值,如果該值的scheme 是 forward,比如:forward://forward-to,則它會使用 Spring 的 DispatcherHandler 處理該請求。請求 URL 的路徑部分,會被 forward URL中的路徑覆蓋。未修改的原始URL,會被追加到 ServerWebExchangeUtils.GATEWAY_ORIGINAL_REQUEST_URL_ATTR 屬性中。 即其作用是用來做本地 forward。深入理解, 請查看相關源碼。
- LoadBalancerClientFilter。 該 Filter 會查看 exchange 的屬性 ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR 的值,如果該值的 scheme 是 lb,比如:lb://lb-service ,它將會使用 Spring Cloud 的 LoadBalancerClient 來將 lb-service 解析成實際的 host/ip 和 port,並替換掉 ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR 的內容。原始地址會追加到 ServerWebExchangeUtils.GATEWAY_ORIGINAL_REQUEST_URL_ATTR 中。該過濾器還會查看 ServerWebExchangeUtils.GATEWAY_SCHEME_PREFIX_ATTR 屬性,如果發現該屬性的值是 lb ,也會執行相同邏輯。 默認情況下, 若無法在 LoadBalancer 找到指定服務的實例, 則會返回的 HTTP Status 為 503(Unavailable service), 可通過配置 spring.cloud.gateway.loadbalancer.use404=true 讓 HTTP Status 返回 404。 當 LoadBalancer 返回的 ServiceInstance 的 isSecure 的值, 會覆蓋請求的scheme。如若請求打到Gateway上使用的是 HTTPS ,但 ServiceInstance 的 isSecure 是 false,那麼下游收到的則是 HTTP 請求,反之亦然。然而,如果該路由指定了 GATEWAY_SCHEME_PREFIX_ATTR 屬性,那麼前綴將會被剝離,並且路由URL中的 scheme 會覆蓋 ServiceInstance 的配置。 LoadBalancer 是基於 Ribbon 實現的。
- NettyRoutingFilter. ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR 的值的 scheme 是 http 或 https ,則會進過此 Filter。它使用 Netty HttpClient 向下遊發送代理請求。獲得的響應將放在 exchange的 ServerWebExchangeUtils.CLIENT_RESPONSE_ATTR 屬性中,以便在後面的 filter中使用。 對於不使用 Netty 的場景, 官方也提供了 WebClientHttpRoutingFilter 過濾器。
- NettyWriteResponseFilter。如果 exchange 中的 ServerWebExchangeUtils.CLIENT_RESPONSE_ATTR 屬性中有 HttpClientResponse,則運行 NettyWriteResponseFilter 。該過濾器在所有其他過濾器執行完成後執行,並將代理響應協會網關的客戶端側。 對於不使用 Netty 的場景, 官方也提供了 WebClientWriteResponseFilter 過濾器。
- RouteToRequestUrlFilter 。如果 exchange 中的 ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR 屬性中有一個 Route 對象,則運行 RouteToRequestUrlFilter 。它根據請求 URI 創建一個新 URI,但會使用該 Route 對象的 URI屬性進行更新。新 URI 放到 exchange 的 ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR 屬性中。如果 URI具有 scheme前綴,例如 lb//serviceid ,該 lb scheme將從 URI 中剝離,並放到 ServerWebExchangeUtils.GATEWAY_SCHEME_PREFIX_ATTR 中,方便後面的過濾器使用。
- WebsocketRoutingFilter。如果 exchange 中的 ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR 屬性的值的 scheme 是 ws/wss ,則運行 WebsocketRoutingFilter。它底層使用 Spring Web Socket 將Websocket 請求轉發到下游。可為 URI添加 lb 前綴實現負載均衡,如 lb//serviceid。
- GatewayMetricsFilter。 需要啟用 Gateway Metrics,即添加 spring-boot-starter-actuator 依賴。 當 spring.cloud.gateway.metrics.enabled 的值為 true 時,會運行 GatewayMetricsFilter。此過濾器添加名為 gateway.requests 的metric 信息,其中包含以下標記:routeId:路由ID;routeUri:API將路由到的URI;outcome:由 HttpStatus.Series 分類;status:返回給客戶端的 Http Status
這些指標暴露在 /actuator/metrics/gateway.requests 端點中,並且可以輕鬆與 Prometheus 整合
Filter 的執行順序
針對於全局 filter, 每個 GlobalFilter 的實現類都需要實現一個 Ordered#getOrder() 以確定其執行的順序, 默認全局過濾器的執行順序如下:
當因實際的業務場景的需要我們自定義 Global Filter 時, 它的 order 根據以下兩種情況決定:
- 如果自定義的 Global Filter 實現了 Ordered 接口或者加了 @Order 註解, 那麼它的 order 就是它自己設定的值
- 否則, 它就沒有 order
針對每個路由的 Filter, 其執行順序的規則如下:
- 如果 RouteFilter 實現了 Ordered 接口或者寫了 @Order 註解, 那麼它的 order就是它自己設定的值。
- 否則, 它的 order 則是從 1 開始, 按照 Route 中定義的順序依次排序。
在實際執行某個 Route 的時候, Spring Cloud Gateway 會將 Global Filter 和 Route Filter 結合起來並排序:
- 沒有給 order 的 Global Filter 則保持 order 為 null 去排序
- 沒有給 order 的 Route Filter 的 order 則從 1 開始, 根據 Route 中定義的順序執行
- 排序邏輯根據 AnnotationAwareOrderComparator
- 對於 Pre Filter, 執行順序與排序順序
- 對於 Post Filter, 執行順序與排序順序相反
- 對於自定義 Global Filter, 則自定義的 Global Pre Filter 要在 Routing Filter 之前執行自定義的 Global Post Filter 要在 Routing Filter 之後執行或者 NettyWriteResponseFilter 之後執行
- 對於自定義 Route Filter, 則自定義 Route Pre Filter 要在 ForwardPathFilter 和 RouteToRequestUrlFilter 之間,而且不需要實現 Ordered 接口或添加 @Order註解自定義的Route Post Filter比較少見,放在 Routing Filter 或者NettyWriteResponseFilter 之後執行
後續系列計劃如下, 可以關注我, 及時收到最新文檔:
- Spring Cloud Alibaba
- Spring Cloud Netflix
- Ribbon
- Feign
- Hystrix
- Resilience4J
- Spring Cloud Config
- Apollo
- Spring Cloud Stream
- Nacos
- ZipKin
- Skywalking
如果你有什麼想了解的,也可以在文章下評論或者私信我。