• SpringCloud gateway自定义请求的 httpClient


    本文为博主原创,转载请注明出处:

      引用 的 spring cloud gateway 的版本为 2.2.5 ;

      SpringCloud gateway 在实现服务路由并请求的具体过程是在 org.springframework.cloud.gateway.filter.NettyRoutingFilter 的过滤器中,该过滤器封装了具体的请求参数,以及根据路由规则请求的对应服务,然后根据 HttpClient 进行微服务之间的请求; 该 httpClient 类是 用netty 封装的 客户端,其包路径为 : reactor.netty.http.client.HttpClient ;

      查看 NettyRoutingFilter 中的 filter 实现过程:

    复制代码
        public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            URI requestUrl = (URI)exchange.getRequiredAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR);
            String scheme = requestUrl.getScheme();
            if (!ServerWebExchangeUtils.isAlreadyRouted(exchange) && ("http".equals(scheme) || "https".equals(scheme))) {
                ServerWebExchangeUtils.setAlreadyRouted(exchange);
                ServerHttpRequest request = exchange.getRequest();
                HttpMethod method = HttpMethod.valueOf(request.getMethodValue());
                String url = requestUrl.toASCIIString();
                HttpHeaders filtered = HttpHeadersFilter.filterRequest(this.getHeadersFilters(), exchange);
                DefaultHttpHeaders httpHeaders = new DefaultHttpHeaders();
                filtered.forEach(httpHeaders::set);
                boolean preserveHost = (Boolean)exchange.getAttributeOrDefault(ServerWebExchangeUtils.PRESERVE_HOST_HEADER_ATTRIBUTE, false);
                Route route = (Route)exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
                Flux responseFlux = ((RequestSender)this.getHttpClient(route, exchange).headers((headers) -> {
                    headers.add(httpHeaders);
                    headers.remove("Host");
                    if (preserveHost) {
                        String host = request.getHeaders().getFirst("Host");
                        headers.add("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(this::getByteBuf));
                }).responseConnection((res, connection) -> {
                    exchange.getAttributes().put(ServerWebExchangeUtils.CLIENT_RESPONSE_ATTR, res);
                    exchange.getAttributes().put(ServerWebExchangeUtils.CLIENT_RESPONSE_CONN_ATTR, connection);
                    ServerHttpResponse response = exchange.getResponse();
                    HttpHeaders headers = new HttpHeaders();
                    res.responseHeaders().forEach((entry) -> {
                        headers.add((String)entry.getKey(), (String)entry.getValue());
                    });
                    String contentTypeValue = headers.getFirst("Content-Type");
                    if (StringUtils.hasLength(contentTypeValue)) {
                        exchange.getAttributes().put("original_response_content_type", contentTypeValue);
                    }
    
                    this.setResponseStatus(res, response);
                    HttpHeaders filteredResponseHeaders = HttpHeadersFilter.filter(this.getHeadersFilters(), headers, exchange, Type.RESPONSE);
                    if (!filteredResponseHeaders.containsKey("Transfer-Encoding") && filteredResponseHeaders.containsKey("Content-Length")) {
                        response.getHeaders().remove("Transfer-Encoding");
                    }
    
                    exchange.getAttributes().put(ServerWebExchangeUtils.CLIENT_RESPONSE_HEADER_NAMES, filteredResponseHeaders.keySet());
                    response.getHeaders().putAll(filteredResponseHeaders);
                    return Mono.just(res);
                });
                Duration responseTimeout = this.getResponseTimeout(route);
                if (responseTimeout != null) {
                    responseFlux = responseFlux.timeout(responseTimeout, Mono.error(new TimeoutException("Response took longer than timeout: " + responseTimeout))).onErrorMap(TimeoutException.class, (th) -> {
                        return new ResponseStatusException(HttpStatus.GATEWAY_TIMEOUT, th.getMessage(), th);
                    });
                }
    
                return responseFlux.then(chain.filter(exchange));
            } else {
                return chain.filter(exchange);
            }
        }
    复制代码

      

      该方法中 有一个 getHttpClient 方法获取 httpClient 客户端实例的过程,由于在  GatewayAutoConfiguration 中 定义了 springCloud gateway 使用的 httpclient 实例,其声明并自动加载的代码如下:

    复制代码
    @Configuration(
            proxyBeanMethods = false
        )
        @ConditionalOnClass({HttpClient.class})
        protected static class NettyConfiguration {
            protected final Log logger = LogFactory.getLog(this.getClass());
    
            protected NettyConfiguration() {
            }
    
            @Bean
            @ConditionalOnProperty(
                name = {"spring.cloud.gateway.httpserver.wiretap"}
            )
            public NettyWebServerFactoryCustomizer nettyServerWiretapCustomizer(Environment environment, ServerProperties serverProperties) {
                return new NettyWebServerFactoryCustomizer(environment, serverProperties) {
                    public void customize(NettyReactiveWebServerFactory factory) {
                        factory.addServerCustomizers(new NettyServerCustomizer[]{(httpServer) -> {
                            return httpServer.wiretap(true);
                        }});
                        super.customize(factory);
                    }
                };
            }
    
            @Bean
            @ConditionalOnMissingBean
            public HttpClient gatewayHttpClient(HttpClientProperties properties, List customizers) {
                Pool pool = properties.getPool();
                ConnectionProvider connectionProvider;
                if (pool.getType() == PoolType.DISABLED) {
                    connectionProvider = ConnectionProvider.newConnection();
                } else if (pool.getType() == PoolType.FIXED) {
                    connectionProvider = ConnectionProvider.fixed(pool.getName(), pool.getMaxConnections(), pool.getAcquireTimeout(), pool.getMaxIdleTime(), pool.getMaxLifeTime());
                } else {
                    connectionProvider = ConnectionProvider.elastic(pool.getName(), pool.getMaxIdleTime(), pool.getMaxLifeTime());
                }
    
                HttpClient httpClient = HttpClient.create(connectionProvider).httpResponseDecoder((spec) -> {
                    if (properties.getMaxHeaderSize() != null) {
                        spec.maxHeaderSize((int)properties.getMaxHeaderSize().toBytes());
                    }
    
                    if (properties.getMaxInitialLineLength() != null) {
                        spec.maxInitialLineLength((int)properties.getMaxInitialLineLength().toBytes());
                    }
    
                    return spec;
                }).tcpConfiguration((tcpClient) -> {
                    if (properties.getConnectTimeout() != null) {
                        tcpClient = tcpClient.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, properties.getConnectTimeout());
                    }
    
                    Proxy proxy = properties.getProxy();
                    if (StringUtils.hasText(proxy.getHost())) {
                        tcpClient = tcpClient.proxy((proxySpec) -> {
                            Builder builder = proxySpec.type(reactor.netty.tcp.ProxyProvider.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 tcpClient;
                });
                Ssl ssl = properties.getSsl();
                if (ssl.getKeyStore() != null && ssl.getKeyStore().length() > 0 || ssl.getTrustedX509CertificatesForTrustManager().length > 0 || ssl.isUseInsecureTrustManager()) {
                    httpClient = httpClient.secure((sslContextSpec) -> {
                        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 var6) {
                            this.logger.error(var6);
                        }
    
                        sslContextSpec.sslContext(sslContextBuilder).defaultConfiguration(ssl.getDefaultConfigurationType()).handshakeTimeout(ssl.getHandshakeTimeout()).closeNotifyFlushTimeout(ssl.getCloseNotifyFlushTimeout()).closeNotifyReadTimeout(ssl.getCloseNotifyReadTimeout());
                    });
                }
    
                if (properties.isWiretap()) {
                    httpClient = httpClient.wiretap(true);
                }
    
                if (!CollectionUtils.isEmpty(customizers)) {
                    customizers.sort(AnnotationAwareOrderComparator.INSTANCE);
    
                    HttpClientCustomizer customizer;
                    for(Iterator var7 = customizers.iterator(); var7.hasNext(); httpClient = customizer.customize(httpClient)) {
                        customizer = (HttpClientCustomizer)var7.next();
                    }
                }
    
                return httpClient;
            }
    
            @Bean
            public HttpClientProperties httpClientProperties() {
                return new HttpClientProperties();
            }
    
            @Bean
            public NettyRoutingFilter routingFilter(HttpClient httpClient, ObjectProvider> 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());
                }
    
                webSocketClient.setHandlePing(properties.getWebsocket().isProxyPing());
                return webSocketClient;
            }
    
            @Bean
            public ReactorNettyRequestUpgradeStrategy reactorNettyRequestUpgradeStrategy(HttpClientProperties httpClientProperties) {
                ReactorNettyRequestUpgradeStrategy requestUpgradeStrategy = new ReactorNettyRequestUpgradeStrategy();
                Websocket websocket = httpClientProperties.getWebsocket();
                PropertyMapper map = PropertyMapper.get();
                websocket.getClass();
                map.from(websocket::getMaxFramePayloadLength).whenNonNull().to(requestUpgradeStrategy::setMaxFramePayloadLength);
                websocket.getClass();
                map.from(websocket::isProxyPing).to(requestUpgradeStrategy::setHandlePing);
                return requestUpgradeStrategy;
            }
        }
    复制代码

      上面 代码中的 gatewayHttpClient 为 spring cloud gateway 使用的 HttpClient 实例,在spring cloud gateway 进行服务请求时,会自动配置使用该 实例。

      如果需要自定义的 HttpClient 实例,如在 httpClient 中自定义 ip 白名单校验,https 请求证书预置,或是添加特殊认证请求头等,这种场景下需要在代码中显示的定义 gatewayHttpClient 实例,代码如下:

    复制代码
        @Configuration
        public class GatewayAutoConfiguration {
    
            @Bean
            @ConditionalOnMissingBean
            public HttpClient gatewayHttpClient(HttpClientProperties properties, List customizers) {
                Pool pool = properties.getPool();
                ConnectionProvider connectionProvider;
                if (pool.getType() == PoolType.DISABLED) {
                    connectionProvider = ConnectionProvider.newConnection();
                } else if (pool.getType() == PoolType.FIXED) {
                    connectionProvider = ConnectionProvider.fixed(pool.getName(), pool.getMaxConnections(), pool.getAcquireTimeout(), pool.getMaxIdleTime(), pool.getMaxLifeTime());
                } else {
                    connectionProvider = ConnectionProvider.elastic(pool.getName(), pool.getMaxIdleTime(), pool.getMaxLifeTime());
                }
    
                HttpClient httpClient = HttpClient.create(connectionProvider).httpResponseDecoder((spec) -> {
                    if (properties.getMaxHeaderSize() != null) {
                        spec.maxHeaderSize((int)properties.getMaxHeaderSize().toBytes());
                    }
    
                    if (properties.getMaxInitialLineLength() != null) {
                        spec.maxInitialLineLength((int)properties.getMaxInitialLineLength().toBytes());
                    }
    
                    return spec;
                }).tcpConfiguration((tcpClient) -> {
                    if (properties.getConnectTimeout() != null) {
                        tcpClient = tcpClient.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, properties.getConnectTimeout());
                    }
    
                    Proxy proxy = properties.getProxy();
                    if (StringUtils.hasText(proxy.getHost())) {
                        tcpClient = tcpClient.proxy((proxySpec) -> {
                            Builder builder = proxySpec.type(reactor.netty.tcp.ProxyProvider.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 tcpClient;
                });
                Ssl ssl = properties.getSsl();
                if (ssl.getKeyStore() != null && ssl.getKeyStore().length() > 0 || ssl.getTrustedX509CertificatesForTrustManager().length > 0 || ssl.isUseInsecureTrustManager()) {
                    httpClient = httpClient.secure((sslContextSpec) -> {
                        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 var6) {
                            this.logger.error(var6);
                        }
    
                        sslContextSpec.sslContext(sslContextBuilder).defaultConfiguration(ssl.getDefaultConfigurationType()).handshakeTimeout(ssl.getHandshakeTimeout()).closeNotifyFlushTimeout(ssl.getCloseNotifyFlushTimeout()).closeNotifyReadTimeout(ssl.getCloseNotifyReadTimeout());
                    });
                }
    
                if (properties.isWiretap()) {
                    httpClient = httpClient.wiretap(true);
                }
    
                if (!CollectionUtils.isEmpty(customizers)) {
                    customizers.sort(AnnotationAwareOrderComparator.INSTANCE);
    
                    HttpClientCustomizer customizer;
                    for(Iterator var7 = customizers.iterator(); var7.hasNext(); httpClient = customizer.customize(httpClient)) {
                        customizer = (HttpClientCustomizer)var7.next();
                    }
                }
    
                return httpClient;
            }
            
        }    
    复制代码

       这样服务在启动的时候就会优先加载自定的 httpClient 实例。

      

     

  • 相关阅读:
    Java.lang.Class类 getTypeParameters()方法有什么功能呢?
    Java替换 html 中隐藏的空格字符
    论文阅读-FCD-Net: 学习检测多类型同源深度伪造人脸图像
    服务器连接校园网
    libusb 源码移植到工程项目中,使用CMake编译
    交联聚苯乙烯微球固载9氮杂二环[3,3,1]壬9氧自由基/聚苯乙烯-二乙烯基苯微球性能解析
    程序员的数学课开篇词 数学,编程能力的营养根基
    [Android开发学iOS系列] 工具篇: Xcode使用和快捷键
    自己的第一个FreeRTOS程序
    国庆活动征文 | 庆国庆,作几首打油诗在此
  • 原文地址:https://www.cnblogs.com/zjdxr-up/p/16530423.html