gateway网关原理

  Gateway 是 Spring Cloud 的一个全新项目,该项目是基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。其核心逻辑是路由转发+执行过滤器链。Gateway是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty。

  Gateway 的目标,不仅提供统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控/指标,和限流。

  Gateway的三个核心组件: Route(路由)、Predicate(断言)、Filter(过滤器)。

1. 项目配置

  路由配置的时候可以配置断言、以及过滤器。

1. 路由配置

路由配置有两种方式,一种是yml 配置, 另一种是代码配置

1. yml 配置

server:
  port: 9527

spring:
  application:
    name: cloud-gateway
  cloud:
    gateway:
      routes:
        - id: payment_routh #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
#          uri: http://localhost:8081          #匹配后提供服务的路由地址
          uri: lb://cloud-payment-service          #根据服务名称进行负载均衡替换
          predicates:
            - Path=/pay/listAll/**         # 断言,路径相匹配的进行路由
            - Host=**.com

        - id: cloud-provider-hystrix-payment #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
          uri: lb://cloud-provider-hystrix-payment          #根据服务名称进行负载均衡替换
          predicates:
            - Path=/hystrix/**         # 断言,路径相匹配的进行路由
          filters:
            # 限流过滤器,使用gateway内置令牌算法
            - name: RequestRateLimiter
              args:
                # 令牌桶每秒填充平均速率,即等价于允许用户每秒处理多少个请求平均数
                redis-rate-limiter.replenishRate: 1
                # 令牌桶的容量,允许在一秒钟内完成的最大请求数
                redis-rate-limiter.burstCapacity: 2
                # 用于限流的键的解析器的 Bean 对象的名字。它使用 SpEL 表达式根据#{@beanName}从 Spring 容器中获取 Bean 对象。
                key-resolver: "#{@apiKeyResolver}"

        - id: payment_routh2 #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
#          uri: http://localhost:8081          #匹配后提供服务的路由地址
          uri: lb://cloud-payment-service          #根据服务名称进行负载均衡替换
          predicates:
            - Path=/pay/getServerPort/**         # 断言,路径相匹配的进行路由
#            - After=2020-03-12T15:44:15.064+08:00[Asia/Shanghai] #日期后面(用于判断日期)
#            - Before=2020-03-12T15:44:15.064+08:00[Asia/Shanghai] #日期后面(用于判断日期)
#            - Between=2020-03-12T15:44:15.064+08:00[Asia/Shanghai], 2021-03-12T15:44:15.064+08:00[Asia/Shanghai] #日期之间(用于判断日期)
#            - Cookie=uname,zs   #带Cookie,并且uname的值为zs
            - Header=X-Request-Id,\d+ #请求头要有 X-Request-Id属性并且值为整数的正则表达式
            - Header=X-Request-Id2,\d+ #请求头要有 X-Request-Id属性并且值为整数的正则表达式

eureka:
  instance:
    hostname: cloud-gateway-service
  client: #服务提供者provider注册进eureka服务列表内
    service-url:
      register-with-eureka: true
      fetch-registry: true
      defaultZone: http://localhost:7001/eureka

2. 代码配置

package cn.qz.cloud.config;

import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class GatewayConfig {
    /**
     * 配置了一个id为route-name的路由规则
     * 当访问地址 http://localhost:9527/guonei时会自动转发到地址: http://news.baidu.com/guonei
     *
     * @param builder
     * @return
     */
    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
        RouteLocatorBuilder.Builder routes = builder.routes();
        routes.route("path_route_eiletxie",
                r -> r.path("/guonei")
                        .uri("http://news.baidu.com/guonei")).build();
        return routes.build();
    }

    @Bean
    public RouteLocator customRouteLocator2(RouteLocatorBuilder builder) {
        RouteLocatorBuilder.Builder routes = builder.routes();
        routes.route("path_route_eiletxie2",
                r -> r.path("/guoji")
                        .uri("http://news.baidu.com/guoji")).build();
        return routes.build();
    }
}

  代码内部的配置,相当于没有使用lb 负载均衡,相当于直接是请求转发的功能。

2. 全局过滤器配置(会应用到每个路由中)

  Gateway 也可以单独增加全局的filter, 如下:

package cn.qz.cloud.filter;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.HttpMethod;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.stream.Collectors;

@Component
public class LoggerFilter implements GlobalFilter, Ordered {

    private static final Logger log = LoggerFactory.getLogger(LoggerFilter.class);

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String method = request.getMethodValue();

