Spring Cloud Gateway series processing request process

Keywords: Programming Spring Netty SSL WebFlux

This article mainly reveals the loading of Spring Cloud Gateway and how to handle the request process from the source point of view.

1. Overview of spring gateway

Spring Cloud Gateway is an official gateway developed by spring based on Spring 5.0, Spring Boot 2.0, Project Reactor and other technologies. Spring Cloud Gateway aims to provide a simple and effective unified API route management method for microservice architecture. As a gateway in the Spring Cloud ecosystem, Spring Cloud Gateway aims to replace Netflix ZUUL, which not only provides a unified routing mode, but also provides the basic functions of the gateway based on the Filter chain, such as: security, monitoring / burying point, and current limiting.

2. Simple loading analysis of container startup process

The process of loading is mainly spring containers. You need to be familiar with the spring framework. There is not much analysis here.
Under the org.springframework.cloud.gateway.config package, we can see four configuration classes:
GatewayAutoConfiguration
GatewayClassPathWarningAutoConfiguration
GatewayLoadBalancerClientAutoConfiguration
GatewayRedisAutoConfiguration
Their initialization sequence is as follows:

2.1 GatewayClassPathWarningAutoConfiguration

Spring Cloud Gateway 2.x is implemented based on Spring WebFlux.

org.springframework.cloud.gateway.config.GatewayClassPathWarningAutoConfiguration is used to check whether the project has correctly imported the spring boot starter weblux dependency, rather than the spring boot starter web dependency. Click to view source code

2.2 GatewayLoadBalancerClientAutoConfiguration

org.springframework.cloud.gateway.config.GatewayLoadBalancerClientAutoConfiguration, initialize LoadBalancerClientFilter, click View source code

2.3 GatewayRedisAutoConfiguration

org.springframework.cloud.gateway.config.GatewayRedisAutoConfiguration to initialize RedisRateLimiter.
RequestRateLimiterGatewayFilterFactory implements the current limiting function of gateway based on RedisRateLimiter

2.4 GatewayAutoConfiguration

org.springframework.cloud.gateway.config.GatewayAutoConfiguration, Spring Cloud Gateway core configuration class, initialization as follows:
NettyConfiguration
GlobalFilter
FilteringWebHandler
GatewayProperties
PrefixPathGatewayFilterFactory
RoutePredicateFactory
RouteDefinitionLocator
RouteLocator
RoutePredicateHandlerMapping
GatewayControllerEndpoint

The component relationship interaction is as follows:

2.5 opening and closing of gateway

From the annotation @ ConditionalOnProperty(name = "spring.cloud.gateway.enabled", matchIfMissing = true) on GatewayAutoConfiguration, we can see that:

Configure the opening and closing of the gateway through spring.cloud.gateway.enabled.
Matchifmissing = true = > gateway is on by default.

2.6 initialize NettyConfiguration

org.springframework.cloud.gateway.config.NettyConfiguration, Netty configuration class. The code is as follows:

 @Configuration
    @ConditionalOnClass({HttpClient.class})
    protected static class NettyConfiguration {
        protected NettyConfiguration() {
        }

        @Bean
        @ConditionalOnMissingBean
        public HttpClient httpClient(@Qualifier("nettyClientOptions") Consumer<? super Builder> options) {
            return HttpClient.create(options);
        }

        @Bean
        public Consumer<? super Builder> nettyClientOptions(HttpClientProperties properties) {
            return (opts) -> {
                Ssl ssl = properties.getSsl();
                if (ssl.isUseInsecureTrustManager()) {
                    opts.sslSupport((sslContextBuilder) -> {
                        sslContextBuilder.trustManager(InsecureTrustManagerFactory.INSTANCE);
                    });
                }

                Pool pool = properties.getPool();
                PoolResources poolResources;
                if (pool.getType() == PoolType.FIXED) {
                    poolResources = PoolResources.fixed(pool.getName(), pool.getMaxConnections(), pool.getAcquireTimeout());
                } else {
                    poolResources = PoolResources.elastic(pool.getName());
                }

                opts.poolResources(poolResources);
                Proxy proxy = properties.getProxy();
                if (StringUtils.hasText(proxy.getHost())) {
                    opts.proxy((typeSpec) -> {
                        reactor.ipc.netty.options.ClientProxyOptions.Builder builder = typeSpec.type(reactor.ipc.netty.options.ClientProxyOptions.Proxy.HTTP).host(proxy.getHost());
                        PropertyMapper map = PropertyMapper.get();
                        proxy.getClass();
                        map.from(proxy::getPort).whenNonNull().to(builder::port);
                        proxy.getClass();
                        map.from(proxy::getUsername).whenHasText().to(builder::username);
                        proxy.getClass();
                        map.from(proxy::getPassword).whenHasText().to((password) -> {
                            builder.password((s) -> {
                                return password;
                            });
                        });
                        proxy.getClass();
                        map.from(proxy::getNonProxyHostsPattern).whenHasText().to(builder::nonProxyHosts);
                        return builder;
                    });
                }

            };
        }

2.7 initialize GlobalFilter

The code is as follows:

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

    @Bean
    @ConditionalOnBean({DispatcherHandler.class})
    public ForwardRoutingFilter forwardRoutingFilter(DispatcherHandler dispatcherHandler) {
        return new ForwardRoutingFilter(dispatcherHandler);
    }

    @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);
    }

