Gateway 是 Spring Cloud 的一個全新項目,該項目是基於 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技術開發的網關,它旨在為微服務架構提供一種簡單有效的統一的 API 路由管理方式。其核心邏輯是路由轉發+執行過濾器鏈。Gateway是基於WebFlux框架實現的,而WebFlux框架底層則使用了高性能的Reactor模式通信框架Netty。
Gateway 的目標,不僅提供統一的路由方式,並且基於 Filter 鏈的方式提供了網關基本的功能,例如:安全,監控/指標,和限流。
Gateway的三個核心組件: Route(路由)、Predicate(斷言)、Filter(過濾器)。
1. 項目配置
路由配置的時候可以配置斷言、以及過濾器。
1. 路由配置
路由配置有兩種方式,一種是yml 配置, 另一種是代碼配置
1. yml 配置
server:
port: 9527
spring:
application:
name: cloud-gateway
cloud:
gateway:
routes:
- id: payment_routh #payment_route #路由的ID,沒有固定規則但要求唯一,建議配合服務名
# uri: http://localhost:8081 #匹配后提供服務的路由地址
uri: lb://cloud-payment-service #根據服務名稱進行負載均衡替換
predicates:
- Path=/pay/listAll/** # 斷言,路徑相匹配的進行路由
- Host=**.com
- id: cloud-provider-hystrix-payment #payment_route #路由的ID,沒有固定規則但要求唯一,建議配合服務名
uri: lb://cloud-provider-hystrix-payment #根據服務名稱進行負載均衡替換
predicates:
- Path=/hystrix/** # 斷言,路徑相匹配的進行路由
filters:
# 限流過濾器,使用gateway內置令牌算法
- name: RequestRateLimiter
args:
# 令牌桶每秒填充平均速率,即等價於允許用戶每秒處理多少個請求平均數
redis-rate-limiter.replenishRate: 1
# 令牌桶的容量,允許在一秒鍾內完成的最大請求數
redis-rate-limiter.burstCapacity: 2
# 用於限流的鍵的解析器的 Bean 對象的名字。它使用 SpEL 表達式根據#{@beanName}從 Spring 容器中獲取 Bean 對象。
key-resolver: "#{@apiKeyResolver}"
- id: payment_routh2 #payment_route #路由的ID,沒有固定規則但要求唯一,建議配合服務名
# uri: http://localhost:8081 #匹配后提供服務的路由地址
uri: lb://cloud-payment-service #根據服務名稱進行負載均衡替換
predicates:
- Path=/pay/getServerPort/** # 斷言,路徑相匹配的進行路由
# - After=2020-03-12T15:44:15.064+08:00[Asia/Shanghai] #日期后面(用於判斷日期)
# - Before=2020-03-12T15:44:15.064+08:00[Asia/Shanghai] #日期后面(用於判斷日期)
# - Between=2020-03-12T15:44:15.064+08:00[Asia/Shanghai], 2021-03-12T15:44:15.064+08:00[Asia/Shanghai] #日期之間(用於判斷日期)
# - Cookie=uname,zs #帶Cookie,並且uname的值為zs
- Header=X-Request-Id,\d+ #請求頭要有 X-Request-Id屬性並且值為整數的正則表達式
- Header=X-Request-Id2,\d+ #請求頭要有 X-Request-Id屬性並且值為整數的正則表達式
eureka:
instance:
hostname: cloud-gateway-service
client: #服務提供者provider注冊進eureka服務列表內
service-url:
register-with-eureka: true
fetch-registry: true
defaultZone: http://localhost:7001/eureka
2. 代碼配置
package cn.qz.cloud.config; import org.springframework.cloud.gateway.route.RouteLocator; import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class GatewayConfig { /** * 配置了一個id為route-name的路由規則 * 當訪問地址 http://localhost:9527/guonei時會自動轉發到地址: http://news.baidu.com/guonei * * @param builder * @return */ @Bean public RouteLocator customRouteLocator(RouteLocatorBuilder builder) { RouteLocatorBuilder.Builder routes = builder.routes(); routes.route("path_route_eiletxie", r -> r.path("/guonei") .uri("http://news.baidu.com/guonei")).build(); return routes.build(); } @Bean public RouteLocator customRouteLocator2(RouteLocatorBuilder builder) { RouteLocatorBuilder.Builder routes = builder.routes(); routes.route("path_route_eiletxie2", r -> r.path("/guoji") .uri("http://news.baidu.com/guoji")).build(); return routes.build(); } }
代碼內部的配置,相當於沒有使用lb 負載均衡,相當於直接是請求轉發的功能。
2. 全局過濾器配置(會應用到每個路由中)
Gateway 也可以單獨增加全局的filter, 如下:
package cn.qz.cloud.filter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.Ordered; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBufferUtils; import org.springframework.http.HttpMethod; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpRequestDecorator; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import java.nio.charset.StandardCharsets; import java.util.Map; import java.util.stream.Collectors; @Component public class LoggerFilter implements GlobalFilter, Ordered { private static final Logger log = LoggerFactory.getLogger(LoggerFilter.class); @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { ServerHttpRequest request = exchange.getRequest(); String method = request.getMethodValue(); if (HttpMethod.POST.matches(method)) { return DataBufferUtils.join(exchange.getRequest().getBody()) .flatMap(dataBuffer -> { byte[] bytes = new byte[dataBuffer.readableByteCount()]; dataBuffer.read(bytes); String bodyString = new String(bytes, StandardCharsets.UTF_8); logtrace(exchange, bodyString); exchange.getAttributes().put("POST_BODY", bodyString); DataBufferUtils.release(dataBuffer); Flux<DataBuffer> cachedFlux = Flux.defer(() -> { DataBuffer buffer = exchange.getResponse().bufferFactory() .wrap(bytes); return Mono.just(buffer); }); ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator( exchange.getRequest()) { @Override public Flux<DataBuffer> getBody() { return cachedFlux; } }; return chain.filter(exchange.mutate().request(mutatedRequest) .build()); }); } else if (HttpMethod.GET.matches(method)) { Map m = request.getQueryParams(); logtrace(exchange, m.toString()); } return chain.filter(exchange); } /** * 日志信息 * * @param exchange * @param param 請求參數 */ private void logtrace(ServerWebExchange exchange, String param) { ServerHttpRequest serverHttpRequest = exchange.getRequest(); String hostString = serverHttpRequest.getRemoteAddress().getHostString(); String path = serverHttpRequest.getURI().getPath(); String method = serverHttpRequest.getMethodValue(); String headers = serverHttpRequest.getHeaders().entrySet() .stream() .map(entry -> " " + entry.getKey() + ": [" + String.join(";", entry.getValue()) + "]") .collect(Collectors.joining("\n")); log.info("\n" + "---------------- ---------------- ---------------->>\n" + "HttpMethod : {}\n" + "requestHost : {}\n" + "Uri : {}\n" + "Param : {}\n" + "Headers : \n" + "{}\n" + "\"<<---------------- ---------------- ----------------" , method, hostString, path, param, headers); } @Override public int getOrder() { return -1; } }
下面研究其作用過程。
2. 啟動過程查看
按照Springboot 的套路,查看一個配置從AutoConfiguration 類查看。
1. 配置累和properties 查看
org.springframework.cloud.gateway.config.GatewayAutoConfiguration 自動配置類源碼如下:(可以看到ConditionalOnProperty、AutoConfigureBefore、AutoConfigureAfter、ConditionalOnClass、ConditionalOnMissingBean 等條件注解)

