1、前言
在當下學習和使用 spring cloud 技術棧的熱潮中,網關已經成了不可或缺的內容。開發者在選擇用來解決特定領域內問題的框架時,多瞭解幾款相關同類產品可加大選擇餘地。除了 Netflix 的 zuul 之外,spring cloud gateway 可作爲開發者的另一個選擇。
Zuul 分 1.x 和 2.x 版本。Zuul 2.x 版本和 spring cloud gateway 都使用 Non-Blocking I/O 模型。
——節選自“https://mp.weixin.qq.com/s/jCQR1WkOsiOzozMAeIPk3g 「歐文雪」”
文章多參考
「歐文雪」:https://mp.weixin.qq.com/s/jCQR1WkOsiOzozMAeIPk3g
芋道源碼http://www.iocoder.cn/categories/Spring-Cloud-Gateway/
1.1 Gateway 技術棧
開發者瞭解以下技術,學習 gateway 會更加容易。
① project reactor,遵循 Reactive Streams Specification,使用非阻塞編程模型。
② webflux,基於 spring 5.x 和 reactor-netty 構建,不依賴於 Servlet 容器,但是可以在支持 Servlet 3.1 Non-Blocking IO API 的容器上運行。
1.2 Gateway 工作機制
Spring cloud gateway 的工作機制大體如下:
① Gateway 接收客戶端請求。
② 客戶端請求與路由信息進行匹配,匹配成功的才能夠被髮往相應的下游服務。
③ 請求經過 Filter 過濾器鏈,執行 pre 處理邏輯,如修改請求頭信息等。
④ 請求被轉發至下游服務並返回響應。
⑤ 響應經過 Filter 過濾器鏈,執行 post 處理邏輯。
⑥ 向客戶端響應應答。
Gateway 工作機制可參考下圖(圖片來自官方文檔):
本文主要介紹 spring cloud gateway 核心組件的初始化步驟。然後,描述當一個外部請求獲取後,經過的處理流程。
2、核心組件
2.1 最核心組件 Route
(1) Route
Route 是 gateway 中最基本的組件之一,表示一個具體的路由信息載體。
Route源代碼:
public class Route implements Ordered {
private final String id; // ①
private final URI uri; // ②
private final int order; // ③
private final AsyncPredicate<ServerWebExchange> predicate; // ④
private final List<GatewayFilter> gatewayFilters; // ⑤
Route 主要定義瞭如下幾個部分:
① id,標識符,區別於其他 Route。
② destination uri,路由指向的目的地 uri,即客戶端請求最終被轉發的目的地。
③ order,用於多個 Route 之間的排序,數值越小排序越靠前,匹配優先級越高。
④ predicate,謂語,表示匹配該 Route 的前置條件,即滿足相應的條件纔會被路由到目的地 uri。
⑤ gateway filters,過濾器用於處理切面邏輯,如路由轉發前修改請求頭等。
(2) AsyncPredicate
Predicate 即 Route 中所定義的部分,用於條件匹配,請參考 Java 8 提供的 Predicate 和 Function。
AsyncPredicate源代碼:
public interface AsyncPredicate<T> extends Function<T, Publisher<Boolean>> {
default AsyncPredicate<T> and(AsyncPredicate<? super T> other) { // ①
Objects.requireNonNull(other, "other must not be null");
return t -> Flux.zip(apply(t), other.apply(t))
.map(tuple -> tuple.getT1() && tuple.getT2());
}
default AsyncPredicate<T> negate() { // ②
return t -> Mono.from(apply(t)).map(b -> !b);
}
default AsyncPredicate<T> or(AsyncPredicate<? super T> other) { // ③
Objects.requireNonNull(other, "other must not be null");
return t -> Flux.zip(apply(t), other.apply(t))
.map(tuple -> tuple.getT1() || tuple.getT2());
}
}
AsyncPredicate 定義了 3 種邏輯操作方法:
① and ,與操作,即兩個 Predicate 組成一個,需要同時滿足。
② negate,取反操作,即對 Predicate 匹配結果取反。
③ or,或操作,即兩個 Predicate 組成一個,只需滿足其一。
(3) GatewayFilter
很多框架都有 Filter 的設計,用於實現可擴展的切面邏輯。
GatewayFilter 源代碼:
public interface GatewayFilter extends ShortcutConfigurable {
String NAME_KEY = "name";
String VALUE_KEY = "value";
/**
* Process the Web request and (optionally) delegate to the next
* {@code WebFilter} through the given {@link GatewayFilterChain}.
* @param exchange the current server exchange
* @param chain provides a way to delegate to the next filter
* @return {@code Mono<Void>} to indicate when request processing is complete
*/
Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);
}
Filter 最終是通過 filter chain 來形成鏈式調用的,每個 filter 處理完 pre filter 邏輯後委派給 filter chain,filter chain 再委派給下一下 filter。
GatewayFilterChain 源代碼:
public interface GatewayFilterChain {
/**
* Delegate to the next {@code WebFilter} in the chain.
* @param exchange the current server exchange
* @return {@code Mono<Void>} to indicate when request handling is complete
*/
Mono<Void> filter(ServerWebExchange exchange);
}
2.2 如何構建 Route
如何構建 Route 組件?Spring 提供了兩種方式:外部化配置和編程的方式。
(1) 外部化配置
Spring cloud gateway 提供了一些開箱即用的 Predicate 和 Filter,它們通過工廠模式來生產。
下面這個例子由官方文檔中的兩個示例組合而成。
spring:
cloud:
gateway: # ①
routes: # ②
- id: cookie_route # ③
uri: http://example.org # ④
predicates: # ⑤
- Cookie=chocolate, ch.p # ⑥
filters: # ⑦
- AddRequestHeader=X-Request-Foo, Bar # ⑧
詳細說明:
① “spring.cloud.gateway” 爲固定前綴。
② 定義路由信息列表,即可定義多個路由。
③ 聲明瞭一個 id 爲 “cookie_route” 的路由。
④ 定義了路由的目的地 uri,即請求轉發的目的地。
⑤ 聲明 predicates,即請求滿足相應的條件才能匹配成功。
⑥ 定義了一個 Predicate,當名稱爲 chocolate 的 Cookie 的值匹配ch.p時 Predicate 才能夠匹配,它由 CookieRoutePredicateFactory 來生產。
⑦ 聲明 filters,即路由轉發前後處理的過濾器。
⑧ 定義了一個 Filter,所有的請求轉發至下游服務時會添加請求頭 X-Request-Foo:Bar ,由AddRequestHeaderGatewayFilterFactory 來生產。
(2) 編程方式
開發者還可以通過編程的方式來定義 Route,編程的方式會更加靈活。
下面通過 fluent API RouteLocatorBuilder 來構建 RouteLocator。
示例(根據官方文檔改造):
// static imports from GatewayFilters and RoutePredicates
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) { // ①
return builder.routes() // ②
.route(r -> r.host("**.abc.org").and().path("/image/png") // ③
.filters(f ->
f.addResponseHeader("X-TestHeader", "foobar")) // ④
.uri("http://httpbin.org:80") // ⑤
)
.build();
}
① RouteLocatorBuilder bean 在 spring-cloud-starter-gateway 模塊自動裝配類中已經聲明,可直接使用。RouteLocator 封裝了對 Route 獲取的定義,可簡單理解成工廠模式。
② RouteLocatorBuilder 可以構建多個路由信息。
③ 指定了 Predicates,這裏包含兩個:
請求頭Host需要匹配**.abc.org,通過 HostRoutePredicateFactory 產生。
請求路徑需要匹配/image/png,通過 PathRoutePredicateFactory 產生。
④ 指定了一個 Filter,下游服務響應後添加響應頭X-TestHeader:foobar,通過AddResponseHeaderGatewayFilterFactory 產生。
⑤ 指定路由轉發的目的地 uri。
如果說外部化配置完全是個黑盒,那麼通過編程的方式使開發者向白盒靠近了一步。因爲開發者需要使用 gateway 的 api,需要開發者對其有工作機制有所瞭解。
2.3 Route 構建的原理
產生Route的兩種方式包括外部化配置方式和自定義編程方式,下圖展示了這兩種方式。
通過自定義RouteLocator方式構建,直接由開發者通過工廠方法RouteLocatorBuilder產生一個Route。故不再細解。本節重點講通過外部化配置方式構建。
(1) 通過RouteDefinition構建
外部化配置方式由RouteDefinitionLocator產生RouteDefinition,再由RouteDefinitionRouteLocator將RouteDefinition轉換爲Route。其中,外部化配置方式除了上文中提到的配置文件方式(PropertiesRouteDefinitionLocator)外,還包括RouteDefinitionRepository、DiscoveryClientRouteDefinitionLocator(通過註冊中心發現)。
關於RouteDefinitionRouteLocator如何將RouteDefinition轉換爲Route,請參考芋道源碼route-locator-route-definition
(2) RouteDefinition
這裏需要先介紹RouteDefinition類,RouteDefinition類定義了外部化配置參數的類,它會被轉化爲Route。RouteDefinition 源碼如下:
public class RouteDefinition {
@NotEmpty
private String id = UUID.randomUUID().toString(); // ①
@NotEmpty
@Valid
private List<PredicateDefinition> predicates = new ArrayList<>(); // ②
@Valid
private List<FilterDefinition> filters = new ArrayList<>(); // ③
@NotNull
private URI uri; // ④
private int order = 0; // ⑤
}
① 定義 Route 的 id,默認使用 UUID。
② 定義 Predicate。
③ 定義 Filter。
④ 定義目的地 URI。
⑤ 定義 Route 的序號。
可見,RouteDefinition 中所定義的屬性與 Route 本身是一一對應的。
(3) FilterDefinition
同樣遵循組件名前綴 + Definition 後綴的命名規範,用於定義 Filter。
public class FilterDefinition {
@NotNull
private String name; // ①
private Map<String, String> args = new LinkedHashMap<>(); // ②
① 定義了 Filter 的名稱,符合特定的命名規範,爲對應的工廠名前綴。
② 一個鍵值對參數用於構造 Filter 對象。
外部配置到 FilterDefinition 對象綁定
以 AddRequestHeader GatewayFilter Factory 爲例:
spring:
cloud:
gateway:
routes:
- id: add_request_header_route
uri: http://example.org
filters:
- AddRequestHeader=X-Request-Foo, Bar # ①
① 這一行配置被 spring 解析後會綁定到一個 FilterDefinition 對象。
AddRequestHeader ,對應 FilterDefinition 中的 name 屬性。
X-Request-Foo, Bar ,會被解析成 FilterDefinition 中的 Map 類型屬性 args。此處會被解析成兩組鍵值對,以英文逗號將=後面的字符串分隔成數組,key是固定字符串 genkey + 數組元素下標,value爲數組元素自身。
相關源碼:
// FilterDefinition 構造函數
public FilterDefinition(String text) {
int eqIdx = text.indexOf("=");
if (eqIdx <= 0) {
setName(text);
return;
}
setName(text.substring(0, eqIdx));
String[] args = tokenizeToStringArray(text.substring(eqIdx+1), ",");
for (int i=0; i < args.length; i++) {
this.args.put(NameUtils.generateName(i), args[i]); // ①
}
}
// ① 使用到的工具類 NameUtils 源碼
public class NameUtils {
public static final String GENERATED_NAME_PREFIX = "_genkey_";
public static String generateName(int i) {
return GENERATED_NAME_PREFIX + i;
}
}
(4) PredicateDefinition
同樣遵循組件名前綴 + Definition 後綴的命名規範,用於定義 Predicate。
public class PredicateDefinition {
@NotNull
private String name; // ①
private Map<String, String> args = new LinkedHashMap<>(); // ②
① 定義了 Predicate 的名稱,它們要符固定的命名規範,爲對應的工廠名稱。
② 一個 Map 類型的參數,構造 Predicate 使用到的鍵值對參數。
外部化配置綁定到 PredicateDefinition 源碼邏輯與 FilterDefinition 類似,不再贅述。
2.4 框架初始化步驟
Spring boot 遵循規約大於配置的原則,starter 模塊都有對應的以模塊名稱作前綴,以 “AutoConfiguration” 後綴的自動裝配類。同樣的還有以模塊名前綴,以Properties後綴的配置類作爲支持。Gateway 模塊自動裝配類及對應的配置類參考:
在官方提供的實例項目 spring-cloud-gateway-sample ,我們看到 GatewaySampleApplication 上有 @EnableAutoConfiguration 註解。因爲該項目導入了 spring-cloud-gateway-core 依賴庫,它會掃描 Spring Cloud Gateway 的配置。(注意,我們的應用中,可能使用了@SpringBootApplication註解,該註解包含@EnableAutoConfiguration註解)
網關初始化步驟參考:芋道源碼 網關初始化
3、請求流程
3.1 網關請求步驟
我們一起來看看,一個請求是怎麼被 Spring Cloud Gateway 處理的,如下圖 :
轉換爲官方的圖:
org.springframework.web.reactive.DispatcherHandler :接收到請求,匹配 HandlerMapping ,此處會匹配到 RoutePredicateHandlerMapping 。
org.springframework.cloud.gateway.handler.RoutePredicateHandlerMapping :接收到請求,匹配 Route 。
org.springframework.cloud.gateway.handler.FilteringWebHandler :獲得 Route 的 GatewayFilter 數組,創建 GatewayFilterChain 處理請求。
3.2 DispatcherHandler
org.springframework.web.reactive.DispatcherHandler ,請求分發處理器,Spring WebFlux 的訪問入口。可能大多數人對這個類都比較陌生,我們來看看他在 Spring MVC 的兄弟 DispatcherServlet 是不是就有點熟悉的感覺。
下面來看看 DispatcherHandler#handle(ServerWebExchange) 方法,代碼如下 :
1: public class DispatcherHandler implements WebHandler, ApplicationContextAware {
2:
3: @Nullable
4: private List<HandlerMapping> handlerMappings;
5:
6: @Nullable
7: private List<HandlerAdapter> handlerAdapters;
8:
9: @Override
10: public Mono<Void> handle(ServerWebExchange exchange) {
11: if (logger.isDebugEnabled()) {
12: ServerHttpRequest request = exchange.getRequest();
13: logger.debug("Processing " + request.getMethodValue() + " request for [" + request.getURI() + "]");
14: }
15: if (this.handlerMappings == null) {
16: return Mono.error(HANDLER_NOT_FOUND_EXCEPTION);
17: }
18: return Flux.fromIterable(this.handlerMappings)
19: .concatMap(mapping -> mapping.getHandler(exchange))
20: .next()
21: .switchIfEmpty(Mono.error(HANDLER_NOT_FOUND_EXCEPTION))
22: .flatMap(handler -> invokeHandler(exchange, handler))
23: .flatMap(result -> handleResult(exchange, result));
24: }
25: }
第 18 至 20 行 :順序使用 handlerMappings 獲得對應的 WebHandler 。
使用 #concatMap(Function) 操作符的原因是考慮 handlerMappings 的順序性,具體作用是將所有handlermapping按照順序組合起來 。然後,遍歷所有handlermapping,找到公共抽象類AbstractHandlerMapping,該類有getHandler(ServerWebExchange exchange)方法,該方法會調用所有HandlerMapping的getHandlerInternal方法(目前發現只有RouterFunctionMapping和RoutePredicateHandlerMapping有該方法)。
使用官方 spring-cloud-gateway-sample 項目,此處打斷點,handlerMappings 變量值如下圖 :
第 19 行,調用 HandlerMapping#getHandler(ServerWebExchange) 獲得 Handler 。在整理,RoutePredicateHandlerMapping 匹配請求對應的 Route ,並返回 FilteringWebHandler 。此時,FilteringWebHandler 還並未獲得 Route 的 GatewayFilter ,創建 GatewayFilterChain 處理請求。
第 21 行 :如果匹配不到 WebHandler ,返回 HANDLER_NOT_FOUND_EXCEPTION 。
第 22 行 :調用 #handle() 方法,執行 Handler 。代碼如下 :
1: private Mono<HandlerResult> invokeHandler(ServerWebExchange exchange, Object handler) {
2: if (this.handlerAdapters != null) {
3: for (HandlerAdapter handlerAdapter : this.handlerAdapters) {
4: if (handlerAdapter.supports(handler)) {
5: return handlerAdapter.handle(exchange, handler);
6: }
7: }
8: }
9: return Mono.error(new IllegalStateException("No HandlerAdapter: " + handler));
10: }
使用官方 spring-cloud-gateway-sample 項目,此處打斷點,handlerMappings 變量值如下圖 :
第 2 至 8 行 :順序匹配 HandlerAdapter ,通過調用 HandlerAdapter#handle(ServerWebExchange, Object) 方法,從而執行 Handler 。在此處,我們會匹配到 SimpleHandlerAdapter 。
第 9 行 :匹配不到 HandlerAdapter ,返回 IllegalStateException 。
第 23 行 :調用 #handleResult() 方法,處理結果。SimpleHandlerAdapter 返回的是 Mono.empty() ,所以不會觸發該方法。#handleResult() 代碼如下 :
1: private Mono<Void> handleResult(ServerWebExchange exchange, HandlerResult result) {
2: return getResultHandler(result).handleResult(exchange, result)
3: .onErrorResume(ex -> result.applyExceptionHandler(ex).flatMap(exceptionResult ->
4: getResultHandler(exceptionResult).handleResult(exchange, exceptionResult)));
5: }
3.3 SimpleHandlerAdapter
org.springframework.web.reactive.result.SimpleHandlerAdapter ,執行 WebHandler 的處理器適配器。
#handle(ServerWebExchange, Object) 方法,代碼如下 :
1: @Override
2: public Mono<HandlerResult> handle(ServerWebExchange exchange, Object handler) {
3: WebHandler webHandler = (WebHandler) handler;
4: Mono<Void> mono = webHandler.handle(exchange);
5: return mono.then(Mono.empty());
6: }
第 3 至 4 行 :調用 WebHandler#handle(ServerWebExchange) 方法,執行處理器。例如,WebHandler 爲 FilteringWebHandler 時,獲得 Route 的 GatewayFilter 數組,創建 GatewayFilterChain 處理請求。
第 5 行 :在 WebHandler 執行完後 ( #then(Mongo) ),然後返回 Mono.empty() 。
3.4 RoutePredicateHandlerMapping
org.springframework.cloud.gateway.handler.RoutePredicateHandlerMapping ,匹配 Route ,並返回處理 Route 的 FilteringWebHandler 。
RoutePredicateHandlerMapping 構造方法,代碼如下 :
public class RoutePredicateHandlerMapping extends AbstractHandlerMapping {
private final FilteringWebHandler webHandler;
private final RouteLocator routeLocator;
public RoutePredicateHandlerMapping(FilteringWebHandler webHandler, RouteLocator routeLocator) {
this.webHandler = webHandler;
this.routeLocator = routeLocator;
setOrder(1); // RequestMappingHandlerMapping 之後
}
}
調用 #setOrder(1) 的原因,Spring Cloud Gateway 的 GatewayWebfluxEndpoint 提供 HTTP API ,不需要經過網關,它通過 RequestMappingHandlerMapping 進行請求匹配處理。RequestMappingHandlerMapping 的 order = 0 ,需要排在 RoutePredicateHandlerMapping 前面。所以,RoutePredicateHandlerMapping 設置 order = 1 。
#getHandlerInternal() 方法,在 DispatcherHandler#handle(ServerWebExchange) 方法的【第 19 行】被調用,匹配 Route ,並返回處理 Route 的 FilteringWebHandler 。代碼如下 :
1: @Override
2: protected Mono<?> getHandlerInternal(ServerWebExchange exchange) {
3: // 設置 GATEWAY_HANDLER_MAPPER_ATTR 爲 RoutePredicateHandlerMapping
4: exchange.getAttributes().put(GATEWAY_HANDLER_MAPPER_ATTR, getClass().getSimpleName());
5:
6: return lookupRoute(exchange) // 匹配 Route
7: // .log("route-predicate-handler-mapping", Level.FINER) //name this
8: .flatMap((Function<Route, Mono<?>>) r -> { // 返回 FilteringWebHandler
9: if (logger.isDebugEnabled()) {
10: logger.debug("Mapping [" + getExchangeDesc(exchange) + "] to " + r);
11: }
12:
13: // 設置 GATEWAY_ROUTE_ATTR 爲 匹配的 Route
14: exchange.getAttributes().put(GATEWAY_ROUTE_ATTR, r);
15: // 返回
16: return Mono.just(webHandler);
17: }).switchIfEmpty(Mono.empty().then(Mono.fromRunnable(() -> { // 匹配不到 Route
18: if (logger.isTraceEnabled()) {
19: logger.trace("No RouteDefinition found for [" + getExchangeDesc(exchange) + "]");
20: }
21: })));
22: }
第 4 行 :設置 GATEWAY_HANDLER_MAPPER_ATTR 爲 RoutePredicateHandlerMapping 。
第 6 行 :調用 #lookupRoute(ServerWebExchange) 方法,匹配 Route 。
第 8 至 16 行 :返回 Route 的處理器 FilteringWebHandler 。
第 14 行 :設置 GATEWAY_ROUTE_ATTR 爲匹配的 Route 。
第 16 行 :返回 FilteringWebHandler。
第 17 至 21 行 :匹配不到 Route ,返回 Mono.empty() ,即不返回處理器。這樣會不會有問題?不會,在 DispatcherHandler#handle(ServerWebExchange) 方法的【第 21 行】,我們可以看到,當沒有合適的 Handler ,返回 Mono.error(HANDLER_NOT_FOUND_EXCEPTION) 。
#lookupRoute(ServerWebExchange) 方法,順序匹配 Route 。代碼如下 :
1: protected Mono<Route> lookupRoute(ServerWebExchange exchange) {
2: return this.routeLocator.getRoutes()
3: .filter(route -> route.getPredicate().test(exchange))
4: .next()
5: //TODO: error handling
6: .map(route -> {
7: if (logger.isDebugEnabled()) {
8: logger.debug("RouteDefinition matched: " + route.getId());
9: }
10: validateRoute(route, exchange);
11: return route;
12: });
13: }
第 2 至 4 行 :調用 RouteLocator#getRoutes() 方法,獲得全部 Route ,並調用 Predicate#test(ServerWebExchange) 方法,順序匹配一個 Route。
第 5 行 :未來會增加匹配過程中發生異常的處理。目前,任何一個 Predicate#test(ServerWebExchange) 的方法調用發生異常時,都會導致匹配不到 Route 。一定要注意。
第 6 至 11 行 :調用 #validateRoute(Route, ServerWebExchange) 方法,校驗 Route 的有效性。目前該方法是個空方法,可以通過繼承 RoutePredicateHandlerMapping 進行覆蓋重寫。
3.5 FilteringWebHandler
在 SimpleHandlerAdapter 裏,我們看到 SimpleHandlerAdapter#handle(ServerWebExchange, Object) 調用 FilteringWebHandler#handle(ServerWebExchange) 方法,處理請求。
FilteringWebHandler 通過創建請求對應的 Route 對應的 GatewayFilterChain 進行處理。
org.springframework.cloud.gateway.handler.FilteringWebHandler ,#handle(ServerWebExchange) 代碼如下 :
1: public class FilteringWebHandler implements WebHandler {
2:
3: /**
4: * 全局過濾器
5: */
6: private final List<GatewayFilter> globalFilters;
7:
8: @Override
9: public Mono<Void> handle(ServerWebExchange exchange) {
10: // 獲得 Route
11: Route route = exchange.getRequiredAttribute(GATEWAY_ROUTE_ATTR);
12: // 獲得 GatewayFilter
13: List<GatewayFilter> gatewayFilters = route.getFilters();
14: List<GatewayFilter> combined = new ArrayList<>(this.globalFilters);
15: combined.addAll(gatewayFilters);
16:
17: // 排序
18: //TODO: needed or cached?
19: AnnotationAwareOrderComparator.sort(combined);
20: logger.debug("Sorted gatewayFilterFactories: "+ combined);
21:
22: // 創建 DefaultGatewayFilterChain
23: return new DefaultGatewayFilterChain(combined).filter(exchange);
24: }
25: }
本方法涉及到的過濾器 GlobalFilter / GatewayFilter / GatewayFilterAdapter / OrderedGatewayFilter 類,參考芋道源碼《Spring-Cloud-Gateway 源碼解析 —— 過濾器 (4.1) 之 GatewayFilter 一覽》 詳細解析。
第 11 行 :從 GATEWAY_ROUTE_ATTR 獲得 請求對應的 Route 。
第 13 至 15 行 :獲得 GatewayFilter 數組,包含 route.filters 和 globalFilters 。
第 19 行 :排序獲得的 GatewayFilter 數組。
第 23 行 :使用獲得的 GatewayFilter 數組創建 DefaultGatewayFilterChain ,過濾處理請求。