        if (HttpMethod.POST.matches(method)) {
            return DataBufferUtils.join(exchange.getRequest().getBody())
                    .flatMap(dataBuffer -> {
                        byte[] bytes = new byte[dataBuffer.readableByteCount()];
                        dataBuffer.read(bytes);
                        String bodyString = new String(bytes, StandardCharsets.UTF_8);
                        logtrace(exchange, bodyString);
                        exchange.getAttributes().put("POST_BODY", bodyString);
                        DataBufferUtils.release(dataBuffer);
                        Flux<DataBuffer> cachedFlux = Flux.defer(() -> {
                            DataBuffer buffer = exchange.getResponse().bufferFactory()
                                    .wrap(bytes);
                            return Mono.just(buffer);
                        });

                        ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(
                                exchange.getRequest()) {
                            @Override
                            public Flux<DataBuffer> getBody() {
                                return cachedFlux;
                            }
                        };
                        return chain.filter(exchange.mutate().request(mutatedRequest)
                                .build());
                    });
        } else if (HttpMethod.GET.matches(method)) {
            Map m = request.getQueryParams();
            logtrace(exchange, m.toString());
        }
        return chain.filter(exchange);
    }

    /**
     * 日志信息
     *
     * @param exchange
     * @param param    请求参数
     */
    private void logtrace(ServerWebExchange exchange, String param) {
        ServerHttpRequest serverHttpRequest = exchange.getRequest();
        String hostString = serverHttpRequest.getRemoteAddress().getHostString();
        String path = serverHttpRequest.getURI().getPath();
        String method = serverHttpRequest.getMethodValue();
        String headers = serverHttpRequest.getHeaders().entrySet()
                .stream()
                .map(entry -> "            " + entry.getKey() + ": [" + String.join(";", entry.getValue()) + "]")
                .collect(Collectors.joining("\n"));
        log.info("\n" + "----------------             ----------------             ---------------->>\n" +
                        "HttpMethod : {}\n" +
                        "requestHost : {}\n" +
                        "Uri        : {}\n" +
                        "Param      : {}\n" +
                        "Headers    : \n" +
                        "{}\n" +
                        "\"<<----------------             ----------------             ----------------"
                , method, hostString, path, param, headers);
    }

    @Override
    public int getOrder() {
        return -1;
    }
}

  下面研究其作用过程。

 2. 启动过程查看

  按照Springboot 的套路,查看一个配置从AutoConfiguration 类查看。

1. 配置累和properties 查看

org.springframework.cloud.gateway.config.GatewayAutoConfiguration 自动配置类源码如下:(可以看到ConditionalOnProperty、AutoConfigureBefore、AutoConfigureAfter、ConditionalOnClass、ConditionalOnMissingBean 等条件注解)

