[case22] Talk about NettyConfiguration of spring cloud gateway

  springcloud

Order

This paper mainly studies NettyConfiguration of spring cloud gateway.

NettyConfiguration

@Configuration
@ConditionalOnProperty(name = "spring.cloud.gateway.enabled", matchIfMissing = true)
@EnableConfigurationProperties
@AutoConfigureBefore(HttpHandlerAutoConfiguration.class)
@AutoConfigureAfter({GatewayLoadBalancerClientAutoConfiguration.class, GatewayClassPathWarningAutoConfiguration.class})
@ConditionalOnClass(DispatcherHandler.class)
public class GatewayAutoConfiguration {

    @Configuration
    @ConditionalOnClass(HttpClient.class)
    protected static class NettyConfiguration {
        @Bean
        @ConditionalOnMissingBean
        public HttpClient httpClient(@Qualifier("nettyClientOptions") Consumer<? super HttpClientOptions.Builder> options) {
            return HttpClient.create(options);
        }

        @Bean
        public Consumer<? super HttpClientOptions.Builder> nettyClientOptions(HttpClientProperties properties) {
            return opts -> {

                // configure ssl
                HttpClientProperties.Ssl ssl = properties.getSsl();

                if (ssl.isUseInsecureTrustManager()) {
                    opts.sslSupport(sslContextBuilder -> {
                        sslContextBuilder.trustManager(InsecureTrustManagerFactory.INSTANCE);
                    });
                }

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

                if (pool.getType() == DISABLED) {
                    opts.disablePool();
                } else if (pool.getType() == FIXED) {
                    PoolResources poolResources = PoolResources.fixed(pool.getName(),
                            pool.getMaxConnections(), pool.getAcquireTimeout());
                    opts.poolResources(poolResources);
                } else {
                    PoolResources poolResources = PoolResources.elastic(pool.getName());
                    opts.poolResources(poolResources);
                }


                // configure proxy if proxy host is set.
                HttpClientProperties.Proxy proxy = properties.getProxy();
                if (StringUtils.hasText(proxy.getHost())) {
                    opts.proxy(typeSpec -> {
                        ClientProxyOptions.Builder builder = typeSpec
                                .type(ClientProxyOptions.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 builder;
                    });
                }
            };
        }

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

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

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

        @Bean
        public ReactorNettyWebSocketClient reactorNettyWebSocketClient(@Qualifier("nettyClientOptions") Consumer<? super HttpClientOptions.Builder> options) {
            return new ReactorNettyWebSocketClient(options);
        }
    }
    //......
}

Spring cloud gateway uses reactor’s httpclient, which constructs options through nettyClientOptions bean. the specific configuration is HttpClientProperties

HttpClientProperties

Configuration description

   {
      "sourceType": "org.springframework.cloud.gateway.config.HttpClientProperties",
      "name": "spring.cloud.gateway.httpclient",
      "type": "org.springframework.cloud.gateway.config.HttpClientProperties"
    },
    {
      "sourceType": "org.springframework.cloud.gateway.config.HttpClientProperties",
      "name": "spring.cloud.gateway.httpclient.pool",
      "sourceMethod": "getPool()",
      "type": "org.springframework.cloud.gateway.config.HttpClientProperties$Pool"
    },
    {
      "sourceType": "org.springframework.cloud.gateway.config.HttpClientProperties",
      "name": "spring.cloud.gateway.httpclient.proxy",
      "sourceMethod": "getProxy()",
      "type": "org.springframework.cloud.gateway.config.HttpClientProperties$Proxy"
    },
    {
      "sourceType": "org.springframework.cloud.gateway.config.HttpClientProperties",
      "name": "spring.cloud.gateway.httpclient.ssl",
      "sourceMethod": "getSsl()",
      "type": "org.springframework.cloud.gateway.config.HttpClientProperties$Ssl"
    }

You can see that the main configurations are pool, proxy and ssl

Configuration class

spring-cloud-gateway-core-2.0.0.RC1-sources.jar! /org/springframework/cloud/gateway/config/HttpClientProperties.java

@ConfigurationProperties("spring.cloud.gateway.httpclient")
public class HttpClientProperties {

    /** Pool configuration for Netty HttpClient */
    private Pool pool = new Pool();

    /** Proxy configuration for Netty HttpClient */
    private Proxy proxy = new Proxy();

    /** SSL configuration for Netty HttpClient */
    private Ssl ssl = new Ssl();

    //......

    @Override
    public String toString() {
        return "HttpClientProperties{" +
                "pool=" + pool +
                ", proxy=" + proxy +
                '}';
    }
}

Pool

    public static class Pool {

        public enum PoolType { ELASTIC, FIXED, DISABLED }

        /** Type of pool for HttpClient to use, defaults to ELASTIC. */
        private PoolType type = PoolType.ELASTIC;

        /** The channel pool map name, defaults to proxy. */
        private String name = "proxy";

        /** Only for type FIXED, the maximum number of connections before starting pending acquisition on existing ones. */
        private Integer maxConnections = PoolResources.DEFAULT_POOL_MAX_CONNECTION;

        /** Only for type FIXED, the maximum time in millis to wait for aquiring. */
        private Long acquireTimeout = PoolResources.DEFAULT_POOL_ACQUIRE_TIMEOUT;

        //......