/* * Copyright 2013-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.cloud.gateway.config; import java.security.cert.X509Certificate; import java.util.List; import com.netflix.hystrix.HystrixObservableCommand; import io.netty.channel.ChannelOption; import io.netty.handler.ssl.SslContextBuilder; import io.netty.handler.ssl.util.InsecureTrustManagerFactory; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import reactor.core.publisher.Flux; import reactor.netty.http.client.HttpClient; import reactor.netty.resources.ConnectionProvider; import reactor.netty.tcp.ProxyProvider; import rx.RxReactiveStreams; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint; import org.springframework.boot.actuate.health.Health; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.NoneNestedConditions; import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.autoconfigure.web.embedded.NettyWebServerFactoryCustomizer; import org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration; import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.PropertyMapper; import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory; import org.springframework.cloud.gateway.actuate.GatewayControllerEndpoint; import org.springframework.cloud.gateway.actuate.GatewayLegacyControllerEndpoint; import org.springframework.cloud.gateway.filter.AdaptCachedBodyGlobalFilter; import org.springframework.cloud.gateway.filter.ForwardPathFilter; import org.springframework.cloud.gateway.filter.ForwardRoutingFilter; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.cloud.gateway.filter.NettyRoutingFilter; import org.springframework.cloud.gateway.filter.NettyWriteResponseFilter; import org.springframework.cloud.gateway.filter.RemoveCachedBodyFilter; import org.springframework.cloud.gateway.filter.RouteToRequestUrlFilter; import org.springframework.cloud.gateway.filter.WebsocketRoutingFilter; import org.springframework.cloud.gateway.filter.WeightCalculatorWebFilter; import org.springframework.cloud.gateway.filter.factory.AddRequestHeaderGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.AddRequestParameterGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.AddResponseHeaderGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.DedupeResponseHeaderGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.FallbackHeadersGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.GatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.HystrixGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.MapRequestHeaderGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.PrefixPathGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.PreserveHostHeaderGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.RedirectToGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.RemoveRequestHeaderGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.RemoveRequestParameterGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.RemoveResponseHeaderGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.RequestHeaderSizeGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.RequestHeaderToRequestUriGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.RequestRateLimiterGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.RequestSizeGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.RetryGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.RewriteLocationResponseHeaderGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.RewritePathGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.RewriteResponseHeaderGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.SaveSessionGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.SecureHeadersGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.SecureHeadersProperties; import org.springframework.cloud.gateway.filter.factory.SetPathGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.SetRequestHeaderGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.SetResponseHeaderGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.SetStatusGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.StripPrefixGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.rewrite.ModifyRequestBodyGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.rewrite.ModifyResponseBodyGatewayFilterFactory; import org.springframework.cloud.gateway.filter.headers.ForwardedHeadersFilter; import org.springframework.cloud.gateway.filter.headers.HttpHeadersFilter; import org.springframework.cloud.gateway.filter.headers.RemoveHopByHopHeadersFilter; import org.springframework.cloud.gateway.filter.headers.XForwardedHeadersFilter; import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver; import org.springframework.cloud.gateway.filter.ratelimit.PrincipalNameKeyResolver; import org.springframework.cloud.gateway.filter.ratelimit.RateLimiter; import org.springframework.cloud.gateway.handler.FilteringWebHandler; import org.springframework.cloud.gateway.handler.RoutePredicateHandlerMapping; import org.springframework.cloud.gateway.handler.predicate.AfterRoutePredicateFactory; import org.springframework.cloud.gateway.handler.predicate.BeforeRoutePredicateFactory; import org.springframework.cloud.gateway.handler.predicate.BetweenRoutePredicateFactory; import org.springframework.cloud.gateway.handler.predicate.CloudFoundryRouteServiceRoutePredicateFactory; import org.springframework.cloud.gateway.handler.predicate.CookieRoutePredicateFactory; import org.springframework.cloud.gateway.handler.predicate.HeaderRoutePredicateFactory; import org.springframework.cloud.gateway.handler.predicate.HostRoutePredicateFactory; import org.springframework.cloud.gateway.handler.predicate.MethodRoutePredicateFactory; import org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory; import org.springframework.cloud.gateway.handler.predicate.QueryRoutePredicateFactory; import org.springframework.cloud.gateway.handler.predicate.ReadBodyPredicateFactory; import org.springframework.cloud.gateway.handler.predicate.RemoteAddrRoutePredicateFactory; import org.springframework.cloud.gateway.handler.predicate.RoutePredicateFactory; import org.springframework.cloud.gateway.handler.predicate.WeightRoutePredicateFactory; import org.springframework.cloud.gateway.route.CachingRouteLocator; import org.springframework.cloud.gateway.route.CompositeRouteDefinitionLocator; import org.springframework.cloud.gateway.route.CompositeRouteLocator; import org.springframework.cloud.gateway.route.InMemoryRouteDefinitionRepository; import org.springframework.cloud.gateway.route.RouteDefinitionLocator; import org.springframework.cloud.gateway.route.RouteDefinitionRepository; import org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator; import org.springframework.cloud.gateway.route.RouteDefinitionWriter; import org.springframework.cloud.gateway.route.RouteLocator; import org.springframework.cloud.gateway.route.RouteRefreshListener; import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; import org.springframework.cloud.gateway.support.ConfigurationService; import org.springframework.cloud.gateway.support.StringToZonedDateTimeConverter; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.DependsOn; import org.springframework.context.annotation.Primary; import org.springframework.core.convert.ConversionService; import org.springframework.core.env.Environment; import org.springframework.http.codec.ServerCodecConfigurer; import org.springframework.util.StringUtils; import org.springframework.validation.Validator; import org.springframework.web.reactive.DispatcherHandler; import org.springframework.web.reactive.socket.client.ReactorNettyWebSocketClient; import org.springframework.web.reactive.socket.client.WebSocketClient; import org.springframework.web.reactive.socket.server.WebSocketService; import org.springframework.web.reactive.socket.server.support.HandshakeWebSocketService; import static org.springframework.cloud.gateway.config.HttpClientProperties.Pool.PoolType.DISABLED; import static org.springframework.cloud.gateway.config.HttpClientProperties.Pool.PoolType.FIXED; /** * @author Spencer Gibb * @author Ziemowit Stolarczyk */ @Configuration(proxyBeanMethods = false) @ConditionalOnProperty(name = "spring.cloud.gateway.enabled", matchIfMissing = true) @EnableConfigurationProperties @AutoConfigureBefore({ HttpHandlerAutoConfiguration.class, WebFluxAutoConfiguration.class }) @AutoConfigureAfter({ GatewayLoadBalancerClientAutoConfiguration.class, GatewayClassPathWarningAutoConfiguration.class }) @ConditionalOnClass(DispatcherHandler.class) public class GatewayAutoConfiguration { @Bean public StringToZonedDateTimeConverter stringToZonedDateTimeConverter() { return new StringToZonedDateTimeConverter(); } @Bean public RouteLocatorBuilder routeLocatorBuilder( ConfigurableApplicationContext context) { return new RouteLocatorBuilder(context); } @Bean @ConditionalOnMissingBean public PropertiesRouteDefinitionLocator propertiesRouteDefinitionLocator( GatewayProperties properties) { return new PropertiesRouteDefinitionLocator(properties); } @Bean @ConditionalOnMissingBean(RouteDefinitionRepository.class) public InMemoryRouteDefinitionRepository inMemoryRouteDefinitionRepository() { return new InMemoryRouteDefinitionRepository(); } @Bean @Primary public RouteDefinitionLocator routeDefinitionLocator( List<RouteDefinitionLocator> routeDefinitionLocators) { return new CompositeRouteDefinitionLocator( Flux.fromIterable(routeDefinitionLocators)); } @Bean public ConfigurationService gatewayConfigurationService(BeanFactory beanFactory, @Qualifier("webFluxConversionService") ConversionService conversionService, Validator validator) { return new ConfigurationService(beanFactory, conversionService, validator); } @Bean public RouteLocator routeDefinitionRouteLocator(GatewayProperties properties, List<GatewayFilterFactory> gatewayFilters, List<RoutePredicateFactory> predicates, RouteDefinitionLocator routeDefinitionLocator, ConfigurationService configurationService) { return new RouteDefinitionRouteLocator(routeDefinitionLocator, predicates, gatewayFilters, properties, configurationService); } @Bean @Primary @ConditionalOnMissingBean(name = "cachedCompositeRouteLocator") // TODO: property to disable composite? public RouteLocator cachedCompositeRouteLocator(List<RouteLocator> routeLocators) { return new CachingRouteLocator( new CompositeRouteLocator(Flux.fromIterable(routeLocators))); } @Bean public RouteRefreshListener routeRefreshListener( ApplicationEventPublisher publisher) { return new RouteRefreshListener(publisher); } @Bean public FilteringWebHandler filteringWebHandler(List<GlobalFilter> globalFilters) { return new FilteringWebHandler(globalFilters); } @Bean public GlobalCorsProperties globalCorsProperties() { return new GlobalCorsProperties(); } @Bean public RoutePredicateHandlerMapping routePredicateHandlerMapping( FilteringWebHandler webHandler, RouteLocator routeLocator, GlobalCorsProperties globalCorsProperties, Environment environment) { return new RoutePredicateHandlerMapping(webHandler, routeLocator, globalCorsProperties, environment); } @Bean public GatewayProperties gatewayProperties() { return new GatewayProperties(); } // ConfigurationProperty beans @Bean public SecureHeadersProperties secureHeadersProperties() { return new SecureHeadersProperties(); } @Bean @ConditionalOnProperty(name = "spring.cloud.gateway.forwarded.enabled", matchIfMissing = true) public ForwardedHeadersFilter forwardedHeadersFilter() { return new ForwardedHeadersFilter(); } // HttpHeaderFilter beans @Bean public RemoveHopByHopHeadersFilter removeHopByHopHeadersFilter() { return new RemoveHopByHopHeadersFilter(); } @Bean @ConditionalOnProperty(name = "spring.cloud.gateway.x-forwarded.enabled", matchIfMissing = true) public XForwardedHeadersFilter xForwardedHeadersFilter() { return new XForwardedHeadersFilter(); } // GlobalFilter beans @Bean public AdaptCachedBodyGlobalFilter adaptCachedBodyGlobalFilter() { return new AdaptCachedBodyGlobalFilter(); } @Bean public RemoveCachedBodyFilter removeCachedBodyFilter() { return new RemoveCachedBodyFilter(); } @Bean public RouteToRequestUrlFilter routeToRequestUrlFilter() { return new RouteToRequestUrlFilter(); } @Bean public ForwardRoutingFilter forwardRoutingFilter( ObjectProvider<DispatcherHandler> dispatcherHandler) { return new ForwardRoutingFilter(dispatcherHandler); } @Bean public ForwardPathFilter forwardPathFilter() { return new ForwardPathFilter(); } @Bean public WebSocketService webSocketService() { return new HandshakeWebSocketService(); } @Bean public WebsocketRoutingFilter websocketRoutingFilter(WebSocketClient webSocketClient, WebSocketService webSocketService, ObjectProvider<List<HttpHeadersFilter>> headersFilters) { return new WebsocketRoutingFilter(webSocketClient, webSocketService, headersFilters); } @Bean public WeightCalculatorWebFilter weightCalculatorWebFilter( ConfigurationService configurationService, ObjectProvider<RouteLocator> routeLocator) { return new WeightCalculatorWebFilter(routeLocator, configurationService); } @Bean public AfterRoutePredicateFactory afterRoutePredicateFactory() { return new AfterRoutePredicateFactory(); } /* * @Bean //TODO: default over netty? configurable public WebClientHttpRoutingFilter * webClientHttpRoutingFilter() { //TODO: WebClient bean return new * WebClientHttpRoutingFilter(WebClient.routes().build()); } * * @Bean public WebClientWriteResponseFilter webClientWriteResponseFilter() { return * new WebClientWriteResponseFilter(); } */ // Predicate Factory beans @Bean public BeforeRoutePredicateFactory beforeRoutePredicateFactory() { return new BeforeRoutePredicateFactory(); } @Bean public BetweenRoutePredicateFactory betweenRoutePredicateFactory() { return new BetweenRoutePredicateFactory(); } @Bean public CookieRoutePredicateFactory cookieRoutePredicateFactory() { return new CookieRoutePredicateFactory(); } @Bean public HeaderRoutePredicateFactory headerRoutePredicateFactory() { return new HeaderRoutePredicateFactory(); } @Bean public HostRoutePredicateFactory hostRoutePredicateFactory() { return new HostRoutePredicateFactory(); } @Bean public MethodRoutePredicateFactory methodRoutePredicateFactory() { return new MethodRoutePredicateFactory(); } @Bean public PathRoutePredicateFactory pathRoutePredicateFactory() { return new PathRoutePredicateFactory(); } @Bean public QueryRoutePredicateFactory queryRoutePredicateFactory() { return new QueryRoutePredicateFactory(); } @Bean public ReadBodyPredicateFactory readBodyPredicateFactory() { return new ReadBodyPredicateFactory(); } @Bean public RemoteAddrRoutePredicateFactory remoteAddrRoutePredicateFactory() { return new RemoteAddrRoutePredicateFactory(); } @Bean @DependsOn("weightCalculatorWebFilter") public WeightRoutePredicateFactory weightRoutePredicateFactory() { return new WeightRoutePredicateFactory(); } @Bean public CloudFoundryRouteServiceRoutePredicateFactory cloudFoundryRouteServiceRoutePredicateFactory() { return new CloudFoundryRouteServiceRoutePredicateFactory(); } // GatewayFilter Factory beans @Bean public AddRequestHeaderGatewayFilterFactory addRequestHeaderGatewayFilterFactory() { return new AddRequestHeaderGatewayFilterFactory(); } @Bean public MapRequestHeaderGatewayFilterFactory mapRequestHeaderGatewayFilterFactory() { return new MapRequestHeaderGatewayFilterFactory(); } @Bean public AddRequestParameterGatewayFilterFactory addRequestParameterGatewayFilterFactory() { return new AddRequestParameterGatewayFilterFactory(); } @Bean public AddResponseHeaderGatewayFilterFactory addResponseHeaderGatewayFilterFactory() { return new AddResponseHeaderGatewayFilterFactory(); } @Bean public ModifyRequestBodyGatewayFilterFactory modifyRequestBodyGatewayFilterFactory() { return new ModifyRequestBodyGatewayFilterFactory(); } @Bean public DedupeResponseHeaderGatewayFilterFactory dedupeResponseHeaderGatewayFilterFactory() { return new DedupeResponseHeaderGatewayFilterFactory(); } @Bean public ModifyResponseBodyGatewayFilterFactory modifyResponseBodyGatewayFilterFactory( ServerCodecConfigurer codecConfigurer) { return new ModifyResponseBodyGatewayFilterFactory(codecConfigurer); } @Bean public PrefixPathGatewayFilterFactory prefixPathGatewayFilterFactory() { return new PrefixPathGatewayFilterFactory(); } @Bean public PreserveHostHeaderGatewayFilterFactory preserveHostHeaderGatewayFilterFactory() { return new PreserveHostHeaderGatewayFilterFactory(); } @Bean public RedirectToGatewayFilterFactory redirectToGatewayFilterFactory() { return new RedirectToGatewayFilterFactory(); } @Bean public RemoveRequestHeaderGatewayFilterFactory removeRequestHeaderGatewayFilterFactory() { return new RemoveRequestHeaderGatewayFilterFactory(); } @Bean public RemoveRequestParameterGatewayFilterFactory removeRequestParameterGatewayFilterFactory() { return new RemoveRequestParameterGatewayFilterFactory(); } @Bean public RemoveResponseHeaderGatewayFilterFactory removeResponseHeaderGatewayFilterFactory() { return new RemoveResponseHeaderGatewayFilterFactory(); } @Bean(name = PrincipalNameKeyResolver.BEAN_NAME) @ConditionalOnBean(RateLimiter.class) @ConditionalOnMissingBean(KeyResolver.class) public PrincipalNameKeyResolver principalNameKeyResolver() { return new PrincipalNameKeyResolver(); } @Bean @ConditionalOnBean({ RateLimiter.class, KeyResolver.class }) public RequestRateLimiterGatewayFilterFactory requestRateLimiterGatewayFilterFactory( RateLimiter rateLimiter, KeyResolver resolver) { return new RequestRateLimiterGatewayFilterFactory(rateLimiter, resolver); } @Bean public RewritePathGatewayFilterFactory rewritePathGatewayFilterFactory() { return new RewritePathGatewayFilterFactory(); } @Bean public RetryGatewayFilterFactory retryGatewayFilterFactory() { return new RetryGatewayFilterFactory(); } @Bean public SetPathGatewayFilterFactory setPathGatewayFilterFactory() { return new SetPathGatewayFilterFactory(); } @Bean public SecureHeadersGatewayFilterFactory secureHeadersGatewayFilterFactory( SecureHeadersProperties properties) { return new SecureHeadersGatewayFilterFactory(properties); } @Bean public SetRequestHeaderGatewayFilterFactory setRequestHeaderGatewayFilterFactory() { return new SetRequestHeaderGatewayFilterFactory(); } @Bean public SetResponseHeaderGatewayFilterFactory setResponseHeaderGatewayFilterFactory() { return new SetResponseHeaderGatewayFilterFactory(); } @Bean public RewriteResponseHeaderGatewayFilterFactory rewriteResponseHeaderGatewayFilterFactory() { return new RewriteResponseHeaderGatewayFilterFactory(); } @Bean public RewriteLocationResponseHeaderGatewayFilterFactory rewriteLocationResponseHeaderGatewayFilterFactory() { return new RewriteLocationResponseHeaderGatewayFilterFactory(); } @Bean public SetStatusGatewayFilterFactory setStatusGatewayFilterFactory() { return new SetStatusGatewayFilterFactory(); } @Bean public SaveSessionGatewayFilterFactory saveSessionGatewayFilterFactory() { return new SaveSessionGatewayFilterFactory(); } @Bean public StripPrefixGatewayFilterFactory stripPrefixGatewayFilterFactory() { return new StripPrefixGatewayFilterFactory(); } @Bean public RequestHeaderToRequestUriGatewayFilterFactory requestHeaderToRequestUriGatewayFilterFactory() { return new RequestHeaderToRequestUriGatewayFilterFactory(); } @Bean public RequestSizeGatewayFilterFactory requestSizeGatewayFilterFactory() { return new RequestSizeGatewayFilterFactory(); } @Bean public RequestHeaderSizeGatewayFilterFactory requestHeaderSizeGatewayFilterFactory() { return new RequestHeaderSizeGatewayFilterFactory(); } @Configuration(proxyBeanMethods = false) @ConditionalOnClass(HttpClient.class) protected static class NettyConfiguration { protected final Log logger = LogFactory.getLog(getClass()); @Bean @ConditionalOnProperty(name = "spring.cloud.gateway.httpserver.wiretap") public NettyWebServerFactoryCustomizer nettyServerWiretapCustomizer( Environment environment, ServerProperties serverProperties) { return new NettyWebServerFactoryCustomizer(environment, serverProperties) { @Override public void customize(NettyReactiveWebServerFactory factory) { factory.addServerCustomizers(httpServer -> httpServer.wiretap(true)); super.customize(factory); } }; } @Bean @ConditionalOnMissingBean public HttpClient gatewayHttpClient(HttpClientProperties properties) { // configure pool resources HttpClientProperties.Pool pool = properties.getPool(); ConnectionProvider connectionProvider; if (pool.getType() == DISABLED) { connectionProvider = ConnectionProvider.newConnection(); } else if (pool.getType() == FIXED) { connectionProvider = ConnectionProvider.fixed(pool.getName(), pool.getMaxConnections(), pool.getAcquireTimeout(), pool.getMaxIdleTime()); } else { connectionProvider = ConnectionProvider.elastic(pool.getName(), pool.getMaxIdleTime()); } HttpClient httpClient = HttpClient.create(connectionProvider) .tcpConfiguration(tcpClient -> { if (properties.getConnectTimeout() != null) { tcpClient = tcpClient.option( ChannelOption.CONNECT_TIMEOUT_MILLIS, properties.getConnectTimeout()); } // configure proxy if proxy host is set. HttpClientProperties.Proxy proxy = properties.getProxy(); if (StringUtils.hasText(proxy.getHost())) { tcpClient = tcpClient.proxy(proxySpec -> { ProxyProvider.Builder builder = proxySpec .type(ProxyProvider.Proxy.HTTP) .host(proxy.getHost()); PropertyMapper map = PropertyMapper.get(); map.from(proxy::getPort).whenNonNull().to(builder::port); map.from(proxy::getUsername).whenHasText() .to(builder::username); map.from(proxy::getPassword).whenHasText() .to(password -> builder.password(s -> password)); map.from(proxy::getNonProxyHostsPattern).whenHasText() .to(builder::nonProxyHosts); }); } return tcpClient; }); HttpClientProperties.Ssl ssl = properties.getSsl(); if ((ssl.getKeyStore() != null && ssl.getKeyStore().length() > 0) || ssl.getTrustedX509CertificatesForTrustManager().length > 0 || ssl.isUseInsecureTrustManager()) { httpClient = httpClient.secure(sslContextSpec -> { // configure ssl SslContextBuilder sslContextBuilder = SslContextBuilder.forClient(); X509Certificate[] trustedX509Certificates = ssl .getTrustedX509CertificatesForTrustManager(); if (trustedX509Certificates.length > 0) { sslContextBuilder = sslContextBuilder .trustManager(trustedX509Certificates); } else if (ssl.isUseInsecureTrustManager()) { sslContextBuilder = sslContextBuilder .trustManager(InsecureTrustManagerFactory.INSTANCE); } try { sslContextBuilder = sslContextBuilder .keyManager(ssl.getKeyManagerFactory()); } catch (Exception e) { logger.error(e); } sslContextSpec.sslContext(sslContextBuilder) .defaultConfiguration(ssl.getDefaultConfigurationType()) .handshakeTimeout(ssl.getHandshakeTimeout()) .closeNotifyFlushTimeout(ssl.getCloseNotifyFlushTimeout()) .closeNotifyReadTimeout(ssl.getCloseNotifyReadTimeout()); }); } if (properties.isWiretap()) { httpClient = httpClient.wiretap(true); } return httpClient; } @Bean public HttpClientProperties httpClientProperties() { return new HttpClientProperties(); } @Bean public NettyRoutingFilter routingFilter(HttpClient httpClient, ObjectProvider<List<HttpHeadersFilter>> headersFilters, HttpClientProperties properties) { return new NettyRoutingFilter(httpClient, headersFilters, properties); } @Bean public NettyWriteResponseFilter nettyWriteResponseFilter( GatewayProperties properties) { return new NettyWriteResponseFilter(properties.getStreamingMediaTypes()); } @Bean public ReactorNettyWebSocketClient reactorNettyWebSocketClient( HttpClientProperties properties, HttpClient httpClient) { ReactorNettyWebSocketClient webSocketClient = new ReactorNettyWebSocketClient( httpClient); if (properties.getWebsocket().getMaxFramePayloadLength() != null) { webSocketClient.setMaxFramePayloadLength( properties.getWebsocket().getMaxFramePayloadLength()); } return webSocketClient; } } @Configuration(proxyBeanMethods = false) @ConditionalOnClass({ HystrixObservableCommand.class, RxReactiveStreams.class }) protected static class HystrixConfiguration { @Bean public HystrixGatewayFilterFactory hystrixGatewayFilterFactory( ObjectProvider<DispatcherHandler> dispatcherHandler) { return new HystrixGatewayFilterFactory(dispatcherHandler); } @Bean @ConditionalOnMissingBean(FallbackHeadersGatewayFilterFactory.class) public FallbackHeadersGatewayFilterFactory fallbackHeadersGatewayFilterFactory() { return new FallbackHeadersGatewayFilterFactory(); } } @Configuration(proxyBeanMethods = false) @ConditionalOnClass(Health.class) protected static class GatewayActuatorConfiguration { @Bean @ConditionalOnProperty(name = "spring.cloud.gateway.actuator.verbose.enabled", matchIfMissing = true) @ConditionalOnAvailableEndpoint public GatewayControllerEndpoint gatewayControllerEndpoint( List<GlobalFilter> globalFilters, List<GatewayFilterFactory> gatewayFilters, List<RoutePredicateFactory> routePredicates, RouteDefinitionWriter routeDefinitionWriter, RouteLocator routeLocator) { return new GatewayControllerEndpoint(globalFilters, gatewayFilters, routePredicates, routeDefinitionWriter, routeLocator); } @Bean @Conditional(OnVerboseDisabledCondition.class) @ConditionalOnAvailableEndpoint public GatewayLegacyControllerEndpoint gatewayLegacyControllerEndpoint( RouteDefinitionLocator routeDefinitionLocator, List<GlobalFilter> globalFilters, List<GatewayFilterFactory> gatewayFilters, List<RoutePredicateFactory> routePredicates, RouteDefinitionWriter routeDefinitionWriter, RouteLocator routeLocator) { return new GatewayLegacyControllerEndpoint(routeDefinitionLocator, globalFilters, gatewayFilters, routePredicates, routeDefinitionWriter, routeLocator); } } private static class OnVerboseDisabledCondition extends NoneNestedConditions { OnVerboseDisabledCondition() { super(ConfigurationPhase.REGISTER_BEAN); } @ConditionalOnProperty(name = "spring.cloud.gateway.actuator.verbose.enabled", matchIfMissing = true) static class VerboseDisabled { } } }
也可以看出在構造 FilteringWebHandler 的時候,依賴Spring 自動注入globalFilters,然后org.springframework.cloud.gateway.handler.FilteringWebHandler#FilteringWebHandler 構造方法中,將filters 進行排序維護到內部:
public FilteringWebHandler(List<GlobalFilter> globalFilters) { this.globalFilters = loadFilters(globalFilters); } private static List<GatewayFilter> loadFilters(List<GlobalFilter> filters) { return filters.stream().map(filter -> { GatewayFilterAdapter gatewayFilter = new GatewayFilterAdapter(filter); if (filter instanceof Ordered) { int order = ((Ordered) filter).getOrder(); return new OrderedGatewayFilter(gatewayFilter, order); } return gatewayFilter; }).collect(Collectors.toList()); }
org.springframework.cloud.gateway.config.GatewayProperties 配置類源碼如下:(yml 配置的 routes 直接注入到該對象的list 屬性中, 這里可以看出也可以配置默認的過濾器, 相當於全局過濾器, 會應用到每個路由中)

/* * Copyright 2013-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.cloud.gateway.config; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import javax.validation.Valid; import javax.validation.constraints.NotNull; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.cloud.gateway.filter.FilterDefinition; import org.springframework.cloud.gateway.route.RouteDefinition; import org.springframework.http.MediaType; import org.springframework.validation.annotation.Validated; /** * @author Spencer Gibb */ @ConfigurationProperties("spring.cloud.gateway") @Validated public class GatewayProperties { private final Log logger = LogFactory.getLog(getClass()); /** * List of Routes. */ @NotNull @Valid private List<RouteDefinition> routes = new ArrayList<>(); /** * List of filter definitions that are applied to every route. */ private List<FilterDefinition> defaultFilters = new ArrayList<>(); private List<MediaType> streamingMediaTypes = Arrays .asList(MediaType.TEXT_EVENT_STREAM, MediaType.APPLICATION_STREAM_JSON); public List<RouteDefinition> getRoutes() { return routes; } public void setRoutes(List<RouteDefinition> routes) { this.routes = routes; if (routes != null && routes.size() > 0 && logger.isDebugEnabled()) { logger.debug("Routes supplied from Gateway Properties: " + routes); } } public List<FilterDefinition> getDefaultFilters() { return defaultFilters; } public void setDefaultFilters(List<FilterDefinition> defaultFilters) { this.defaultFilters = defaultFilters; } public List<MediaType> getStreamingMediaTypes() { return streamingMediaTypes; } public void setStreamingMediaTypes(List<MediaType> streamingMediaTypes) { this.streamingMediaTypes = streamingMediaTypes; } @Override public String toString() { return "GatewayProperties{" + "routes=" + routes + ", defaultFilters=" + defaultFilters + ", streamingMediaTypes=" + streamingMediaTypes + '}'; } }
可以看到自動配置累注入了幾個重要的對象:
1》 org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder 路由坐標構造器,用於業務直接使用, 比如上面代碼配置中方法上自動注入的對象就是這里的Builder
2》 PropertiesRouteDefinitionLocator、InMemoryRouteDefinitionRepository 是兩個RouteDefinitionLocator 對象, 最后再作為組合對象注入到CompositeRouteDefinitionLocator 內部。
3》 一些RoutePredicateFactory 斷言工廠, 和 一些 GatewayFilterFactory 工廠
4》 一些內置的GateWayFilter, 在處理請求轉發過程中也是這些過濾器在發揮重要作用
5》 routeDefinitionRouteLocator 對象,這個負責解析 properties 文件配置的routes 對象; 然后和之前代碼注入的兩個RouteLocator, 一起作為一個集合返回一個CachingRouteLocator 對象,如下方法:
@Bean @Primary @ConditionalOnMissingBean(name = "cachedCompositeRouteLocator") // TODO: property to disable composite? public RouteLocator cachedCompositeRouteLocator(List<RouteLocator> routeLocators) { return new CachingRouteLocator( new CompositeRouteLocator(Flux.fromIterable(routeLocators))); }
到這個方法傳遞的參數如下:
這里最后會解析所有的RoteDefinition 對象,解析為Route 對象, 並且維護到: org.springframework.cloud.gateway.route.CachingRouteLocator#routes 屬性中。
解析鏈如下: 核心入庫是org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator

/* * Copyright 2013-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.cloud.gateway.route; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import reactor.core.publisher.Flux; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.gateway.config.GatewayProperties; import org.springframework.cloud.gateway.event.FilterArgsEvent; import org.springframework.cloud.gateway.event.PredicateArgsEvent; import org.springframework.cloud.gateway.filter.FilterDefinition; import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.cloud.gateway.filter.OrderedGatewayFilter; import org.springframework.cloud.gateway.filter.factory.GatewayFilterFactory; import org.springframework.cloud.gateway.handler.AsyncPredicate; import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition; import org.springframework.cloud.gateway.handler.predicate.RoutePredicateFactory; import org.springframework.cloud.gateway.support.ConfigurationService; import org.springframework.cloud.gateway.support.HasRouteId; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisherAware; import org.springframework.core.Ordered; import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.core.convert.ConversionService; import org.springframework.validation.Validator; import org.springframework.web.server.ServerWebExchange; /** * {@link RouteLocator} that loads routes from a {@link RouteDefinitionLocator}. * * @author Spencer Gibb */ public class RouteDefinitionRouteLocator implements RouteLocator, BeanFactoryAware, ApplicationEventPublisherAware { /** * Default filters name. */ public static final String DEFAULT_FILTERS = "defaultFilters"; protected final Log logger = LogFactory.getLog(getClass()); private final RouteDefinitionLocator routeDefinitionLocator; private final ConfigurationService configurationService; private final Map<String, RoutePredicateFactory> predicates = new LinkedHashMap<>(); private final Map<String, GatewayFilterFactory> gatewayFilterFactories = new HashMap<>(); private final GatewayProperties gatewayProperties; @Deprecated public RouteDefinitionRouteLocator(RouteDefinitionLocator routeDefinitionLocator, List<RoutePredicateFactory> predicates, List<GatewayFilterFactory> gatewayFilterFactories, GatewayProperties gatewayProperties, ConversionService conversionService) { this.routeDefinitionLocator = routeDefinitionLocator; this.configurationService = new ConfigurationService(); this.configurationService.setConversionService(conversionService); initFactories(predicates); gatewayFilterFactories.forEach( factory -> this.gatewayFilterFactories.put(factory.name(), factory)); this.gatewayProperties = gatewayProperties; } public RouteDefinitionRouteLocator(RouteDefinitionLocator routeDefinitionLocator, List<RoutePredicateFactory> predicates, List<GatewayFilterFactory> gatewayFilterFactories, GatewayProperties gatewayProperties, ConfigurationService configurationService) { this.routeDefinitionLocator = routeDefinitionLocator; this.configurationService = configurationService; initFactories(predicates); gatewayFilterFactories.forEach( factory -> this.gatewayFilterFactories.put(factory.name(), factory)); this.gatewayProperties = gatewayProperties; } @Override @Deprecated public void setBeanFactory(BeanFactory beanFactory) throws BeansException { if (this.configurationService.getBeanFactory() == null) { this.configurationService.setBeanFactory(beanFactory); } } @Autowired @Deprecated public void setValidator(Validator validator) { if (this.configurationService.getValidator() == null) { this.configurationService.setValidator(validator); } } @Override @Deprecated public void setApplicationEventPublisher(ApplicationEventPublisher publisher) { if (this.configurationService.getPublisher() == null) { this.configurationService.setApplicationEventPublisher(publisher); } } private void initFactories(List<RoutePredicateFactory> predicates) { predicates.forEach(factory -> { String key = factory.name(); if (this.predicates.containsKey(key)) { this.logger.warn("A RoutePredicateFactory named " + key + " already exists, class: " + this.predicates.get(key) + ". It will be overwritten."); } this.predicates.put(key, factory); if (logger.isInfoEnabled()) { logger.info("Loaded RoutePredicateFactory [" + key + "]"); } }); } @Override public Flux<Route> getRoutes() { return this.routeDefinitionLocator.getRouteDefinitions().map(this::convertToRoute) // TODO: error handling .map(route -> { if (logger.isDebugEnabled()) { logger.debug("RouteDefinition matched: " + route.getId()); } return route; }); /* * TODO: trace logging if (logger.isTraceEnabled()) { * logger.trace("RouteDefinition did not match: " + routeDefinition.getId()); } */ } private Route convertToRoute(RouteDefinition routeDefinition) { AsyncPredicate<ServerWebExchange> predicate = combinePredicates(routeDefinition); List<GatewayFilter> gatewayFilters = getFilters(routeDefinition); return Route.async(routeDefinition).asyncPredicate(predicate) .replaceFilters(gatewayFilters).build(); } @SuppressWarnings("unchecked") List<GatewayFilter> loadGatewayFilters(String id, List<FilterDefinition> filterDefinitions) { ArrayList<GatewayFilter> ordered = new ArrayList<>(filterDefinitions.size()); for (int i = 0; i < filterDefinitions.size(); i++) { FilterDefinition definition = filterDefinitions.get(i); GatewayFilterFactory factory = this.gatewayFilterFactories .get(definition.getName()); if (factory == null) { throw new IllegalArgumentException( "Unable to find GatewayFilterFactory with name " + definition.getName()); } if (logger.isDebugEnabled()) { logger.debug("RouteDefinition " + id + " applying filter " + definition.getArgs() + " to " + definition.getName()); } // @formatter:off Object configuration = this.configurationService.with(factory) .name(definition.getName()) .properties(definition.getArgs()) .eventFunction((bound, properties) -> new FilterArgsEvent( // TODO: why explicit cast needed or java compile fails RouteDefinitionRouteLocator.this, id, (Map<String, Object>) properties)) .bind(); // @formatter:on // some filters require routeId // TODO: is there a better place to apply this? if (configuration instanceof HasRouteId) { HasRouteId hasRouteId = (HasRouteId) configuration; hasRouteId.setRouteId(id); } GatewayFilter gatewayFilter = factory.apply(configuration); if (gatewayFilter instanceof Ordered) { ordered.add(gatewayFilter); } else { ordered.add(new OrderedGatewayFilter(gatewayFilter, i + 1)); } } return ordered; } private List<GatewayFilter> getFilters(RouteDefinition routeDefinition) { List<GatewayFilter> filters = new ArrayList<>(); // TODO: support option to apply defaults after route specific filters? if (!this.gatewayProperties.getDefaultFilters().isEmpty()) { filters.addAll(loadGatewayFilters(DEFAULT_FILTERS, this.gatewayProperties.getDefaultFilters())); } if (!routeDefinition.getFilters().isEmpty()) { filters.addAll(loadGatewayFilters(routeDefinition.getId(), routeDefinition.getFilters())); } AnnotationAwareOrderComparator.sort(filters); return filters; } private AsyncPredicate<ServerWebExchange> combinePredicates( RouteDefinition routeDefinition) { List<PredicateDefinition> predicates = routeDefinition.getPredicates(); AsyncPredicate<ServerWebExchange> predicate = lookup(routeDefinition, predicates.get(0)); for (PredicateDefinition andPredicate : predicates.subList(1, predicates.size())) { AsyncPredicate<ServerWebExchange> found = lookup(routeDefinition, andPredicate); predicate = predicate.and(found); } return predicate; } @SuppressWarnings("unchecked") private AsyncPredicate<ServerWebExchange> lookup(RouteDefinition route, PredicateDefinition predicate) { RoutePredicateFactory<Object> factory = this.predicates.get(predicate.getName()); if (factory == null) { throw new IllegalArgumentException( "Unable to find RoutePredicateFactory with name " + predicate.getName()); } if (logger.isDebugEnabled()) { logger.debug("RouteDefinition " + route.getId() + " applying " + predicate.getArgs() + " to " + predicate.getName()); } // @formatter:off Object config = this.configurationService.with(factory) .name(predicate.getName()) .properties(predicate.getArgs()) .eventFunction((bound, properties) -> new PredicateArgsEvent( RouteDefinitionRouteLocator.this, route.getId(), properties)) .bind(); // @formatter:on return factory.applyAsync(config); } }
可以看到這個對象內部就是根據predicate.getName(), 然后從spring 容器獲取對應的斷言工廠,然后構造相對應的斷言。解析Filter 也是同樣的原理。 內置的一些斷言工廠和過濾器工廠如下:
斷言工廠predicates:
過濾器工廠gatewayFilterFactories:(30個)
result = {HashMap@8599} size = 30 "SetPath" -> {SetPathGatewayFilterFactory@8711} "[SetPathGatewayFilterFactory@1805782b configClass = SetPathGatewayFilterFactory.Config]" "RequestHeaderToRequestUri" -> {RequestHeaderToRequestUriGatewayFilterFactory@8713} "[RequestHeaderToRequestUriGatewayFilterFactory@68bd197d configClass = AbstractGatewayFilterFactory.NameConfig]" "RequestHeaderSize" -> {RequestHeaderSizeGatewayFilterFactory@8715} "[RequestHeaderSizeGatewayFilterFactory@17e6209b configClass = RequestHeaderSizeGatewayFilterFactory.Config]" "CircuitBreaker" -> {SpringCloudCircuitBreakerHystrixFilterFactory@8717} "[SpringCloudCircuitBreakerHystrixFilterFactory@795cab8d configClass = SpringCloudCircuitBreakerFilterFactory.Config]" "RemoveRequestHeader" -> {RemoveRequestHeaderGatewayFilterFactory@8719} "[RemoveRequestHeaderGatewayFilterFactory@7f85ae76 configClass = AbstractGatewayFilterFactory.NameConfig]" "RemoveRequestParameter" -> {RemoveRequestParameterGatewayFilterFactory@8721} "[RemoveRequestParameterGatewayFilterFactory@13229d28 configClass = AbstractGatewayFilterFactory.NameConfig]" "ModifyRequestBody" -> {ModifyRequestBodyGatewayFilterFactory@8723} "[ModifyRequestBodyGatewayFilterFactory@75755b31 configClass = ModifyRequestBodyGatewayFilterFactory.Config]" "AddRequestParameter" -> {AddRequestParameterGatewayFilterFactory@8725} "[AddRequestParameterGatewayFilterFactory@5fed4a4e configClass = AbstractNameValueGatewayFilterFactory.NameValueConfig]" "RewriteLocationResponseHeader" -> {RewriteLocationResponseHeaderGatewayFilterFactory@8727} "[RewriteLocationResponseHeaderGatewayFilterFactory@62256510 configClass = RewriteLocationResponseHeaderGatewayFilterFactory.Config]" "MapRequestHeader" -> {MapRequestHeaderGatewayFilterFactory@8729} "[MapRequestHeaderGatewayFilterFactory@57843cd8 configClass = MapRequestHeaderGatewayFilterFactory.Config]" "DedupeResponseHeader" -> {DedupeResponseHeaderGatewayFilterFactory@8731} "[DedupeResponseHeaderGatewayFilterFactory@3e1cbbb configClass = DedupeResponseHeaderGatewayFilterFactory.Config]" "RequestRateLimiter" -> {RequestRateLimiterGatewayFilterFactory@8733} "[RequestRateLimiterGatewayFilterFactory@13f06661 configClass = RequestRateLimiterGatewayFilterFactory.Config]" "PreserveHostHeader" -> {PreserveHostHeaderGatewayFilterFactory@8735} "[PreserveHostHeaderGatewayFilterFactory@1ec6c80d configClass = Object]" "RewritePath" -> {RewritePathGatewayFilterFactory@8737} "[RewritePathGatewayFilterFactory@41549c77 configClass = RewritePathGatewayFilterFactory.Config]" "SetStatus" -> {SetStatusGatewayFilterFactory@8739} "[SetStatusGatewayFilterFactory@2d4d6215 configClass = SetStatusGatewayFilterFactory.Config]" "SetRequestHeader" -> {SetRequestHeaderGatewayFilterFactory@8741} "[SetRequestHeaderGatewayFilterFactory@6d04446d configClass = AbstractNameValueGatewayFilterFactory.NameValueConfig]" "PrefixPath" -> {PrefixPathGatewayFilterFactory@8743} "[PrefixPathGatewayFilterFactory@41b5bfd9 configClass = PrefixPathGatewayFilterFactory.Config]" "SaveSession" -> {SaveSessionGatewayFilterFactory@8745} "[SaveSessionGatewayFilterFactory@1a4477a5 configClass = Object]" "StripPrefix" -> {StripPrefixGatewayFilterFactory@8747} "[StripPrefixGatewayFilterFactory@21f9e5b7 configClass = StripPrefixGatewayFilterFactory.Config]" "ModifyResponseBody" -> {ModifyResponseBodyGatewayFilterFactory@8749} "[ModifyResponseBodyGatewayFilterFactory@36eb5eb3 configClass = ModifyResponseBodyGatewayFilterFactory.Config]" "RequestSize" -> {RequestSizeGatewayFilterFactory@8751} "[RequestSizeGatewayFilterFactory@2ea693b5 configClass = RequestSizeGatewayFilterFactory.RequestSizeConfig]" "RedirectTo" -> {RedirectToGatewayFilterFactory@8753} "[RedirectToGatewayFilterFactory@13f7747d configClass = RedirectToGatewayFilterFactory.Config]" "SetResponseHeader" -> {SetResponseHeaderGatewayFilterFactory@8755} "[SetResponseHeaderGatewayFilterFactory@6f0b16b7 configClass = AbstractNameValueGatewayFilterFactory.NameValueConfig]" "SecureHeaders" -> {SecureHeadersGatewayFilterFactory@8757} "[SecureHeadersGatewayFilterFactory@8d6d624 configClass = Object]" "AddResponseHeader" -> {AddResponseHeaderGatewayFilterFactory@8759} "[AddResponseHeaderGatewayFilterFactory@271a0679 configClass = AbstractNameValueGatewayFilterFactory.NameValueConfig]" "FallbackHeaders" -> {FallbackHeadersGatewayFilterFactory@8761} "[FallbackHeadersGatewayFilterFactory@792a232a configClass = FallbackHeadersGatewayFilterFactory.Config]" "Retry" -> {RetryGatewayFilterFactory@8763} "[RetryGatewayFilterFactory@461892a8 configClass = RetryGatewayFilterFactory.RetryConfig]" "AddRequestHeader" -> {AddRequestHeaderGatewayFilterFactory@8765} "[AddRequestHeaderGatewayFilterFactory@2adf0c5f configClass = AbstractNameValueGatewayFilterFactory.NameValueConfig]" "RemoveResponseHeader" -> {RemoveResponseHeaderGatewayFilterFactory@8767} "[RemoveResponseHeaderGatewayFilterFactory@3b0ca9e1 configClass = AbstractGatewayFilterFactory.NameConfig]" "RewriteResponseHeader" -> {RewriteResponseHeaderGatewayFilterFactory@8769} "[RewriteResponseHeaderGatewayFilterFactory@5cfcef5d configClass = RewriteResponseHeaderGatewayFilterFactory.Config]"
org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory 路徑斷言工廠如下:(可以看到核心是返回一個GatewayPredicate 斷言對象,內部的test 方法會發揮重要作用)

/* * Copyright 2013-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.cloud.gateway.handler.predicate; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Optional; import java.util.function.Predicate; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.core.style.ToStringCreator; import org.springframework.http.server.PathContainer; import org.springframework.util.CollectionUtils; import org.springframework.validation.annotation.Validated; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.util.pattern.PathPattern; import org.springframework.web.util.pattern.PathPattern.PathMatchInfo; import org.springframework.web.util.pattern.PathPatternParser; import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.putUriTemplateVariables; import static org.springframework.http.server.PathContainer.parsePath; /** * @author Spencer Gibb */ public class PathRoutePredicateFactory extends AbstractRoutePredicateFactory<PathRoutePredicateFactory.Config> { private static final Log log = LogFactory.getLog(RoutePredicateFactory.class); private static final String MATCH_OPTIONAL_TRAILING_SEPARATOR_KEY = "matchOptionalTrailingSeparator"; private PathPatternParser pathPatternParser = new PathPatternParser(); public PathRoutePredicateFactory() { super(Config.class); } private static void traceMatch(String prefix, Object desired, Object actual, boolean match) { if (log.isTraceEnabled()) { String message = String.format("%s \"%s\" %s against value \"%s\"", prefix, desired, match ? "matches" : "does not match", actual); log.trace(message); } } public void setPathPatternParser(PathPatternParser pathPatternParser) { this.pathPatternParser = pathPatternParser; } @Override public List<String> shortcutFieldOrder() { return Arrays.asList("patterns", MATCH_OPTIONAL_TRAILING_SEPARATOR_KEY); } @Override public ShortcutType shortcutType() { return ShortcutType.GATHER_LIST_TAIL_FLAG; } @Override public Predicate<ServerWebExchange> apply(Config config) { final ArrayList<PathPattern> pathPatterns = new ArrayList<>(); synchronized (this.pathPatternParser) { pathPatternParser.setMatchOptionalTrailingSeparator( config.isMatchOptionalTrailingSeparator()); config.getPatterns().forEach(pattern -> { PathPattern pathPattern = this.pathPatternParser.parse(pattern); pathPatterns.add(pathPattern); }); } return new GatewayPredicate() { @Override public boolean test(ServerWebExchange exchange) { PathContainer path = parsePath( exchange.getRequest().getURI().getRawPath()); Optional<PathPattern> optionalPathPattern = pathPatterns.stream() .filter(pattern -> pattern.matches(path)).findFirst(); if (optionalPathPattern.isPresent()) { PathPattern pathPattern = optionalPathPattern.get(); traceMatch("Pattern", pathPattern.getPatternString(), path, true); PathMatchInfo pathMatchInfo = pathPattern.matchAndExtract(path); putUriTemplateVariables(exchange, pathMatchInfo.getUriVariables()); return true; } else { traceMatch("Pattern", config.getPatterns(), path, false); return false; } } @Override public String toString() { return String.format("Paths: %s, match trailing slash: %b", config.getPatterns(), config.isMatchOptionalTrailingSeparator()); } }; } @Validated public static class Config { private List<String> patterns = new ArrayList<>(); private boolean matchOptionalTrailingSeparator = true; @Deprecated public String getPattern() { if (!CollectionUtils.isEmpty(this.patterns)) { return patterns.get(0); } return null; } @Deprecated public Config setPattern(String pattern) { this.patterns = new ArrayList<>(); this.patterns.add(pattern); return this; } public List<String> getPatterns() { return patterns; } public Config setPatterns(List<String> patterns) { this.patterns = patterns; return this; } public boolean isMatchOptionalTrailingSeparator() { return matchOptionalTrailingSeparator; } public Config setMatchOptionalTrailingSeparator( boolean matchOptionalTrailingSeparator) { this.matchOptionalTrailingSeparator = matchOptionalTrailingSeparator; return this; } @Override public String toString() { return new ToStringCreator(this).append("patterns", patterns) .append("matchOptionalTrailingSeparator", matchOptionalTrailingSeparator) .toString(); } } }
6》 FilteringWebHandler 過濾器執行處理器,內部包含全局過濾器。同時也是gateway 獲取到handler之后進行處理的入口。
7》 RoutePredicateHandlerMapping, 類似於SpringMVC 內部的org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerMapping(解析SpringMVC 的請求); 只不過這個HandlerMapping 是解析路由請求, 繼承關系如下:
3. 服務調用過程
1. 一個簡單的URL為例子
$ curl --header 'X-Request-Id: 2' --header 'X-Request-Id2: 3' http://localhost:9527/pay/getServerPort % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 52 0 52 0 0 2588 0 --:--:-- --:--:-- --:--:-- 2888{"success":true,"code":"200","msg":"","data":"8081"}
2. 查看其調用過程如下:
1. 程序入口 org.springframework.web.reactive.DispatcherHandler, 源碼如下:

package org.springframework.web.reactive; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.http.HttpStatus; import org.springframework.lang.Nullable; import org.springframework.web.server.ResponseStatusException; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.WebHandler; import org.springframework.web.server.adapter.WebHttpHandlerBuilder; /** * Central dispatcher for HTTP request handlers/controllers. Dispatches to * registered handlers for processing a request, providing convenient mapping * facilities. * * <p>{@code DispatcherHandler} discovers the delegate components it needs from * Spring configuration. It detects the following in the application context: * <ul> * <li>{@link HandlerMapping} -- map requests to handler objects * <li>{@link HandlerAdapter} -- for using any handler interface * <li>{@link HandlerResultHandler} -- process handler return values * </ul> * * <p>{@code DispatcherHandler} is also designed to be a Spring bean itself and * implements {@link ApplicationContextAware} for access to the context it runs * in. If {@code DispatcherHandler} is declared with the bean name "webHandler" * it is discovered by {@link WebHttpHandlerBuilder#applicationContext} which * creates a processing chain together with {@code WebFilter}, * {@code WebExceptionHandler} and others. * * <p>A {@code DispatcherHandler} bean declaration is included in * {@link org.springframework.web.reactive.config.EnableWebFlux @EnableWebFlux} * configuration. * * @author Rossen Stoyanchev * @author Sebastien Deleuze * @author Juergen Hoeller * @since 5.0 * @see WebHttpHandlerBuilder#applicationContext(ApplicationContext) */ public class DispatcherHandler implements WebHandler, ApplicationContextAware { @Nullable private List<HandlerMapping> handlerMappings; @Nullable private List<HandlerAdapter> handlerAdapters; @Nullable private List<HandlerResultHandler> resultHandlers; /** * Create a new {@code DispatcherHandler} which needs to be configured with * an {@link ApplicationContext} through {@link #setApplicationContext}. */ public DispatcherHandler() { } /** * Create a new {@code DispatcherHandler} for the given {@link ApplicationContext}. * @param applicationContext the application context to find the handler beans in */ public DispatcherHandler(ApplicationContext applicationContext) { initStrategies(applicationContext); } /** * Return all {@link HandlerMapping} beans detected by type in the * {@link #setApplicationContext injected context} and also * {@link AnnotationAwareOrderComparator#sort(List) sorted}. * <p><strong>Note:</strong> This method may return {@code null} if invoked * prior to {@link #setApplicationContext(ApplicationContext)}. * @return immutable list with the configured mappings or {@code null} */ @Nullable public final List<HandlerMapping> getHandlerMappings() { return this.handlerMappings; } @Override public void setApplicationContext(ApplicationContext applicationContext) { initStrategies(applicationContext); } protected void initStrategies(ApplicationContext context) { Map<String, HandlerMapping> mappingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors( context, HandlerMapping.class, true, false); ArrayList<HandlerMapping> mappings = new ArrayList<>(mappingBeans.values()); AnnotationAwareOrderComparator.sort(mappings); this.handlerMappings = Collections.unmodifiableList(mappings); Map<String, HandlerAdapter> adapterBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors( context, HandlerAdapter.class, true, false); this.handlerAdapters = new ArrayList<>(adapterBeans.values()); AnnotationAwareOrderComparator.sort(this.handlerAdapters); Map<String, HandlerResultHandler> beans = BeanFactoryUtils.beansOfTypeIncludingAncestors( context, HandlerResultHandler.class, true, false); this.resultHandlers = new ArrayList<>(beans.values()); AnnotationAwareOrderComparator.sort(this.resultHandlers); } @Override public Mono<Void> handle(ServerWebExchange exchange) { if (this.handlerMappings == null) { return createNotFoundError(); } return Flux.fromIterable(this.handlerMappings) .concatMap(mapping -> mapping.getHandler(exchange)) .next() .switchIfEmpty(createNotFoundError()) .flatMap(handler -> invokeHandler(exchange, handler)) .flatMap(result -> handleResult(exchange, result)); } private <R> Mono<R> createNotFoundError() { return Mono.defer(() -> { Exception ex = new ResponseStatusException(HttpStatus.NOT_FOUND, "No matching handler"); return Mono.error(ex); }); } private Mono<HandlerResult> invokeHandler(ServerWebExchange exchange, Object handler) { if (this.handlerAdapters != null) { for (HandlerAdapter handlerAdapter : this.handlerAdapters) { if (handlerAdapter.supports(handler)) { return handlerAdapter.handle(exchange, handler); } } } return Mono.error(new IllegalStateException("No HandlerAdapter: " + handler)); } private Mono<Void> handleResult(ServerWebExchange exchange, HandlerResult result) { return getResultHandler(result).handleResult(exchange, result) .checkpoint("Handler " + result.getHandler() + " [DispatcherHandler]") .onErrorResume(ex -> result.applyExceptionHandler(ex).flatMap(exResult -> { String text = "Exception handler " + exResult.getHandler() + ", error=\"" + ex.getMessage() + "\" [DispatcherHandler]"; return getResultHandler(exResult).handleResult(exchange, exResult).checkpoint(text); })); } private HandlerResultHandler getResultHandler(HandlerResult handlerResult) { if (this.resultHandlers != null) { for (HandlerResultHandler resultHandler : this.resultHandlers) { if (resultHandler.supports(handlerResult)) { return resultHandler; } } } throw new IllegalStateException("No HandlerResultHandler for " + handlerResult.getReturnValue()); } }
入口是: org.springframework.web.reactive.DispatcherHandler#handle:
調用鏈如下:(可以看到也是從netty 相關調用到該類的)
這個方法也比較清晰, 主要就是遍歷handlerMappings 獲取到Handler, 然后調用handler, 並且處理結果。
2. 遍歷handlerMappings獲取handler
handlerMappings 如下, 可以看到有4個,有就是遍歷這四個獲取handler, 找到就繼續后面的流程。 對於SpringMVC 走的是 RequestMappinHandlerMapping 找到org.springframework.web.method.HandlerMethod
對於Webflux 路由類型,會到: org.springframework.cloud.gateway.handler.RoutePredicateHandlerMapping#getHandlerInternal 尋找handler
protected Mono<?> getHandlerInternal(ServerWebExchange exchange) { if (this.managementPortType == RoutePredicateHandlerMapping.ManagementPortType.DIFFERENT && this.managementPort != null && exchange.getRequest().getURI().getPort() == this.managementPort) { return Mono.empty(); } else { exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_HANDLER_MAPPER_ATTR, this.getSimpleName()); return this.lookupRoute(exchange).flatMap((r) -> { exchange.getAttributes().remove(ServerWebExchangeUtils.GATEWAY_PREDICATE_ROUTE_ATTR); if (this.logger.isDebugEnabled()) { this.logger.debug("Mapping [" + this.getExchangeDesc(exchange) + "] to " + r); } exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR, r); return Mono.just(this.webHandler); }).switchIfEmpty(Mono.empty().then(Mono.fromRunnable(() -> { exchange.getAttributes().remove(ServerWebExchangeUtils.GATEWAY_PREDICATE_ROUTE_ATTR); if (this.logger.isTraceEnabled()) { this.logger.trace("No RouteDefinition found for [" + this.getExchangeDesc(exchange) + "]"); } }))); } }
1》 org.springframework.cloud.gateway.handler.RoutePredicateHandlerMapping#lookupRoute 根據路徑尋找滿足條件的路由
protected Mono<Route> lookupRoute(ServerWebExchange exchange) { return this.routeLocator.getRoutes().concatMap((route) -> { return Mono.just(route).filterWhen((r) -> { exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_PREDICATE_ROUTE_ATTR, r.getId()); return (Publisher)r.getPredicate().apply(exchange); }).doOnError((e) -> { this.logger.error("Error applying predicate for route: " + route.getId(), e); }).onErrorResume((e) -> { return Mono.empty(); }); }).next().map((route) -> { if (this.logger.isDebugEnabled()) { this.logger.debug("Route matched: " + route.getId()); } this.validateRoute(route, exchange); return route; }); }
這里就是調用org.springframework.cloud.gateway.route.CachingRouteLocator#routes 獲取到所有的Route 對象, 然后調用getPredicate().apply(exchange) 判斷是否滿足斷言的要求。
org.springframework.cloud.gateway.handler.AsyncPredicate.AndAsyncPredicate#apply :
public Publisher<Boolean> apply(T t) { return Flux.zip((Publisher)this.left.apply(t), (Publisher)this.right.apply(t)).map((tuple) -> { return (Boolean)tuple.getT1() && (Boolean)tuple.getT2(); }); }
可以看到如果是多個斷言,就進行邏輯與進行判斷。比如會先到: org.springframework.cloud.gateway.handler.predicate.GatewayPredicate#test
public boolean test(ServerWebExchange exchange) { PathContainer path = PathContainer.parsePath(exchange.getRequest().getURI().getRawPath()); Optional<PathPattern> optionalPathPattern = pathPatterns.stream().filter((pattern) -> { return pattern.matches(path); }).findFirst(); if (optionalPathPattern.isPresent()) { PathPattern pathPattern = (PathPattern)optionalPathPattern.get(); PathRoutePredicateFactory.traceMatch("Pattern", pathPattern.getPatternString(), path, true); PathMatchInfo pathMatchInfo = pathPattern.matchAndExtract(path); ServerWebExchangeUtils.putUriTemplateVariables(exchange, pathMatchInfo.getUriVariables()); return true; } else { PathRoutePredicateFactory.traceMatch("Pattern", config.getPatterns(), path, false); return false; } }
2》 flatMap 內部核心邏輯:
exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR, r); 代碼將Route 對象放到了org.springframework.web.server.adapter.DefaultServerWebExchange#attributes 內部的Map中用於后續業務代碼使用(org.springframework.web.server.adapter.DefaultServerWebExchange#attributes 這個屬性是一個內部Map, 用於當前上下文共享和傳遞一些信息)。 然后返回的webHandler 就是 自動配置注入的org.springframework.cloud.gateway.handler.FilteringWebHandler 對象。
3. 調用org.springframework.web.reactive.DispatcherHandler#invokeHandler 開始處理邏輯
private Mono<HandlerResult> invokeHandler(ServerWebExchange exchange, Object handler) { if (this.handlerAdapters != null) { for (HandlerAdapter handlerAdapter : this.handlerAdapters) { if (handlerAdapter.supports(handler)) { return handlerAdapter.handle(exchange, handler); } } } return Mono.error(new IllegalStateException("No HandlerAdapter: " + handler)); }
和springmvc 機制一樣,交給HandlerAdapter處理器適配器, 如果支持這樣的handler就交給處理器適配器進行調用。 處理器適配器有三個:
1》 最后匹配到: org.springframework.web.reactive.result.SimpleHandlerAdapter#supports
public boolean supports(Object handler) { return WebHandler.class.isAssignableFrom(handler.getClass()); }
2》 然后進行調用:org.springframework.web.reactive.result.SimpleHandlerAdapter#handle
public Mono<HandlerResult> handle(ServerWebExchange exchange, Object handler) { WebHandler webHandler = (WebHandler)handler; Mono<Void> mono = webHandler.handle(exchange); return mono.then(Mono.empty()); }
3》 繼續調用到: org.springframework.cloud.gateway.handler.FilteringWebHandler#handle
@Override public Mono<Void> handle(ServerWebExchange exchange) { Route route = exchange.getRequiredAttribute(GATEWAY_ROUTE_ATTR); List<GatewayFilter> gatewayFilters = route.getFilters(); List<GatewayFilter> combined = new ArrayList<>(this.globalFilters); combined.addAll(gatewayFilters); // TODO: needed or cached? AnnotationAwareOrderComparator.sort(combined); if (logger.isDebugEnabled()) { logger.debug("Sorted gatewayFilterFactories: " + combined); } return new DefaultGatewayFilterChain(combined).filter(exchange); }
這里可以看到其核心邏輯是:首先從上面的緩存map 獲取到路由Route 對象; 然后獲取到過濾器; 然后獲取到全局過濾器; 兩個過濾器合並, 合並之后排序完創建鏈條進行過濾器鏈的調用。
這里合並后的過濾器鏈如下:
4》 org.springframework.cloud.gateway.handler.FilteringWebHandler.DefaultGatewayFilterChain 過濾器鏈:(可以看到是一個鏈條模式的調用)
private static class GatewayFilterAdapter implements GatewayFilter { private final GlobalFilter delegate; GatewayFilterAdapter(GlobalFilter delegate) { this.delegate = delegate; } public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { return this.delegate.filter(exchange, chain); } public String toString() { StringBuilder sb = new StringBuilder("GatewayFilterAdapter{"); sb.append("delegate=").append(this.delegate); sb.append('}'); return sb.toString(); } } private static class DefaultGatewayFilterChain implements GatewayFilterChain { private final int index; private final List<GatewayFilter> filters; DefaultGatewayFilterChain(List<GatewayFilter> filters) { this.filters = filters; this.index = 0; } private DefaultGatewayFilterChain(FilteringWebHandler.DefaultGatewayFilterChain parent, int index) { this.filters = parent.getFilters(); this.index = index; } public List<GatewayFilter> getFilters() { return this.filters; } public Mono<Void> filter(ServerWebExchange exchange) { return Mono.defer(() -> { if (this.index < this.filters.size()) { GatewayFilter filter = (GatewayFilter)this.filters.get(this.index); FilteringWebHandler.DefaultGatewayFilterChain chain = new FilteringWebHandler.DefaultGatewayFilterChain(this, this.index + 1); return filter.filter(exchange, chain); } else { return Mono.empty(); } }); } }
這里介紹幾個重要的過濾器的作用:
(1) org.springframework.cloud.gateway.filter.NettyWriteResponseFilter#filter: 可以看到核心作用是在鏈條執行完發送結果
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { return chain.filter(exchange).doOnError((throwable) -> { this.cleanup(exchange); }).then(Mono.defer(() -> { Connection connection = (Connection)exchange.getAttribute(ServerWebExchangeUtils.CLIENT_RESPONSE_CONN_ATTR); if (connection == null) { return Mono.empty(); } else { if (log.isTraceEnabled()) { log.trace("NettyWriteResponseFilter start inbound: " + connection.channel().id().asShortText() + ", outbound: " + exchange.getLogPrefix()); } ServerHttpResponse response = exchange.getResponse(); NettyDataBufferFactory factory = (NettyDataBufferFactory)response.bufferFactory(); ByteBufFlux var10000 = connection.inbound().receive().retain(); factory.getClass(); Flux<NettyDataBuffer> body = var10000.map(factory::wrap); MediaType contentType = null; try { contentType = response.getHeaders().getContentType(); } catch (Exception var8) { if (log.isTraceEnabled()) { log.trace("invalid media type", var8); } } return this.isStreamingMediaType(contentType) ? response.writeAndFlushWith(body.map(Flux::just)) : response.writeWith(body); } })).doOnCancel(() -> { this.cleanup(exchange); }); }
(2) org.springframework.cloud.gateway.filter.RouteToRequestUrlFilter#filter
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { Route route = (Route)exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR); if (route == null) { return chain.filter(exchange); } else { log.trace("RouteToRequestUrlFilter start"); URI uri = exchange.getRequest().getURI(); boolean encoded = ServerWebExchangeUtils.containsEncodedParts(uri); URI routeUri = route.getUri(); if (hasAnotherScheme(routeUri)) { exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_SCHEME_PREFIX_ATTR, routeUri.getScheme()); routeUri = URI.create(routeUri.getSchemeSpecificPart()); } if ("lb".equalsIgnoreCase(routeUri.getScheme()) && routeUri.getHost() == null) { throw new IllegalStateException("Invalid host: " + routeUri.toString()); } else { URI mergedUrl = UriComponentsBuilder.fromUri(uri).scheme(routeUri.getScheme()).host(routeUri.getHost()).port(routeUri.getPort()).build(encoded).toUri(); exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR, mergedUrl); return chain.filter(exchange); } }
替換請求的url, 最終放到org.springframework.web.server.adapter.DefaultServerWebExchange#attributes 內部替換后的url 為: lb://cloud-payment-service/pay/getServerPort
(3) org.springframework.cloud.gateway.filter.LoadBalancerClientFilter#filter
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { URI url = (URI)exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR); String schemePrefix = (String)exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_SCHEME_PREFIX_ATTR); if (url != null && ("lb".equals(url.getScheme()) || "lb".equals(schemePrefix))) { ServerWebExchangeUtils.addOriginalRequestUrl(exchange, url); if (log.isTraceEnabled()) { log.trace("LoadBalancerClientFilter url before: " + url); } ServiceInstance instance = this.choose(exchange); if (instance == null) { throw NotFoundException.create(this.properties.isUse404(), "Unable to find instance for " + url.getHost()); } else { URI uri = exchange.getRequest().getURI(); String overrideScheme = instance.isSecure() ? "https" : "http"; if (schemePrefix != null) { overrideScheme = url.getScheme(); } URI requestUrl = this.loadBalancer.reconstructURI(new DelegatingServiceInstance(instance, overrideScheme), uri); if (log.isTraceEnabled()) { log.trace("LoadBalancerClientFilter url chosen: " + requestUrl); } exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR, requestUrl); return chain.filter(exchange); } } else { return chain.filter(exchange); } } protected ServiceInstance choose(ServerWebExchange exchange) { return this.loadBalancer.choose(((URI)exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR)).getHost()); }
這里也就是判斷當前上下文的請求路徑, 如果以lb 開頭就開始調用loadBalancer(這里默認是ribbon 相關組件), 然后替換后生成最終的url 如下: http://127.0.0.1:8081/pay/getServerPort, 然后存入到exchange.getAttributes() 緩存map 中。
(4) org.springframework.cloud.gateway.filter.NettyRoutingFilter#filter 這里也就是進行發送數據請求, 並且將netty 通道信息記錄在connection 對象中存入exchange.getAttribute() 屬性中用於后續獲取結果等操作