/*
 * Copyright 2013-2019 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.cloud.gateway.config;

import java.security.cert.X509Certificate;
import java.util.List;

import com.netflix.hystrix.HystrixObservableCommand;
import io.netty.channel.ChannelOption;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import reactor.core.publisher.Flux;
import reactor.netty.http.client.HttpClient;
import reactor.netty.resources.ConnectionProvider;
import reactor.netty.tcp.ProxyProvider;
import rx.RxReactiveStreams;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.NoneNestedConditions;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.embedded.NettyWebServerFactoryCustomizer;
import org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration;
import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory;
import org.springframework.cloud.gateway.actuate.GatewayControllerEndpoint;
import org.springframework.cloud.gateway.actuate.GatewayLegacyControllerEndpoint;
import org.springframework.cloud.gateway.filter.AdaptCachedBodyGlobalFilter;
import org.springframework.cloud.gateway.filter.ForwardPathFilter;
import org.springframework.cloud.gateway.filter.ForwardRoutingFilter;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.NettyRoutingFilter;
import org.springframework.cloud.gateway.filter.NettyWriteResponseFilter;
import org.springframework.cloud.gateway.filter.RemoveCachedBodyFilter;
import org.springframework.cloud.gateway.filter.RouteToRequestUrlFilter;
import org.springframework.cloud.gateway.filter.WebsocketRoutingFilter;
import org.springframework.cloud.gateway.filter.WeightCalculatorWebFilter;
import org.springframework.cloud.gateway.filter.factory.AddRequestHeaderGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.AddRequestParameterGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.AddResponseHeaderGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.DedupeResponseHeaderGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.FallbackHeadersGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.GatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.HystrixGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.MapRequestHeaderGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.PrefixPathGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.PreserveHostHeaderGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.RedirectToGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.RemoveRequestHeaderGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.RemoveRequestParameterGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.RemoveResponseHeaderGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.RequestHeaderSizeGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.RequestHeaderToRequestUriGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.RequestRateLimiterGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.RequestSizeGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.RetryGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.RewriteLocationResponseHeaderGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.RewritePathGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.RewriteResponseHeaderGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.SaveSessionGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.SecureHeadersGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.SecureHeadersProperties;
import org.springframework.cloud.gateway.filter.factory.SetPathGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.SetRequestHeaderGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.SetResponseHeaderGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.SetStatusGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.StripPrefixGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.rewrite.ModifyRequestBodyGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.rewrite.ModifyResponseBodyGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.headers.ForwardedHeadersFilter;
import org.springframework.cloud.gateway.filter.headers.HttpHeadersFilter;
import org.springframework.cloud.gateway.filter.headers.RemoveHopByHopHeadersFilter;
import org.springframework.cloud.gateway.filter.headers.XForwardedHeadersFilter;
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.cloud.gateway.filter.ratelimit.PrincipalNameKeyResolver;
import org.springframework.cloud.gateway.filter.ratelimit.RateLimiter;
import org.springframework.cloud.gateway.handler.FilteringWebHandler;
import org.springframework.cloud.gateway.handler.RoutePredicateHandlerMapping;
import org.springframework.cloud.gateway.handler.predicate.AfterRoutePredicateFactory;
import org.springframework.cloud.gateway.handler.predicate.BeforeRoutePredicateFactory;
import org.springframework.cloud.gateway.handler.predicate.BetweenRoutePredicateFactory;
import org.springframework.cloud.gateway.handler.predicate.CloudFoundryRouteServiceRoutePredicateFactory;
import org.springframework.cloud.gateway.handler.predicate.CookieRoutePredicateFactory;
import org.springframework.cloud.gateway.handler.predicate.HeaderRoutePredicateFactory;
import org.springframework.cloud.gateway.handler.predicate.HostRoutePredicateFactory;
import org.springframework.cloud.gateway.handler.predicate.MethodRoutePredicateFactory;
import org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory;
import org.springframework.cloud.gateway.handler.predicate.QueryRoutePredicateFactory;
import org.springframework.cloud.gateway.handler.predicate.ReadBodyPredicateFactory;
import org.springframework.cloud.gateway.handler.predicate.RemoteAddrRoutePredicateFactory;
import org.springframework.cloud.gateway.handler.predicate.RoutePredicateFactory;
import org.springframework.cloud.gateway.handler.predicate.WeightRoutePredicateFactory;
import org.springframework.cloud.gateway.route.CachingRouteLocator;
import org.springframework.cloud.gateway.route.CompositeRouteDefinitionLocator;
import org.springframework.cloud.gateway.route.CompositeRouteLocator;
import org.springframework.cloud.gateway.route.InMemoryRouteDefinitionRepository;
import org.springframework.cloud.gateway.route.RouteDefinitionLocator;
import org.springframework.cloud.gateway.route.RouteDefinitionRepository;
import org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.RouteRefreshListener;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.cloud.gateway.support.ConfigurationService;
import org.springframework.cloud.gateway.support.StringToZonedDateTimeConverter;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.Primary;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.env.Environment;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.util.StringUtils;
import org.springframework.validation.Validator;
import org.springframework.web.reactive.DispatcherHandler;
import org.springframework.web.reactive.socket.client.ReactorNettyWebSocketClient;
import org.springframework.web.reactive.socket.client.WebSocketClient;
import org.springframework.web.reactive.socket.server.WebSocketService;
import org.springframework.web.reactive.socket.server.support.HandshakeWebSocketService;

import static org.springframework.cloud.gateway.config.HttpClientProperties.Pool.PoolType.DISABLED;
import static org.springframework.cloud.gateway.config.HttpClientProperties.Pool.PoolType.FIXED;

/**
 * @author Spencer Gibb
 * @author Ziemowit Stolarczyk
 */
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(name = "spring.cloud.gateway.enabled", matchIfMissing = true)
@EnableConfigurationProperties
@AutoConfigureBefore({ HttpHandlerAutoConfiguration.class,
        WebFluxAutoConfiguration.class })
@AutoConfigureAfter({ GatewayLoadBalancerClientAutoConfiguration.class,
        GatewayClassPathWarningAutoConfiguration.class })
@ConditionalOnClass(DispatcherHandler.class)
public class GatewayAutoConfiguration {

    @Bean
    public StringToZonedDateTimeConverter stringToZonedDateTimeConverter() {
        return new StringToZonedDateTimeConverter();
    }

    @Bean
    public RouteLocatorBuilder routeLocatorBuilder(
            ConfigurableApplicationContext context) {
        return new RouteLocatorBuilder(context);
    }

    @Bean
    @ConditionalOnMissingBean
    public PropertiesRouteDefinitionLocator propertiesRouteDefinitionLocator(
            GatewayProperties properties) {
        return new PropertiesRouteDefinitionLocator(properties);
    }

    @Bean
    @ConditionalOnMissingBean(RouteDefinitionRepository.class)
    public InMemoryRouteDefinitionRepository inMemoryRouteDefinitionRepository() {
        return new InMemoryRouteDefinitionRepository();
    }

