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 {
}
}
}
View Code
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 + '}';
}
}
View Code
可以看到自动配置累注入了几个重要的对象:
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);
}
}
View Code
可以看到这个对象内部就是根据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();
}
}
}
View Code
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());
}
}
View Code
入口是: 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();
}
}
View Code
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();
}
}
View Code
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 也是一个重要的类,用于在整个请求链中作为上下文传递一些参数。
exchange.getAttribute
【当你用心写完每一篇博客之后,你会发现它比你用代码实现功能更有成就感!】