/* * Copyright 2013-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.cloud.gateway.filter; import java.net.URI; import java.time.Duration; import java.util.List; import io.netty.channel.ChannelOption; import io.netty.handler.codec.http.DefaultHttpHeaders; import io.netty.handler.codec.http.HttpMethod; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.netty.http.client.HttpClient; import reactor.netty.http.client.HttpClientResponse; import org.springframework.beans.factory.ObjectProvider; import org.springframework.cloud.gateway.config.HttpClientProperties; import org.springframework.cloud.gateway.filter.headers.HttpHeadersFilter; import org.springframework.cloud.gateway.filter.headers.HttpHeadersFilter.Type; import org.springframework.cloud.gateway.route.Route; import org.springframework.cloud.gateway.support.TimeoutException; import org.springframework.core.Ordered; import org.springframework.core.io.buffer.NettyDataBuffer; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.server.reactive.AbstractServerHttpResponse; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.http.server.reactive.ServerHttpResponseDecorator; import org.springframework.util.StringUtils; import org.springframework.web.server.ResponseStatusException; import org.springframework.web.server.ServerWebExchange; import static org.springframework.cloud.gateway.filter.headers.HttpHeadersFilter.filterRequest; import static org.springframework.cloud.gateway.support.RouteMetadataUtils.CONNECT_TIMEOUT_ATTR; import static org.springframework.cloud.gateway.support.RouteMetadataUtils.RESPONSE_TIMEOUT_ATTR; import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.CLIENT_RESPONSE_ATTR; import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.CLIENT_RESPONSE_CONN_ATTR; import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.CLIENT_RESPONSE_HEADER_NAMES; import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR; import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR; import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR; import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.PRESERVE_HOST_HEADER_ATTRIBUTE; import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.isAlreadyRouted; import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.setAlreadyRouted; /** * @author Spencer Gibb * @author Biju Kunjummen */ public class NettyRoutingFilter implements GlobalFilter, Ordered { private static final Log log = LogFactory.getLog(NettyRoutingFilter.class); private final HttpClient httpClient; private final ObjectProvider<List<HttpHeadersFilter>> headersFiltersProvider; private final HttpClientProperties properties; // do not use this headersFilters directly, use getHeadersFilters() instead. private volatile List<HttpHeadersFilter> headersFilters; public NettyRoutingFilter(HttpClient httpClient, ObjectProvider<List<HttpHeadersFilter>> headersFiltersProvider, HttpClientProperties properties) { this.httpClient = httpClient; this.headersFiltersProvider = headersFiltersProvider; this.properties = properties; } public List<HttpHeadersFilter> getHeadersFilters() { if (headersFilters == null) { headersFilters = headersFiltersProvider.getIfAvailable(); } return headersFilters; } @Override public int getOrder() { return Ordered.LOWEST_PRECEDENCE; } @Override @SuppressWarnings("Duplicates") public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { URI requestUrl = exchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR); String scheme = requestUrl.getScheme(); if (isAlreadyRouted(exchange) || (!"http".equals(scheme) && !"https".equals(scheme))) { return chain.filter(exchange); } setAlreadyRouted(exchange); ServerHttpRequest request = exchange.getRequest(); final HttpMethod method = HttpMethod.valueOf(request.getMethodValue()); final String url = requestUrl.toASCIIString(); HttpHeaders filtered = filterRequest(getHeadersFilters(), exchange); final DefaultHttpHeaders httpHeaders = new DefaultHttpHeaders(); filtered.forEach(httpHeaders::set); boolean preserveHost = exchange .getAttributeOrDefault(PRESERVE_HOST_HEADER_ATTRIBUTE, false); Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR); Flux<HttpClientResponse> responseFlux = httpClientWithTimeoutFrom(route) .headers(headers -> { headers.add(httpHeaders); // Will either be set below, or later by Netty headers.remove(HttpHeaders.HOST); if (preserveHost) { String host = request.getHeaders().getFirst(HttpHeaders.HOST); headers.add(HttpHeaders.HOST, host); } }).request(method).uri(url).send((req, nettyOutbound) -> { if (log.isTraceEnabled()) { nettyOutbound .withConnection(connection -> log.trace("outbound route: " + connection.channel().id().asShortText() + ", inbound: " + exchange.getLogPrefix())); } return nettyOutbound.send(request.getBody() .map(dataBuffer -> ((NettyDataBuffer) dataBuffer) .getNativeBuffer())); }).responseConnection((res, connection) -> { // Defer committing the response until all route filters have run // Put client response as ServerWebExchange attribute and write // response later NettyWriteResponseFilter exchange.getAttributes().put(CLIENT_RESPONSE_ATTR, res); exchange.getAttributes().put(CLIENT_RESPONSE_CONN_ATTR, connection); ServerHttpResponse response = exchange.getResponse(); // put headers and status so filters can modify the response HttpHeaders headers = new HttpHeaders(); res.responseHeaders().forEach( entry -> headers.add(entry.getKey(), entry.getValue())); String contentTypeValue = headers.getFirst(HttpHeaders.CONTENT_TYPE); if (StringUtils.hasLength(contentTypeValue)) { exchange.getAttributes().put(ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR, contentTypeValue); } setResponseStatus(res, response); // make sure headers filters run after setting status so it is // available in response HttpHeaders filteredResponseHeaders = HttpHeadersFilter.filter( getHeadersFilters(), headers, exchange, Type.RESPONSE); if (!filteredResponseHeaders .containsKey(HttpHeaders.TRANSFER_ENCODING) && filteredResponseHeaders .containsKey(HttpHeaders.CONTENT_LENGTH)) { // It is not valid to have both the transfer-encoding header and // the content-length header. // Remove the transfer-encoding header in the response if the // content-length header is present. response.getHeaders().remove(HttpHeaders.TRANSFER_ENCODING); } exchange.getAttributes().put(CLIENT_RESPONSE_HEADER_NAMES, filteredResponseHeaders.keySet()); response.getHeaders().putAll(filteredResponseHeaders); return Mono.just(res); }); Duration responseTimeout = getResponseTimeout(route); if (responseTimeout != null) { responseFlux = responseFlux .timeout(responseTimeout, Mono.error(new TimeoutException( "Response took longer than timeout: " + responseTimeout))) .onErrorMap(TimeoutException.class, th -> new ResponseStatusException(HttpStatus.GATEWAY_TIMEOUT, th.getMessage(), th)); } return responseFlux.then(chain.filter(exchange)); } private void setResponseStatus(HttpClientResponse clientResponse, ServerHttpResponse response) { HttpStatus status = HttpStatus.resolve(clientResponse.status().code()); if (status != null) { response.setStatusCode(status); } else { while (response instanceof ServerHttpResponseDecorator) { response = ((ServerHttpResponseDecorator) response).getDelegate(); } if (response instanceof AbstractServerHttpResponse) { ((AbstractServerHttpResponse) response) .setStatusCodeValue(clientResponse.status().code()); } else { // TODO: log warning here, not throw error? throw new IllegalStateException("Unable to set status code " + clientResponse.status().code() + " on response of type " + response.getClass().getName()); } } } private HttpClient httpClientWithTimeoutFrom(Route route) { Integer connectTimeout = (Integer) route.getMetadata().get(CONNECT_TIMEOUT_ATTR); if (connectTimeout != null) { return this.httpClient.tcpConfiguration((tcpClient) -> tcpClient .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connectTimeout)); } return httpClient; } private Duration getResponseTimeout(Route route) { Number responseTimeout = (Number) route.getMetadata().get(RESPONSE_TIMEOUT_ATTR); return responseTimeout != null ? Duration.ofMillis(responseTimeout.longValue()) : properties.getResponseTimeout(); } }
filter方法的headers如下:
內部的httpClient 如下:
connection 對象如下:
(5) org.springframework.cloud.gateway.filter.ForwardRoutingFilter#filter
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { URI requestUrl = (URI)exchange.getRequiredAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR); String scheme = requestUrl.getScheme(); if (!ServerWebExchangeUtils.isAlreadyRouted(exchange) && "forward".equals(scheme)) { if (log.isTraceEnabled()) { log.trace("Forwarding to URI: " + requestUrl); } return this.getDispatcherHandler().handle(exchange); } else { return chain.filter(exchange); } }
處理forward 轉發請求的。
上述鏈條執行完成之后則請求回到org.springframework.cloud.gateway.filter.NettyWriteResponseFilter#filter 執行回傳結果的操作。也就是寫到org.springframework.web.server.ServerWebExchange#getResponse 回傳給請求的客戶端。
總結:我理解整體的思路是:
1》 客戶端通過netty 和 gateway 建立連接, 封裝一個org.springframework.web.server.ServerWebExchange 對象用於整個請求鏈的上下文環境共享