    @Bean
    @Primary
    public RouteDefinitionLocator routeDefinitionLocator(
            List<RouteDefinitionLocator> routeDefinitionLocators) {
        return new CompositeRouteDefinitionLocator(
                Flux.fromIterable(routeDefinitionLocators));
    }

    @Bean
    public ConfigurationService gatewayConfigurationService(BeanFactory beanFactory,
            @Qualifier("webFluxConversionService") ConversionService conversionService,
            Validator validator) {
        return new ConfigurationService(beanFactory, conversionService, validator);
    }

    @Bean
    public RouteLocator routeDefinitionRouteLocator(GatewayProperties properties,
            List<GatewayFilterFactory> gatewayFilters,
            List<RoutePredicateFactory> predicates,
            RouteDefinitionLocator routeDefinitionLocator,
            ConfigurationService configurationService) {
        return new RouteDefinitionRouteLocator(routeDefinitionLocator, predicates,
                gatewayFilters, properties, configurationService);
    }

    @Bean
    @Primary
    @ConditionalOnMissingBean(name = "cachedCompositeRouteLocator")
    // TODO: property to disable composite?
    public RouteLocator cachedCompositeRouteLocator(List<RouteLocator> routeLocators) {
        return new CachingRouteLocator(
                new CompositeRouteLocator(Flux.fromIterable(routeLocators)));
    }

    @Bean
    public RouteRefreshListener routeRefreshListener(
            ApplicationEventPublisher publisher) {
        return new RouteRefreshListener(publisher);
    }

    @Bean
    public FilteringWebHandler filteringWebHandler(List<GlobalFilter> globalFilters) {
        return new FilteringWebHandler(globalFilters);
    }

    @Bean
    public GlobalCorsProperties globalCorsProperties() {
        return new GlobalCorsProperties();
    }

    @Bean
    public RoutePredicateHandlerMapping routePredicateHandlerMapping(
            FilteringWebHandler webHandler, RouteLocator routeLocator,
            GlobalCorsProperties globalCorsProperties, Environment environment) {
        return new RoutePredicateHandlerMapping(webHandler, routeLocator,
                globalCorsProperties, environment);
    }

    @Bean
    public GatewayProperties gatewayProperties() {
        return new GatewayProperties();
    }

    // ConfigurationProperty beans

    @Bean
    public SecureHeadersProperties secureHeadersProperties() {
        return new SecureHeadersProperties();
    }

    @Bean
    @ConditionalOnProperty(name = "spring.cloud.gateway.forwarded.enabled",
            matchIfMissing = true)
    public ForwardedHeadersFilter forwardedHeadersFilter() {
        return new ForwardedHeadersFilter();
    }

    // HttpHeaderFilter beans

    @Bean
    public RemoveHopByHopHeadersFilter removeHopByHopHeadersFilter() {
        return new RemoveHopByHopHeadersFilter();
    }

    @Bean
    @ConditionalOnProperty(name = "spring.cloud.gateway.x-forwarded.enabled",
            matchIfMissing = true)
    public XForwardedHeadersFilter xForwardedHeadersFilter() {
        return new XForwardedHeadersFilter();
    }

    // GlobalFilter beans

    @Bean
    public AdaptCachedBodyGlobalFilter adaptCachedBodyGlobalFilter() {
        return new AdaptCachedBodyGlobalFilter();
    }

    @Bean
    public RemoveCachedBodyFilter removeCachedBodyFilter() {
        return new RemoveCachedBodyFilter();
    }

    @Bean
    public RouteToRequestUrlFilter routeToRequestUrlFilter() {
        return new RouteToRequestUrlFilter();
    }

    @Bean
    public ForwardRoutingFilter forwardRoutingFilter(
            ObjectProvider<DispatcherHandler> dispatcherHandler) {
        return new ForwardRoutingFilter(dispatcherHandler);
    }

    @Bean
    public ForwardPathFilter forwardPathFilter() {
        return new ForwardPathFilter();
    }

    @Bean
    public WebSocketService webSocketService() {
        return new HandshakeWebSocketService();
    }

    @Bean
    public WebsocketRoutingFilter websocketRoutingFilter(WebSocketClient webSocketClient,
            WebSocketService webSocketService,
            ObjectProvider<List<HttpHeadersFilter>> headersFilters) {
        return new WebsocketRoutingFilter(webSocketClient, webSocketService,
                headersFilters);
    }

    @Bean
    public WeightCalculatorWebFilter weightCalculatorWebFilter(
            ConfigurationService configurationService,
            ObjectProvider<RouteLocator> routeLocator) {
        return new WeightCalculatorWebFilter(routeLocator, configurationService);
    }

    @Bean
    public AfterRoutePredicateFactory afterRoutePredicateFactory() {
        return new AfterRoutePredicateFactory();
    }