        @Override
        public String toString() {
            return "Pool{" +
                    "type=" + type +
                    ", name='" + name + '\'' +
                    ", maxConnections=" + maxConnections +
                    ", acquireTimeout=" + acquireTimeout +
                    '}';
        }
    }

A total of the following attributes can be specified

  • Spring.cloud.gateway.http client.pool.type, default is ELASTIC.
  • Spring.cloud.gateway.http client.pool.name, the default is proxy.

If type is a fixed type, you can also specify the following two parameters

  • The default is poolresources.default _ pool _ max _ connection.
    /**
     * Default max connection, if -1 will never wait to acquire before opening new
     * connection in an unbounded fashion. Fallback to
     * available number of processors.
     */
    int DEFAULT_POOL_MAX_CONNECTION =
            Integer.parseInt(System.getProperty("reactor.ipc.netty.pool.maxConnections",
            "" + Math.max(Runtime.getRuntime()
                        .availableProcessors(), 8) * 2));
  • Spring.cloud.gateway.http client.pool.acquiretimeout, default _ pool _ acquiretimeout
    /**
     * Default acquisition timeout before error. If -1 will never wait to
     * acquire before opening new
     * connection in an unbounded fashion. Fallback to
     * available number of processors.
     */
    long DEFAULT_POOL_ACQUIRE_TIMEOUT = Long.parseLong(System.getProperty(
            "reactor.ipc.netty.pool.acquireTimeout",
            "" + 45000));

Proxy

    public class Proxy {
        /** Hostname for proxy configuration of Netty HttpClient. */
        private String host;
        /** Port for proxy configuration of Netty HttpClient. */
        private Integer port;
        /** Username for proxy configuration of Netty HttpClient. */
        private String username;
        /** Password for proxy configuration of Netty HttpClient. */
        private String password;
        /** Regular expression (Java) for a configured list of hosts
         * that should be reached directly, bypassing the proxy */
        private String nonProxyHostsPattern;

        //......

        @Override
        public String toString() {
            return "Proxy{" +
                    "host='" + host + '\'' +
                    ", port=" + port +
                    ", username='" + username + '\'' +
                    ", password='" + password + '\'' +
                    ", nonProxyHostsPattern='" + nonProxyHostsPattern + '\'' +
                    '}';
        }
    }

The following parameters can be configured

  • spring.cloud.gateway.httpclient.proxy.host
  • spring.cloud.gateway.httpclient.proxy.port
  • spring.cloud.gateway.httpclient.proxy.username
  • spring.cloud.gateway.httpclient.proxy.password
  • spring.cloud.gateway.httpclient.proxy.nonProxyHostsPattern

Ssl

    public class Ssl {
        /** Installs the netty InsecureTrustManagerFactory. This is insecure and not suitable for production. */
        private boolean useInsecureTrustManager = false;

        //TODO: support configuration of other trust manager factories

        public boolean isUseInsecureTrustManager() {
            return useInsecureTrustManager;
        }

        public void setUseInsecureTrustManager(boolean useInsecureTrustManager) {
            this.useInsecureTrustManager = useInsecureTrustManager;
        }

        @Override
        public String toString() {
            return "Ssl{" +
                    "useInsecureTrustManager=" + useInsecureTrustManager +
                    '}';
        }
    }

It is mainly to configure the Spring.cloud.gateway.http client.ssl.use-insurance-trust-manager attribute. If it is set to true, the INSecure Trustmanagerfactory.instance will be used.

netty-handler-4.1.23.Final-sources.jar! /io/netty/handler/ssl/util/InsecureTrustManagerFactory.java

/**
 * An insecure {@link TrustManagerFactory} that trusts all X.509 certificates without any verification.
 * <p>
 * <strong>NOTE:</strong>
 * Never use this {@link TrustManagerFactory} in production.
 * It is purely for testing purposes, and thus it is very insecure.
 * </p>
 */
public final class InsecureTrustManagerFactory extends SimpleTrustManagerFactory {

    private static final InternalLogger logger = InternalLoggerFactory.getInstance(InsecureTrustManagerFactory.class);

    public static final TrustManagerFactory INSTANCE = new InsecureTrustManagerFactory();

    private static final TrustManager tm = new X509TrustManager() {
        @Override
        public void checkClientTrusted(X509Certificate[] chain, String s) {
            logger.debug("Accepting a client certificate: " + chain[0].getSubjectDN());
        }

        @Override
        public void checkServerTrusted(X509Certificate[] chain, String s) {
            logger.debug("Accepting a server certificate: " + chain[0].getSubjectDN());
        }

        @Override
        public X509Certificate[] getAcceptedIssuers() {
            return EmptyArrays.EMPTY_X509_CERTIFICATES;
        }
    };

    private InsecureTrustManagerFactory() { }

    @Override
    protected void engineInit(KeyStore keyStore) throws Exception { }

    @Override
    protected void engineInit(ManagerFactoryParameters managerFactoryParameters) throws Exception { }

    @Override
    protected TrustManager[] engineGetTrustManagers() {
        return new TrustManager[] { tm };
    }
}

Summary

The bottom layer of spring cloud gateway uses reactor’s httpclient, and relevant options can be specified through the configuration of spring.cloud.gateway.http client prefix. There are three main categories: pool, proxy and ssl. The default type of pool is elastic. if it is fixed, you can also specify maxConnections and acquireTimeout parameters.

doc