/* * Copyright 2002-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.web.server; import java.security.Principal; import java.time.Instant; import java.util.Map; import java.util.function.Consumer; import java.util.function.Function; import reactor.core.publisher.Mono; import org.springframework.context.ApplicationContext; import org.springframework.context.i18n.LocaleContext; import org.springframework.http.codec.multipart.Part; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.MultiValueMap; /** * Contract for an HTTP request-response interaction. Provides access to the HTTP * request and response and also exposes additional server-side processing * related properties and features such as request attributes. * * @author Rossen Stoyanchev * @since 5.0 */ public interface ServerWebExchange { /** * Name of {@link #getAttributes() attribute} whose value can be used to * correlate log messages for this exchange. Use {@link #getLogPrefix()} to * obtain a consistently formatted prefix based on this attribute. * @since 5.1 * @see #getLogPrefix() */ String LOG_ID_ATTRIBUTE = ServerWebExchange.class.getName() + ".LOG_ID"; /** * Return the current HTTP request. */ ServerHttpRequest getRequest(); /** * Return the current HTTP response. */ ServerHttpResponse getResponse(); /** * Return a mutable map of request attributes for the current exchange. */ Map<String, Object> getAttributes(); /** * Return the request attribute value if present. * @param name the attribute name * @param <T> the attribute type * @return the attribute value */ @SuppressWarnings("unchecked") @Nullable default <T> T getAttribute(String name) { return (T) getAttributes().get(name); } /** * Return the request attribute value or if not present raise an * {@link IllegalArgumentException}. * @param name the attribute name * @param <T> the attribute type * @return the attribute value */ @SuppressWarnings("unchecked") default <T> T getRequiredAttribute(String name) { T value = getAttribute(name); Assert.notNull(value, () -> "Required attribute '" + name + "' is missing"); return value; } /** * Return the request attribute value, or a default, fallback value. * @param name the attribute name * @param defaultValue a default value to return instead * @param <T> the attribute type * @return the attribute value */ @SuppressWarnings("unchecked") default <T> T getAttributeOrDefault(String name, T defaultValue) { return (T) getAttributes().getOrDefault(name, defaultValue); } /** * Return the web session for the current request. Always guaranteed to * return an instance either matching to the session id requested by the * client, or with a new session id either because the client did not * specify one or because the underlying session had expired. Use of this * method does not automatically create a session. See {@link WebSession} * for more details. */ Mono<WebSession> getSession(); /** * Return the authenticated user for the request, if any. */ <T extends Principal> Mono<T> getPrincipal(); /** * Return the form data from the body of the request if the Content-Type is * {@code "application/x-www-form-urlencoded"} or an empty map otherwise. * <p><strong>Note:</strong> calling this method causes the request body to * be read and parsed in full and the resulting {@code MultiValueMap} is * cached so that this method is safe to call more than once. */ Mono<MultiValueMap<String, String>> getFormData(); /** * Return the parts of a multipart request if the Content-Type is * {@code "multipart/form-data"} or an empty map otherwise. * <p><strong>Note:</strong> calling this method causes the request body to * be read and parsed in full and the resulting {@code MultiValueMap} is * cached so that this method is safe to call more than once. * <p><strong>Note:</strong>the {@linkplain Part#content() contents} of each * part is not cached, and can only be read once. */ Mono<MultiValueMap<String, Part>> getMultipartData(); /** * Return the {@link LocaleContext} using the configured * {@link org.springframework.web.server.i18n.LocaleContextResolver}. */ LocaleContext getLocaleContext(); /** * Return the {@link ApplicationContext} associated with the web application, * if it was initialized with one via * {@link org.springframework.web.server.adapter.WebHttpHandlerBuilder#applicationContext(ApplicationContext)}. * @since 5.0.3 * @see org.springframework.web.server.adapter.WebHttpHandlerBuilder#applicationContext(ApplicationContext) */ @Nullable ApplicationContext getApplicationContext(); /** * Returns {@code true} if the one of the {@code checkNotModified} methods * in this contract were used and they returned true. */ boolean isNotModified(); /** * An overloaded variant of {@link #checkNotModified(String, Instant)} with * a last-modified timestamp only. * @param lastModified the last-modified time * @return whether the request qualifies as not modified */ boolean checkNotModified(Instant lastModified); /** * An overloaded variant of {@link #checkNotModified(String, Instant)} with * an {@code ETag} (entity tag) value only. * @param etag the entity tag for the underlying resource. * @return true if the request does not require further processing. */ boolean checkNotModified(String etag); /** * Check whether the requested resource has been modified given the supplied * {@code ETag} (entity tag) and last-modified timestamp as determined by * the application. Also transparently prepares the response, setting HTTP * status, and adding "ETag" and "Last-Modified" headers when applicable. * This method works with conditional GET/HEAD requests as well as with * conditional POST/PUT/DELETE requests. * <p><strong>Note:</strong> The HTTP specification recommends setting both * ETag and Last-Modified values, but you can also use * {@code #checkNotModified(String)} or * {@link #checkNotModified(Instant)}. * @param etag the entity tag that the application determined for the * underlying resource. This parameter will be padded with quotes (") * if necessary. * @param lastModified the last-modified timestamp that the application * determined for the underlying resource * @return true if the request does not require further processing. */ boolean checkNotModified(@Nullable String etag, Instant lastModified); /** * Transform the given url according to the registered transformation function(s). * By default, this method returns the given {@code url}, though additional * transformation functions can by registered with {@link #addUrlTransformer} * @param url the URL to transform * @return the transformed URL */ String transformUrl(String url); /** * Register an additional URL transformation function for use with {@link #transformUrl}. * The given function can be used to insert an id for authentication, a nonce for CSRF * protection, etc. * <p>Note that the given function is applied after any previously registered functions. * @param transformer a URL transformation function to add */ void addUrlTransformer(Function<String, String> transformer); /** * Return a log message prefix to use to correlate messages for this exchange. * The prefix is based on the value of the attribute {@link #LOG_ID_ATTRIBUTE} * along with some extra formatting so that the prefix can be conveniently * prepended with no further formatting no separators required. * @return the log message prefix or an empty String if the * {@link #LOG_ID_ATTRIBUTE} is not set. * @since 5.1 */ String getLogPrefix(); /** * Return a builder to mutate properties of this exchange by wrapping it * with {@link ServerWebExchangeDecorator} and returning either mutated * values or delegating back to this instance. */ default Builder mutate() { return new DefaultServerWebExchangeBuilder(this); } /** * Builder for mutating an existing {@link ServerWebExchange}. * Removes the need */ interface Builder { /** * Configure a consumer to modify the current request using a builder. * <p>Effectively this: * <pre> * exchange.mutate().request(builder-> builder.method(HttpMethod.PUT)); * * // vs... * * ServerHttpRequest request = exchange.getRequest().mutate() * .method(HttpMethod.PUT) * .build(); * * exchange.mutate().request(request); * </pre> * @see ServerHttpRequest#mutate() */ Builder request(Consumer<ServerHttpRequest.Builder> requestBuilderConsumer); /** * Set the request to use especially when there is a need to override * {@link ServerHttpRequest} methods. To simply mutate request properties * see {@link #request(Consumer)} instead. * @see org.springframework.http.server.reactive.ServerHttpRequestDecorator */ Builder request(ServerHttpRequest request); /** * Set the response to use. * @see org.springframework.http.server.reactive.ServerHttpResponseDecorator */ Builder response(ServerHttpResponse response); /** * Set the {@code Mono<Principal>} to return for this exchange. */ Builder principal(Mono<Principal> principalMono); /** * Build a {@link ServerWebExchange} decorator with the mutated properties. */ ServerWebExchange build(); } }
2》 到達org.springframework.web.reactive.DispatcherHandler#handle 方法開始處理: 找到滿足條件的Route 對象(經過一系列斷言匹配); 然后找到該Route的filter和全局filter, 組合后開始過濾器鏈條的執行
3》 進行過濾器鏈條的執行, 核心的包括:
請求路徑替換的
lb 負載均衡的過濾器
Netty 請求數據(使用負載均衡過濾器處理后的url 請求數據,並將響應通道channel封裝起來存入ServerWebExchange.getAttributes() 屬性中)
Netty 響應數據處理器,從上面的channel 中獲取到響應數據, 處理后通過ServerWebExchange#getResponse 再寫回去數據。
所以起重要作用的是其內部的一系列過濾器鏈, 以鏈條的形式完成整個請求響應。org.springframework.web.server.ServerWebExchange 也是一個重要的類,用於在整個請求鏈中作為上下文傳遞一些參數。