    /*
     * @Bean //TODO: default over netty? configurable public WebClientHttpRoutingFilter
     * webClientHttpRoutingFilter() { //TODO: WebClient bean return new
     * WebClientHttpRoutingFilter(WebClient.routes().build()); }
     *
     * @Bean public WebClientWriteResponseFilter webClientWriteResponseFilter() { return
     * new WebClientWriteResponseFilter(); }
     */

    // Predicate Factory beans

    @Bean
    public BeforeRoutePredicateFactory beforeRoutePredicateFactory() {
        return new BeforeRoutePredicateFactory();
    }

    @Bean
    public BetweenRoutePredicateFactory betweenRoutePredicateFactory() {
        return new BetweenRoutePredicateFactory();
    }

    @Bean
    public CookieRoutePredicateFactory cookieRoutePredicateFactory() {
        return new CookieRoutePredicateFactory();
    }

    @Bean
    public HeaderRoutePredicateFactory headerRoutePredicateFactory() {
        return new HeaderRoutePredicateFactory();
    }

    @Bean
    public HostRoutePredicateFactory hostRoutePredicateFactory() {
        return new HostRoutePredicateFactory();
    }

    @Bean
    public MethodRoutePredicateFactory methodRoutePredicateFactory() {
        return new MethodRoutePredicateFactory();
    }

    @Bean
    public PathRoutePredicateFactory pathRoutePredicateFactory() {
        return new PathRoutePredicateFactory();
    }

    @Bean
    public QueryRoutePredicateFactory queryRoutePredicateFactory() {
        return new QueryRoutePredicateFactory();
    }

    @Bean
    public ReadBodyPredicateFactory readBodyPredicateFactory() {
        return new ReadBodyPredicateFactory();
    }

    @Bean
    public RemoteAddrRoutePredicateFactory remoteAddrRoutePredicateFactory() {
        return new RemoteAddrRoutePredicateFactory();
    }

    @Bean
    @DependsOn("weightCalculatorWebFilter")
    public WeightRoutePredicateFactory weightRoutePredicateFactory() {
        return new WeightRoutePredicateFactory();
    }

    @Bean
    public CloudFoundryRouteServiceRoutePredicateFactory cloudFoundryRouteServiceRoutePredicateFactory() {
        return new CloudFoundryRouteServiceRoutePredicateFactory();
    }

    // GatewayFilter Factory beans

    @Bean
    public AddRequestHeaderGatewayFilterFactory addRequestHeaderGatewayFilterFactory() {
        return new AddRequestHeaderGatewayFilterFactory();
    }

    @Bean
    public MapRequestHeaderGatewayFilterFactory mapRequestHeaderGatewayFilterFactory() {
        return new MapRequestHeaderGatewayFilterFactory();
    }

    @Bean
    public AddRequestParameterGatewayFilterFactory addRequestParameterGatewayFilterFactory() {
        return new AddRequestParameterGatewayFilterFactory();
    }

    @Bean
    public AddResponseHeaderGatewayFilterFactory addResponseHeaderGatewayFilterFactory() {
        return new AddResponseHeaderGatewayFilterFactory();
    }

    @Bean
    public ModifyRequestBodyGatewayFilterFactory modifyRequestBodyGatewayFilterFactory() {
        return new ModifyRequestBodyGatewayFilterFactory();
    }

    @Bean
    public DedupeResponseHeaderGatewayFilterFactory dedupeResponseHeaderGatewayFilterFactory() {
        return new DedupeResponseHeaderGatewayFilterFactory();
    }

    @Bean
    public ModifyResponseBodyGatewayFilterFactory modifyResponseBodyGatewayFilterFactory(
            ServerCodecConfigurer codecConfigurer) {
        return new ModifyResponseBodyGatewayFilterFactory(codecConfigurer);
    }

    @Bean
    public PrefixPathGatewayFilterFactory prefixPathGatewayFilterFactory() {
        return new PrefixPathGatewayFilterFactory();
    }

    @Bean
    public PreserveHostHeaderGatewayFilterFactory preserveHostHeaderGatewayFilterFactory() {
        return new PreserveHostHeaderGatewayFilterFactory();
    }

    @Bean
    public RedirectToGatewayFilterFactory redirectToGatewayFilterFactory() {
        return new RedirectToGatewayFilterFactory();
    }

    @Bean
    public RemoveRequestHeaderGatewayFilterFactory removeRequestHeaderGatewayFilterFactory() {
        return new RemoveRequestHeaderGatewayFilterFactory();
    }

    @Bean
    public RemoveRequestParameterGatewayFilterFactory removeRequestParameterGatewayFilterFactory() {
        return new RemoveRequestParameterGatewayFilterFactory();
    }

    @Bean
    public RemoveResponseHeaderGatewayFilterFactory removeResponseHeaderGatewayFilterFactory() {
        return new RemoveResponseHeaderGatewayFilterFactory();
    }

