gateway网关原理
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 也是一个重要的类,用于在整个请求链中作为上下文传递一些参数。