2.8 initialize filteringwehandler

When all org.springframework.cloud.gateway.filter.GlobalFilter initialization is completed (including the above NettyRoutingFilter / NettyWriteResponseFilter), create a Bean object of type org.springframework.cloud.gateway.handler.filteringwehandler. The code is as follows:

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

2.9 initialize GatewayProperties

Create a Bean object of type org.springframework.cloud.gateway.config.GatewayProperties to load the RouteDefinition / FilterDefinition of the configuration file configuration. The code is as follows:

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

3.0 initialize PrefixPathGatewayFilterFactory

In link Create the implementers of the org.springframework.cloud.gateway.filter.factory.GatewayFilterFactory interface under the org.springframework.cloud.gateway.filter.factory package.

3.1 initialize RoutePredicateFactory

In Links Create the implementers of the org.springframework.cloud.gateway.handler.predict.routepredictefactory interface under the org.springframework.cloud.gateway.handler.predict package.

3.2 initialize RouteDefinitionLocator

The code is as follows:

@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));
    }

3.3 initialize RouteLocator

The code is as follows:

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

    @Bean
    @Primary
    public RouteLocator cachedCompositeRouteLocator(List<RouteLocator> routeLocators) {
        return new CachingRouteLocator(new CompositeRouteLocator(Flux.fromIterable(routeLocators)));
    }

In addition, there are two ways to implement a custom RouteLocator:

Use the routes ා locator() ා build() method to create a RouteLocator. The example code is as follows:

@Bean
public RouteLocator customRouteLocator() {
    //@formatter:off
    return Routes.locator()
            .route("test")
                .predicate(host("**.abc.org").and(path("/image/png")))
                .addResponseHeader("X-TestHeader", "foobar")
                .uri("http://httpbin.org:80")
            .route("test2")
                .predicate(path("/image/webp"))
                .add(addResponseHeader("X-AnotherHeader", "baz"))
                .uri("http://httpbin.org:80")
            .build();
    ////@formatter:on
}

Use routelocatordsl ා gateway() method to create RouteLocator, which is implemented by Kotlin. The example code is as follows:

@Configuration
class AdditionalRoutes {
    @Bean
    fun additionalRouteLocator(): RouteLocator = gateway {
        route(id = "test-kotlin") {
            uri("http://httpbin.org:80")
            predicate(host("kotlin.abc.org") and path("/image/png"))
            add(addResponseHeader("X-TestHeader", "foobar"))
        }
    }
}

3.4 initialize RoutePredicateHandlerMapping

Create a Bean object of type org.springframework.cloud.gateway.handler.RoutePredicateHandlerMapping, which is used to find and match routes, and process it. The code is as follows:

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

3.5 initialize GatewayWebfluxEndpoint

Create a Bean object of type org.springframework.cloud.gateway.actuate.GatewayControllerEndpoint to provide the HTTP API for managing gateways. The code is as follows:

 @Configuration
    @ConditionalOnClass({Health.class})
    protected static class GatewayActuatorConfiguration {
        protected GatewayActuatorConfiguration() {
        }

        @Bean
        @ConditionalOnEnabledEndpoint
        public GatewayControllerEndpoint gatewayControllerEndpoint(RouteDefinitionLocator routeDefinitionLocator, List<GlobalFilter> globalFilters, List<GatewayFilterFactory> GatewayFilters, RouteDefinitionWriter routeDefinitionWriter, RouteLocator routeLocator) {
            return new GatewayControllerEndpoint(routeDefinitionLocator, globalFilters, GatewayFilters, routeDefinitionWriter, routeLocator);
        }
    }

4. Spring Cloud gateway request entry analysis

No matter Zuul, spring cloud gateway or self-developed gateway based on Netty, the Request or the returned Response will be packaged, transformed and extracted as the context information of the gateway operation, while in spring cloud gateway, the context of the gateway is ServerWebExchange.

4.1 import HttpServerRequest and HttpServerResponse conversion

Request entry of Spring Cloud Gateway, org.springframework.http.server.reactive.reactorhttphandleradapter ා apply method

The code comes from spring-web-5.0.4.RELEASE.jar. This method is the request entry method of Spring Cloud Gateway. The function of this method is to convert the received HttpServerRequest or the final HttpServerResponse to reactor server httprequest and reactor server httpresponse.

4.2 construct the context ServerWebExchange of Spring Cloud gateway

In line 91 of org.springframework.web.server.adapter.HttpWebHandlerAdapter, the code is as follows:

createExchange() builds the gateway context ServerWebExchange from ServerHttpRequest ServerHttpResponse.

PS:org.springframework.web.server.handler.WebHandlerDecorator.getDelegate() obtains a series of webhandlers to be processed through delegation

4.3 enter the Filter chain

Org.springframework.cloud.gateway.handler.filteringwebhandler ා handle method, i.e. line 46, the code is as follows:

4.4 execute Filter chain

4.5 Gateway Filter is delegated to Gloable Filter

Posted by Michael Lasky on Thu, 23 Apr 2020 00:29:58 -0700