    @Bean(name = PrincipalNameKeyResolver.BEAN_NAME)
    @ConditionalOnBean(RateLimiter.class)
    @ConditionalOnMissingBean(KeyResolver.class)
    public PrincipalNameKeyResolver principalNameKeyResolver() {
        return new PrincipalNameKeyResolver();
    }

    @Bean
    @ConditionalOnBean({ RateLimiter.class, KeyResolver.class })
    public RequestRateLimiterGatewayFilterFactory requestRateLimiterGatewayFilterFactory(
            RateLimiter rateLimiter, KeyResolver resolver) {
        return new RequestRateLimiterGatewayFilterFactory(rateLimiter, resolver);
    }

    @Bean
    public RewritePathGatewayFilterFactory rewritePathGatewayFilterFactory() {
        return new RewritePathGatewayFilterFactory();
    }

    @Bean
    public RetryGatewayFilterFactory retryGatewayFilterFactory() {
        return new RetryGatewayFilterFactory();
    }

    @Bean
    public SetPathGatewayFilterFactory setPathGatewayFilterFactory() {
        return new SetPathGatewayFilterFactory();
    }

    @Bean
    public SecureHeadersGatewayFilterFactory secureHeadersGatewayFilterFactory(
            SecureHeadersProperties properties) {
        return new SecureHeadersGatewayFilterFactory(properties);
    }

    @Bean
    public SetRequestHeaderGatewayFilterFactory setRequestHeaderGatewayFilterFactory() {
        return new SetRequestHeaderGatewayFilterFactory();
    }

    @Bean
    public SetResponseHeaderGatewayFilterFactory setResponseHeaderGatewayFilterFactory() {
        return new SetResponseHeaderGatewayFilterFactory();
    }

    @Bean
    public RewriteResponseHeaderGatewayFilterFactory rewriteResponseHeaderGatewayFilterFactory() {
        return new RewriteResponseHeaderGatewayFilterFactory();
    }

    @Bean
    public RewriteLocationResponseHeaderGatewayFilterFactory rewriteLocationResponseHeaderGatewayFilterFactory() {
        return new RewriteLocationResponseHeaderGatewayFilterFactory();
    }

    @Bean
    public SetStatusGatewayFilterFactory setStatusGatewayFilterFactory() {
        return new SetStatusGatewayFilterFactory();
    }

    @Bean
    public SaveSessionGatewayFilterFactory saveSessionGatewayFilterFactory() {
        return new SaveSessionGatewayFilterFactory();
    }

    @Bean
    public StripPrefixGatewayFilterFactory stripPrefixGatewayFilterFactory() {
        return new StripPrefixGatewayFilterFactory();
    }

    @Bean
    public RequestHeaderToRequestUriGatewayFilterFactory requestHeaderToRequestUriGatewayFilterFactory() {
        return new RequestHeaderToRequestUriGatewayFilterFactory();
    }

    @Bean
    public RequestSizeGatewayFilterFactory requestSizeGatewayFilterFactory() {
        return new RequestSizeGatewayFilterFactory();
    }

    @Bean
    public RequestHeaderSizeGatewayFilterFactory requestHeaderSizeGatewayFilterFactory() {
        return new RequestHeaderSizeGatewayFilterFactory();
    }

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass(HttpClient.class)
    protected static class NettyConfiguration {

        protected final Log logger = LogFactory.getLog(getClass());

        @Bean
        @ConditionalOnProperty(name = "spring.cloud.gateway.httpserver.wiretap")
        public NettyWebServerFactoryCustomizer nettyServerWiretapCustomizer(
                Environment environment, ServerProperties serverProperties) {
            return new NettyWebServerFactoryCustomizer(environment, serverProperties) {
                @Override
                public void customize(NettyReactiveWebServerFactory factory) {
                    factory.addServerCustomizers(httpServer -> httpServer.wiretap(true));
                    super.customize(factory);
                }
            };
        }

