本文基於Spring Cloud Gateway 2.1.1.RELEASE。
在講SCG的Filter的排序問題之前得先比較一下Spring Cloud Gateway在對待Filter的方面與Zuul2有着哪些不同。
Filter的Scope
- SCG採用的是Global Filter和Route Filter相結合的方式
- Zuul2則都是Global Filter
SCG所謂Route Filter就是像下面這樣的:
spring:
cloud:
gateway:
routes:
- id: tomcat_route
uri: http://tomcat:8080
predicates:
- Path=/tomcat/docs
filters:
- StripPrefix=1
- RemoveRequestHeader=X-Request-Foo
上面的StripPrefix
和RemoveRequestHeader
就是Route Filter,而SCG的Global Filter則是隱式的,無需顯式配置,它們會在請求過來的時候被SCG調用。
也就是說你可以配置不同的Route,然後爲每個Route配置不同的Route Filter,這一切都是在配置階段就決定下來的。
而Zuul2則都是Global Filter,因此你得運行時在每個Filter內部自己決定是否要幹活,除此之外,發送到Origin(被代理的服務)的url也得你自己設置,下面是一個例子(來自Zuul2 Sample):
public class Routes extends HttpInboundSyncFilter {
@Override
public boolean shouldFilter(HttpRequestMessage httpRequestMessage) {
// ...
return true;
}
@Override
public HttpRequestMessage apply(HttpRequestMessage request) {
// ...
// Route healthchecks to the healthcheck endpoint.;
context.setEndpoint(ZuulEndPointRunner.PROXY_ENDPOINT_FILTER_NAME);
context.setRouteVIP("tomcat");
return request;
}
}
Filter的角色
- 在SCG概念中只有一種Filter(撇開Global和Route的區別),它用代碼來區分Pre Filter、Post Filter。在文檔中還提到了Routing Filter,其實也是Pre Filter。
- Zuul2在代碼中顯示得提供了InboundFilter(負責進來的請求)、OutboundFilter(負責出去的響應)、ProxyEndpoint(負責請求到Origin,串起Inbound和Outbound)。
下面是SCG的Pre Filter(裁剪自官方例子12.2 Writing Custom GatewayFilter Factories):
public class PreGatewayFilterFactory extends AbstractGatewayFilterFactory {
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
// business logic
return chain.filter();
};
}
}
Post Filter的例子:
public class PostGatewayFilterFactory extends AbstractGatewayFilterFactory {
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
return chain.filter(exchange).then(/* business logic */);
};
}
}
在Zuul2裏,你則得分別實現HttpInboundSyncFilter
和HttpOutboundSyncFilter
,ProxyEndpoint
不需要你自己實現。
SCG Filter的問題
SCG的優點很明顯,它做了Zuul2不做的事情:
- 替你決定進來的請求轉發到哪個Origin。在Zuul2裏這個交給你自己來實現。
- 在配置上就決定了這個Route會應用哪些Filter。在Zuul2裏這個交給你自己來判斷。
但是隨着對SCG的深入瞭解,發現了關於Filter的執行順序存在一些坑,如果不瞭解清楚會容易出錯。
Filter的排序
前面講了,SCG在執行過程中Global Filter和Route Filter是一起執行的,那麼它們的order是怎樣的?
先來看看Global Filter,你可以訪問/actuator/gateway/globalfilters
(見文檔)得到Global Filter的排序:
那麼如果你寫了一個自定義 Global Filter,那麼它的order是什麼呢?這個要看情況:
- 如果你的自定義Global Filter實現了
Ordered
接口或者寫了@Order
註解,那麼它的order就是它自己設定的值 - 否則,它就沒有order
關於這點可以看FilteringWebHandler.java的源代碼。
再來看看Route Filter,這也分兩種情況:
- 如果RouteFilter實現了
Ordered
接口或者寫了@Order
註解,那麼它的order就是它自己設定的值。 - 否則,它的order則是從1開始,按照Route中定義的順序依次排序。
關於這點可以看RouteDefinitionRouteLocator.java的源代碼。
最後SCG把它們兩個結合起來,做一個排序,對於沒有order的Filter,它的order則默認爲Ordered.LOWEST_PRECEDENCE
。關於這點可以看FilteringWebHandler.java的源代碼。
用一張圖做總結:
Filter的執行順序
先看SCG文檔3. How It Works中的這張圖:
這張圖大概告訴你了SCG的調用過程,可以看到經過了一堆Filters,但是並沒有告訴你Filter的執行順序。然後在SCG的6.1 Combined Global Filter and GatewayFilter Ordering提到了:
As Spring Cloud Gateway distinguishes between "pre" and "post" phases for filter logic execution (see: How It Works), the filter with the highest precedence will be the first in the "pre"-phase and the last in the "post"-phase.
也就是說意思如果這個Filter是Pre Filter,那麼執行順序和排序順序相同,如果這個Filter是Post Filter則執行順序和排序順序相反。我整理了一下SCG自帶GlobalFilter的執行順序:
可以看到GatewayMetricsFilter既是Pre Filter也是Post Filter。
總結
-
執行某個Route的時候,SCG會將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之後執行
- 自定義Route Pre Filter要在