        @Bean
        @ConditionalOnMissingBean
        public HttpClient gatewayHttpClient(HttpClientProperties properties) {

            // configure pool resources
            HttpClientProperties.Pool pool = properties.getPool();

            ConnectionProvider connectionProvider;
            if (pool.getType() == DISABLED) {
                connectionProvider = ConnectionProvider.newConnection();
            }
            else if (pool.getType() == FIXED) {
                connectionProvider = ConnectionProvider.fixed(pool.getName(),
                        pool.getMaxConnections(), pool.getAcquireTimeout(),
                        pool.getMaxIdleTime());
            }
            else {
                connectionProvider = ConnectionProvider.elastic(pool.getName(),
                        pool.getMaxIdleTime());
            }

            HttpClient httpClient = HttpClient.create(connectionProvider)
                    .tcpConfiguration(tcpClient -> {

                        if (properties.getConnectTimeout() != null) {
                            tcpClient = tcpClient.option(
                                    ChannelOption.CONNECT_TIMEOUT_MILLIS,
                                    properties.getConnectTimeout());
                        }

                        // configure proxy if proxy host is set.
                        HttpClientProperties.Proxy proxy = properties.getProxy();

                        if (StringUtils.hasText(proxy.getHost())) {

                            tcpClient = tcpClient.proxy(proxySpec -> {
                                ProxyProvider.Builder builder = proxySpec
                                        .type(ProxyProvider.Proxy.HTTP)
                                        .host(proxy.getHost());

                                PropertyMapper map = PropertyMapper.get();

                                map.from(proxy::getPort).whenNonNull().to(builder::port);
                                map.from(proxy::getUsername).whenHasText()
                                        .to(builder::username);
                                map.from(proxy::getPassword).whenHasText()
                                        .to(password -> builder.password(s -> password));
                                map.from(proxy::getNonProxyHostsPattern).whenHasText()
                                        .to(builder::nonProxyHosts);
                            });
                        }
                        return tcpClient;
                    });

            HttpClientProperties.Ssl ssl = properties.getSsl();
            if ((ssl.getKeyStore() != null && ssl.getKeyStore().length() > 0)
                    || ssl.getTrustedX509CertificatesForTrustManager().length > 0
                    || ssl.isUseInsecureTrustManager()) {
                httpClient = httpClient.secure(sslContextSpec -> {
                    // configure ssl
                    SslContextBuilder sslContextBuilder = SslContextBuilder.forClient();

                    X509Certificate[] trustedX509Certificates = ssl
                            .getTrustedX509CertificatesForTrustManager();
                    if (trustedX509Certificates.length > 0) {
                        sslContextBuilder = sslContextBuilder
                                .trustManager(trustedX509Certificates);
                    }
                    else if (ssl.isUseInsecureTrustManager()) {
                        sslContextBuilder = sslContextBuilder
                                .trustManager(InsecureTrustManagerFactory.INSTANCE);
                    }

                    try {
                        sslContextBuilder = sslContextBuilder
                                .keyManager(ssl.getKeyManagerFactory());
                    }
                    catch (Exception e) {
                        logger.error(e);
                    }

                    sslContextSpec.sslContext(sslContextBuilder)
                            .defaultConfiguration(ssl.getDefaultConfigurationType())
                            .handshakeTimeout(ssl.getHandshakeTimeout())
                            .closeNotifyFlushTimeout(ssl.getCloseNotifyFlushTimeout())
                            .closeNotifyReadTimeout(ssl.getCloseNotifyReadTimeout());
                });
            }

            if (properties.isWiretap()) {
                httpClient = httpClient.wiretap(true);
            }

            return httpClient;
        }

        @Bean
        public HttpClientProperties httpClientProperties() {
            return new HttpClientProperties();
        }

        @Bean
        public NettyRoutingFilter routingFilter(HttpClient httpClient,
                ObjectProvider<List<HttpHeadersFilter>> headersFilters,
                HttpClientProperties properties) {
            return new NettyRoutingFilter(httpClient, headersFilters, properties);
        }

        @Bean
        public NettyWriteResponseFilter nettyWriteResponseFilter(
                GatewayProperties properties) {
            return new NettyWriteResponseFilter(properties.getStreamingMediaTypes());
        }

        @Bean
        public ReactorNettyWebSocketClient reactorNettyWebSocketClient(
                HttpClientProperties properties, HttpClient httpClient) {
            ReactorNettyWebSocketClient webSocketClient = new ReactorNettyWebSocketClient(
                    httpClient);
            if (properties.getWebsocket().getMaxFramePayloadLength() != null) {
                webSocketClient.setMaxFramePayloadLength(
                        properties.getWebsocket().getMaxFramePayloadLength());
            }
            return webSocketClient;
        }

    }

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass({ HystrixObservableCommand.class, RxReactiveStreams.class })
    protected static class HystrixConfiguration {

        @Bean
        public HystrixGatewayFilterFactory hystrixGatewayFilterFactory(
                ObjectProvider<DispatcherHandler> dispatcherHandler) {
            return new HystrixGatewayFilterFactory(dispatcherHandler);
        }

        @Bean
        @ConditionalOnMissingBean(FallbackHeadersGatewayFilterFactory.class)
        public FallbackHeadersGatewayFilterFactory fallbackHeadersGatewayFilterFactory() {
            return new FallbackHeadersGatewayFilterFactory();
        }

    }

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass(Health.class)
    protected static class GatewayActuatorConfiguration {

        @Bean
        @ConditionalOnProperty(name = "spring.cloud.gateway.actuator.verbose.enabled",
                matchIfMissing = true)
        @ConditionalOnAvailableEndpoint
        public GatewayControllerEndpoint gatewayControllerEndpoint(
                List<GlobalFilter> globalFilters,
                List<GatewayFilterFactory> gatewayFilters,
                List<RoutePredicateFactory> routePredicates,
                RouteDefinitionWriter routeDefinitionWriter, RouteLocator routeLocator) {
            return new GatewayControllerEndpoint(globalFilters, gatewayFilters,
                    routePredicates, routeDefinitionWriter, routeLocator);
        }

        @Bean
        @Conditional(OnVerboseDisabledCondition.class)
        @ConditionalOnAvailableEndpoint
        public GatewayLegacyControllerEndpoint gatewayLegacyControllerEndpoint(
                RouteDefinitionLocator routeDefinitionLocator,
                List<GlobalFilter> globalFilters,
                List<GatewayFilterFactory> gatewayFilters,
                List<RoutePredicateFactory> routePredicates,
                RouteDefinitionWriter routeDefinitionWriter, RouteLocator routeLocator) {
            return new GatewayLegacyControllerEndpoint(routeDefinitionLocator,
                    globalFilters, gatewayFilters, routePredicates, routeDefinitionWriter,
                    routeLocator);
        }

    }

    private static class OnVerboseDisabledCondition extends NoneNestedConditions {

        OnVerboseDisabledCondition() {
            super(ConfigurationPhase.REGISTER_BEAN);
        }

        @ConditionalOnProperty(name = "spring.cloud.gateway.actuator.verbose.enabled",
                matchIfMissing = true)
        static class VerboseDisabled {

        }

    }

}
View Code

  也可以看出在构造 FilteringWebHandler 的时候,依赖Spring 自动注入globalFilters,然后org.springframework.cloud.gateway.handler.FilteringWebHandler#FilteringWebHandler 构造方法中,将filters 进行排序维护到内部:

    public FilteringWebHandler(List<GlobalFilter> globalFilters) {
        this.globalFilters = loadFilters(globalFilters);
    }

    private static List<GatewayFilter> loadFilters(List<GlobalFilter> filters) {
        return filters.stream().map(filter -> {
            GatewayFilterAdapter gatewayFilter = new GatewayFilterAdapter(filter);
            if (filter instanceof Ordered) {
                int order = ((Ordered) filter).getOrder();
                return new OrderedGatewayFilter(gatewayFilter, order);
            }
            return gatewayFilter;
        }).collect(Collectors.toList());
    }

 

org.springframework.cloud.gateway.config.GatewayProperties 配置类源码如下:(yml 配置的 routes 直接注入到该对象的list 属性中, 这里可以看出也可以配置默认的过滤器, 相当于全局过滤器, 会应用到每个路由中)

/*
 * Copyright 2013-2019 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.cloud.gateway.config;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import javax.validation.Valid;
import javax.validation.constraints.NotNull;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.gateway.filter.FilterDefinition;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.http.MediaType;
import org.springframework.validation.annotation.Validated;

/**
 * @author Spencer Gibb
 */
@ConfigurationProperties("spring.cloud.gateway")
@Validated
public class GatewayProperties {

    private final Log logger = LogFactory.getLog(getClass());

    /**
     * List of Routes.
     */
    @NotNull
    @Valid
    private List<RouteDefinition> routes = new ArrayList<>();

    /**
     * List of filter definitions that are applied to every route.
     */
    private List<FilterDefinition> defaultFilters = new ArrayList<>();

    private List<MediaType> streamingMediaTypes = Arrays
            .asList(MediaType.TEXT_EVENT_STREAM, MediaType.APPLICATION_STREAM_JSON);

    public List<RouteDefinition> getRoutes() {
        return routes;
    }

    public void setRoutes(List<RouteDefinition> routes) {
        this.routes = routes;
        if (routes != null && routes.size() > 0 && logger.isDebugEnabled()) {
            logger.debug("Routes supplied from Gateway Properties: " + routes);
        }
    }

    public List<FilterDefinition> getDefaultFilters() {
        return defaultFilters;
    }

    public void setDefaultFilters(List<FilterDefinition> defaultFilters) {
        this.defaultFilters = defaultFilters;
    }

    public List<MediaType> getStreamingMediaTypes() {
        return streamingMediaTypes;
    }

    public void setStreamingMediaTypes(List<MediaType> streamingMediaTypes) {
        this.streamingMediaTypes = streamingMediaTypes;
    }

    @Override
    public String toString() {
        return "GatewayProperties{" + "routes=" + routes + ", defaultFilters="
                + defaultFilters + ", streamingMediaTypes=" + streamingMediaTypes + '}';
    }

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

posted @ 2021-11-24 23:08  QiaoZhi  阅读(3803)  评论(0编辑  收藏  举报