Merge branch 'configure-httpclientfactory' into 3.1.x
This commit is contained in:
@@ -16,26 +16,16 @@
|
||||
|
||||
package org.springframework.cloud.gateway.config;
|
||||
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import io.netty.channel.ChannelOption;
|
||||
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.Http11SslContextSpec;
|
||||
import reactor.netty.http.Http2SslContextSpec;
|
||||
import reactor.netty.http.HttpProtocol;
|
||||
import reactor.netty.http.client.HttpClient;
|
||||
import reactor.netty.http.client.WebsocketClientSpec;
|
||||
import reactor.netty.http.server.WebsocketServerSpec;
|
||||
import reactor.netty.resources.ConnectionProvider;
|
||||
import reactor.netty.tcp.SslProvider.ProtocolSslContextSpec;
|
||||
import reactor.netty.transport.ProxyProvider;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
@@ -155,15 +145,12 @@ 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.annotation.AnnotationAwareOrderComparator;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.http.codec.ServerCodecConfigurer;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
|
||||
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientManager;
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.validation.Validator;
|
||||
import org.springframework.web.reactive.DispatcherHandler;
|
||||
import org.springframework.web.reactive.socket.client.ReactorNettyWebSocketClient;
|
||||
@@ -173,9 +160,6 @@ import org.springframework.web.reactive.socket.server.WebSocketService;
|
||||
import org.springframework.web.reactive.socket.server.support.HandshakeWebSocketService;
|
||||
import org.springframework.web.reactive.socket.server.upgrade.ReactorNettyRequestUpgradeStrategy;
|
||||
|
||||
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
|
||||
@@ -672,139 +656,10 @@ public class GatewayAutoConfiguration {
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public HttpClient gatewayHttpClient(HttpClientProperties properties, ServerProperties serverProperties,
|
||||
List<HttpClientCustomizer> customizers) {
|
||||
|
||||
// configure pool resources
|
||||
ConnectionProvider connectionProvider = buildConnectionProvider(properties);
|
||||
|
||||
HttpClient httpClient = HttpClient.create(connectionProvider)
|
||||
// TODO: move customizations to HttpClientCustomizers
|
||||
.httpResponseDecoder(spec -> {
|
||||
if (properties.getMaxHeaderSize() != null) {
|
||||
// cast to int is ok, since @Max is Integer.MAX_VALUE
|
||||
spec.maxHeaderSize((int) properties.getMaxHeaderSize().toBytes());
|
||||
}
|
||||
if (properties.getMaxInitialLineLength() != null) {
|
||||
// cast to int is ok, since @Max is Integer.MAX_VALUE
|
||||
spec.maxInitialLineLength((int) properties.getMaxInitialLineLength().toBytes());
|
||||
}
|
||||
return spec;
|
||||
});
|
||||
|
||||
if (serverProperties.getHttp2().isEnabled()) {
|
||||
httpClient = httpClient.protocol(HttpProtocol.HTTP11, HttpProtocol.H2);
|
||||
}
|
||||
|
||||
if (properties.getConnectTimeout() != null) {
|
||||
httpClient = httpClient.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, properties.getConnectTimeout());
|
||||
}
|
||||
|
||||
// configure proxy if proxy host is set.
|
||||
if (StringUtils.hasText(properties.getProxy().getHost())) {
|
||||
HttpClientProperties.Proxy proxy = properties.getProxy();
|
||||
|
||||
httpClient = httpClient.proxy(proxySpec -> {
|
||||
ProxyProvider.Builder builder = proxySpec.type(proxy.getType()).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);
|
||||
});
|
||||
}
|
||||
|
||||
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
|
||||
ProtocolSslContextSpec clientSslContext = (serverProperties.getHttp2().isEnabled())
|
||||
? Http2SslContextSpec.forClient() : Http11SslContextSpec.forClient();
|
||||
clientSslContext.configure(sslContextBuilder -> {
|
||||
X509Certificate[] trustedX509Certificates = ssl.getTrustedX509CertificatesForTrustManager();
|
||||
if (trustedX509Certificates.length > 0) {
|
||||
sslContextBuilder.trustManager(trustedX509Certificates);
|
||||
}
|
||||
else if (ssl.isUseInsecureTrustManager()) {
|
||||
sslContextBuilder.trustManager(InsecureTrustManagerFactory.INSTANCE);
|
||||
}
|
||||
|
||||
try {
|
||||
sslContextBuilder.keyManager(ssl.getKeyManagerFactory());
|
||||
}
|
||||
catch (Exception e) {
|
||||
logger.error(e);
|
||||
}
|
||||
});
|
||||
|
||||
sslContextSpec.sslContext(clientSslContext).handshakeTimeout(ssl.getHandshakeTimeout())
|
||||
.closeNotifyFlushTimeout(ssl.getCloseNotifyFlushTimeout())
|
||||
.closeNotifyReadTimeout(ssl.getCloseNotifyReadTimeout());
|
||||
});
|
||||
}
|
||||
else if (serverProperties.getHttp2().isEnabled()) {
|
||||
httpClient = httpClient.secure(sslContextSpec -> {
|
||||
Http2SslContextSpec clientSslCtxt = Http2SslContextSpec.forClient()
|
||||
.configure(builder -> builder.trustManager(InsecureTrustManagerFactory.INSTANCE));
|
||||
sslContextSpec.sslContext(clientSslCtxt).handshakeTimeout(ssl.getHandshakeTimeout())
|
||||
.closeNotifyFlushTimeout(ssl.getCloseNotifyFlushTimeout())
|
||||
.closeNotifyReadTimeout(ssl.getCloseNotifyReadTimeout());
|
||||
});
|
||||
}
|
||||
|
||||
if (properties.isWiretap()) {
|
||||
httpClient = httpClient.wiretap(true);
|
||||
}
|
||||
|
||||
if (properties.isCompression()) {
|
||||
httpClient = httpClient.compress(true);
|
||||
}
|
||||
|
||||
if (!CollectionUtils.isEmpty(customizers)) {
|
||||
customizers.sort(AnnotationAwareOrderComparator.INSTANCE);
|
||||
for (HttpClientCustomizer customizer : customizers) {
|
||||
httpClient = customizer.customize(httpClient);
|
||||
}
|
||||
}
|
||||
|
||||
return httpClient;
|
||||
}
|
||||
|
||||
private ConnectionProvider buildConnectionProvider(HttpClientProperties properties) {
|
||||
HttpClientProperties.Pool pool = properties.getPool();
|
||||
|
||||
ConnectionProvider connectionProvider;
|
||||
if (pool.getType() == DISABLED) {
|
||||
connectionProvider = ConnectionProvider.newConnection();
|
||||
}
|
||||
else {
|
||||
// create either Fixed or Elastic pool
|
||||
ConnectionProvider.Builder builder = ConnectionProvider.builder(pool.getName());
|
||||
if (pool.getType() == FIXED) {
|
||||
builder.maxConnections(pool.getMaxConnections()).pendingAcquireMaxCount(-1)
|
||||
.pendingAcquireTimeout(Duration.ofMillis(pool.getAcquireTimeout()));
|
||||
}
|
||||
else {
|
||||
// Elastic
|
||||
builder.maxConnections(Integer.MAX_VALUE).pendingAcquireTimeout(Duration.ofMillis(0))
|
||||
.pendingAcquireMaxCount(-1);
|
||||
}
|
||||
|
||||
if (pool.getMaxIdleTime() != null) {
|
||||
builder.maxIdleTime(pool.getMaxIdleTime());
|
||||
}
|
||||
if (pool.getMaxLifeTime() != null) {
|
||||
builder.maxLifeTime(pool.getMaxLifeTime());
|
||||
}
|
||||
builder.evictInBackground(pool.getEvictionInterval());
|
||||
builder.metrics(pool.isMetrics());
|
||||
connectionProvider = builder.build();
|
||||
}
|
||||
return connectionProvider;
|
||||
@ConditionalOnMissingBean({ HttpClient.class, HttpClientFactory.class })
|
||||
public HttpClientFactory gatewayHttpClientFactory(HttpClientProperties properties,
|
||||
ServerProperties serverProperties, List<HttpClientCustomizer> customizers) {
|
||||
return new HttpClientFactory(properties, serverProperties, customizers);
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -0,0 +1,314 @@
|
||||
/*
|
||||
* Copyright 2013-2022 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.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import javax.net.ssl.KeyManagerFactory;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.handler.ssl.SslContextBuilder;
|
||||
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
|
||||
import reactor.netty.http.Http11SslContextSpec;
|
||||
import reactor.netty.http.Http2SslContextSpec;
|
||||
import reactor.netty.http.HttpProtocol;
|
||||
import reactor.netty.http.client.HttpClient;
|
||||
import reactor.netty.http.client.HttpResponseDecoderSpec;
|
||||
import reactor.netty.resources.ConnectionProvider;
|
||||
import reactor.netty.tcp.SslProvider;
|
||||
import reactor.netty.transport.ProxyProvider;
|
||||
|
||||
import org.springframework.beans.factory.config.AbstractFactoryBean;
|
||||
import org.springframework.boot.autoconfigure.web.ServerProperties;
|
||||
import org.springframework.boot.context.properties.PropertyMapper;
|
||||
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.ResourceUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import static org.springframework.cloud.gateway.config.HttpClientProperties.Pool.PoolType.DISABLED;
|
||||
import static org.springframework.cloud.gateway.config.HttpClientProperties.Pool.PoolType.FIXED;
|
||||
|
||||
/**
|
||||
* Factory Bean that allows users to extend and customize parts of the HttpClient. Also
|
||||
* allows for testing the configuration of the HttpClient.
|
||||
*
|
||||
* @author Spencer Gibb
|
||||
* @since 3.1.1
|
||||
*/
|
||||
public class HttpClientFactory extends AbstractFactoryBean<HttpClient> {
|
||||
|
||||
protected final HttpClientProperties properties;
|
||||
|
||||
protected final ServerProperties serverProperties;
|
||||
|
||||
protected final List<HttpClientCustomizer> customizers;
|
||||
|
||||
public HttpClientFactory(HttpClientProperties properties, ServerProperties serverProperties,
|
||||
List<HttpClientCustomizer> customizers) {
|
||||
this.properties = properties;
|
||||
this.serverProperties = serverProperties;
|
||||
this.customizers = customizers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getObjectType() {
|
||||
return HttpClient.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HttpClient createInstance() {
|
||||
// configure pool resources
|
||||
ConnectionProvider connectionProvider = buildConnectionProvider(properties);
|
||||
|
||||
HttpClient httpClient = HttpClient.create(connectionProvider)
|
||||
// TODO: move customizations to HttpClientCustomizers
|
||||
.httpResponseDecoder(this::httpResponseDecoder);
|
||||
|
||||
if (serverProperties.getHttp2().isEnabled()) {
|
||||
httpClient = httpClient.protocol(HttpProtocol.HTTP11, HttpProtocol.H2);
|
||||
}
|
||||
|
||||
if (properties.getConnectTimeout() != null) {
|
||||
httpClient = httpClient.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, properties.getConnectTimeout());
|
||||
}
|
||||
|
||||
httpClient = configureProxy(httpClient);
|
||||
|
||||
httpClient = configureSsl(httpClient);
|
||||
|
||||
if (properties.isWiretap()) {
|
||||
httpClient = httpClient.wiretap(true);
|
||||
}
|
||||
|
||||
if (properties.isCompression()) {
|
||||
httpClient = httpClient.compress(true);
|
||||
}
|
||||
|
||||
httpClient = applyCustomizers(httpClient);
|
||||
|
||||
return httpClient;
|
||||
}
|
||||
|
||||
private HttpClient applyCustomizers(HttpClient httpClient) {
|
||||
if (!CollectionUtils.isEmpty(customizers)) {
|
||||
customizers.sort(AnnotationAwareOrderComparator.INSTANCE);
|
||||
for (HttpClientCustomizer customizer : customizers) {
|
||||
httpClient = customizer.customize(httpClient);
|
||||
}
|
||||
}
|
||||
return httpClient;
|
||||
}
|
||||
|
||||
protected HttpClient configureSsl(HttpClient httpClient) {
|
||||
HttpClientProperties.Ssl ssl = properties.getSsl();
|
||||
if ((ssl.getKeyStore() != null && ssl.getKeyStore().length() > 0)
|
||||
|| getTrustedX509CertificatesForTrustManager().length > 0 || ssl.isUseInsecureTrustManager()) {
|
||||
httpClient = httpClient.secure(sslContextSpec -> {
|
||||
// configure ssl
|
||||
configureSslContext(ssl, sslContextSpec);
|
||||
});
|
||||
}
|
||||
return httpClient;
|
||||
}
|
||||
|
||||
protected void configureSslContext(HttpClientProperties.Ssl ssl, SslProvider.SslContextSpec sslContextSpec) {
|
||||
SslProvider.ProtocolSslContextSpec clientSslContext = (serverProperties.getHttp2().isEnabled())
|
||||
? Http2SslContextSpec.forClient() : Http11SslContextSpec.forClient();
|
||||
clientSslContext.configure(sslContextBuilder -> {
|
||||
X509Certificate[] trustedX509Certificates = getTrustedX509CertificatesForTrustManager();
|
||||
if (trustedX509Certificates.length > 0) {
|
||||
setTrustManager(sslContextBuilder, trustedX509Certificates);
|
||||
}
|
||||
else if (ssl.isUseInsecureTrustManager()) {
|
||||
setTrustManager(sslContextBuilder, InsecureTrustManagerFactory.INSTANCE);
|
||||
}
|
||||
|
||||
try {
|
||||
sslContextBuilder.keyManager(getKeyManagerFactory());
|
||||
}
|
||||
catch (Exception e) {
|
||||
logger.error(e);
|
||||
}
|
||||
});
|
||||
|
||||
sslContextSpec.sslContext(clientSslContext).handshakeTimeout(ssl.getHandshakeTimeout())
|
||||
.closeNotifyFlushTimeout(ssl.getCloseNotifyFlushTimeout())
|
||||
.closeNotifyReadTimeout(ssl.getCloseNotifyReadTimeout());
|
||||
}
|
||||
|
||||
protected HttpClient configureProxy(HttpClient httpClient) {
|
||||
// configure proxy if proxy host is set.
|
||||
if (StringUtils.hasText(properties.getProxy().getHost())) {
|
||||
HttpClientProperties.Proxy proxy = properties.getProxy();
|
||||
|
||||
httpClient = httpClient.proxy(proxySpec -> {
|
||||
configureProxyProvider(proxy, proxySpec);
|
||||
});
|
||||
}
|
||||
return httpClient;
|
||||
}
|
||||
|
||||
protected ProxyProvider.Builder configureProxyProvider(HttpClientProperties.Proxy proxy,
|
||||
ProxyProvider.TypeSpec proxySpec) {
|
||||
ProxyProvider.Builder builder = proxySpec.type(proxy.getType()).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;
|
||||
}
|
||||
|
||||
protected HttpResponseDecoderSpec httpResponseDecoder(HttpResponseDecoderSpec spec) {
|
||||
if (properties.getMaxHeaderSize() != null) {
|
||||
// cast to int is ok, since @Max is Integer.MAX_VALUE
|
||||
spec.maxHeaderSize((int) properties.getMaxHeaderSize().toBytes());
|
||||
}
|
||||
if (properties.getMaxInitialLineLength() != null) {
|
||||
// cast to int is ok, since @Max is Integer.MAX_VALUE
|
||||
spec.maxInitialLineLength((int) properties.getMaxInitialLineLength().toBytes());
|
||||
}
|
||||
return spec;
|
||||
}
|
||||
|
||||
protected X509Certificate[] getTrustedX509CertificatesForTrustManager() {
|
||||
HttpClientProperties.Ssl ssl = properties.getSsl();
|
||||
|
||||
try {
|
||||
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
|
||||
ArrayList<Certificate> allCerts = new ArrayList<>();
|
||||
for (String trustedCert : ssl.getTrustedX509Certificates()) {
|
||||
try {
|
||||
URL url = ResourceUtils.getURL(trustedCert);
|
||||
Collection<? extends Certificate> certs = certificateFactory.generateCertificates(url.openStream());
|
||||
allCerts.addAll(certs);
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new RuntimeException("Could not load certificate '" + trustedCert + "'", e);
|
||||
}
|
||||
}
|
||||
return allCerts.toArray(new X509Certificate[allCerts.size()]);
|
||||
}
|
||||
catch (CertificateException e1) {
|
||||
throw new RuntimeException("Could not load CertificateFactory X.509", e1);
|
||||
}
|
||||
}
|
||||
|
||||
protected KeyManagerFactory getKeyManagerFactory() {
|
||||
HttpClientProperties.Ssl ssl = properties.getSsl();
|
||||
try {
|
||||
if (ssl.getKeyStore() != null && ssl.getKeyStore().length() > 0) {
|
||||
KeyManagerFactory keyManagerFactory = KeyManagerFactory
|
||||
.getInstance(KeyManagerFactory.getDefaultAlgorithm());
|
||||
char[] keyPassword = ssl.getKeyPassword() != null ? ssl.getKeyPassword().toCharArray() : null;
|
||||
|
||||
if (keyPassword == null && ssl.getKeyStorePassword() != null) {
|
||||
keyPassword = ssl.getKeyStorePassword().toCharArray();
|
||||
}
|
||||
|
||||
keyManagerFactory.init(this.createKeyStore(), keyPassword);
|
||||
|
||||
return keyManagerFactory;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
protected KeyStore createKeyStore() {
|
||||
HttpClientProperties.Ssl ssl = properties.getSsl();
|
||||
try {
|
||||
KeyStore store = ssl.getKeyStoreProvider() != null
|
||||
? KeyStore.getInstance(ssl.getKeyStoreType(), ssl.getKeyStoreProvider())
|
||||
: KeyStore.getInstance(ssl.getKeyStoreType());
|
||||
try {
|
||||
URL url = ResourceUtils.getURL(ssl.getKeyStore());
|
||||
store.load(url.openStream(),
|
||||
ssl.getKeyStorePassword() != null ? ssl.getKeyStorePassword().toCharArray() : null);
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new RuntimeException("Could not load key store ' " + ssl.getKeyStore() + "'", e);
|
||||
}
|
||||
|
||||
return store;
|
||||
}
|
||||
catch (KeyStoreException | NoSuchProviderException e) {
|
||||
throw new RuntimeException("Could not load KeyStore for given type and provider", e);
|
||||
}
|
||||
}
|
||||
|
||||
protected void setTrustManager(SslContextBuilder sslContextBuilder, X509Certificate... trustedX509Certificates) {
|
||||
sslContextBuilder.trustManager(trustedX509Certificates);
|
||||
}
|
||||
|
||||
protected void setTrustManager(SslContextBuilder sslContextBuilder, TrustManagerFactory factory) {
|
||||
sslContextBuilder.trustManager(factory);
|
||||
}
|
||||
|
||||
protected ConnectionProvider buildConnectionProvider(HttpClientProperties properties) {
|
||||
HttpClientProperties.Pool pool = properties.getPool();
|
||||
|
||||
ConnectionProvider connectionProvider;
|
||||
if (pool.getType() == DISABLED) {
|
||||
connectionProvider = ConnectionProvider.newConnection();
|
||||
}
|
||||
else {
|
||||
// create either Fixed or Elastic pool
|
||||
ConnectionProvider.Builder builder = ConnectionProvider.builder(pool.getName());
|
||||
if (pool.getType() == FIXED) {
|
||||
builder.maxConnections(pool.getMaxConnections()).pendingAcquireMaxCount(-1)
|
||||
.pendingAcquireTimeout(Duration.ofMillis(pool.getAcquireTimeout()));
|
||||
}
|
||||
else {
|
||||
// Elastic
|
||||
builder.maxConnections(Integer.MAX_VALUE).pendingAcquireTimeout(Duration.ofMillis(0))
|
||||
.pendingAcquireMaxCount(-1);
|
||||
}
|
||||
|
||||
if (pool.getMaxIdleTime() != null) {
|
||||
builder.maxIdleTime(pool.getMaxIdleTime());
|
||||
}
|
||||
if (pool.getMaxLifeTime() != null) {
|
||||
builder.maxLifeTime(pool.getMaxLifeTime());
|
||||
}
|
||||
builder.evictInBackground(pool.getEvictionInterval());
|
||||
builder.metrics(pool.isMetrics());
|
||||
connectionProvider = builder.build();
|
||||
}
|
||||
return connectionProvider;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -483,6 +483,7 @@ public class HttpClientProperties {
|
||||
this.trustedX509Certificates = trustedX509;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public X509Certificate[] getTrustedX509CertificatesForTrustManager() {
|
||||
try {
|
||||
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
|
||||
@@ -505,6 +506,7 @@ public class HttpClientProperties {
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public KeyManagerFactory getKeyManagerFactory() {
|
||||
try {
|
||||
if (getKeyStore() != null && getKeyStore().length() > 0) {
|
||||
@@ -528,6 +530,7 @@ public class HttpClientProperties {
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public KeyStore createKeyStore() {
|
||||
try {
|
||||
KeyStore store = getKeyStoreProvider() != null
|
||||
|
||||
@@ -18,18 +18,30 @@ package org.springframework.cloud.gateway.config;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
|
||||
import io.netty.channel.ChannelOption;
|
||||
import io.netty.handler.ssl.SslContextBuilder;
|
||||
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
|
||||
import org.junit.Test;
|
||||
import reactor.netty.http.HttpProtocol;
|
||||
import reactor.netty.http.client.HttpClient;
|
||||
import reactor.netty.http.client.HttpClientConfig;
|
||||
import reactor.netty.http.client.WebsocketClientSpec;
|
||||
import reactor.netty.http.server.WebsocketServerSpec;
|
||||
import reactor.netty.resources.ConnectionProvider;
|
||||
import reactor.netty.tcp.SslProvider;
|
||||
import reactor.netty.transport.ProxyProvider;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.SpringBootConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration;
|
||||
@@ -48,6 +60,7 @@ import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientManager;
|
||||
import org.springframework.web.filter.reactive.HiddenHttpMethodFilter;
|
||||
import org.springframework.web.reactive.socket.client.ReactorNettyWebSocketClient;
|
||||
@@ -76,21 +89,19 @@ public class GatewayAutoConfigurationTests {
|
||||
ServerPropertiesConfig.class))
|
||||
.withPropertyValues("debug=true").run(context -> {
|
||||
assertThat(context).hasSingleBean(HttpClient.class);
|
||||
assertThat(context).hasBean("gatewayHttpClient");
|
||||
HttpClient httpClient = context.getBean(HttpClient.class);
|
||||
/*
|
||||
* FIXME: 2.1.0 HttpClientOptions options = httpClient.options();
|
||||
*
|
||||
* PoolResources poolResources = options.getPoolResources();
|
||||
* assertThat(poolResources).isNotNull(); //TODO: howto test
|
||||
* PoolResources
|
||||
*
|
||||
* ClientProxyOptions proxyOptions = options.getProxyOptions();
|
||||
* assertThat(proxyOptions).isNull();
|
||||
*
|
||||
* SslContext sslContext = options.sslContext();
|
||||
* assertThat(sslContext).isNull();
|
||||
*/
|
||||
CustomHttpClientFactory factory = context.getBean(CustomHttpClientFactory.class);
|
||||
|
||||
assertThat(factory.connectionProvider).isNotNull();
|
||||
assertThat(factory.connectionProvider.maxConnections()).isEqualTo(Integer.MAX_VALUE); // elastic
|
||||
|
||||
assertThat(factory.proxyProvider).isNull();
|
||||
assertThat(factory.sslConfigured).isFalse();
|
||||
|
||||
assertThat(httpClient.configuration().isAcceptGzip()).isFalse();
|
||||
assertThat(httpClient.configuration().loggingHandler()).isNull();
|
||||
assertThat(httpClient.configuration().options())
|
||||
.doesNotContainKey(ChannelOption.CONNECT_TIMEOUT_MILLIS);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -107,6 +118,7 @@ public class GatewayAutoConfigurationTests {
|
||||
"spring.cloud.gateway.httpclient.pool.type=fixed",
|
||||
"spring.cloud.gateway.httpclient.pool.metrics=true",
|
||||
"spring.cloud.gateway.httpclient.compression=true",
|
||||
"spring.cloud.gateway.httpclient.wiretap=true",
|
||||
// greater than integer max value
|
||||
"spring.cloud.gateway.httpclient.max-initial-line-length=2147483647",
|
||||
"spring.cloud.gateway.httpclient.proxy.host=myhost",
|
||||
@@ -114,27 +126,30 @@ public class GatewayAutoConfigurationTests {
|
||||
.run(context -> {
|
||||
assertThat(context).hasSingleBean(HttpClient.class);
|
||||
HttpClient httpClient = context.getBean(HttpClient.class);
|
||||
CustomHttpClientFactory factory = context.getBean(CustomHttpClientFactory.class);
|
||||
HttpClientProperties properties = context.getBean(HttpClientProperties.class);
|
||||
assertThat(properties.getMaxInitialLineLength().toBytes()).isLessThanOrEqualTo(Integer.MAX_VALUE);
|
||||
assertThat(properties.isCompression()).isEqualTo(true);
|
||||
assertThat(properties.getPool().getEvictionInterval()).hasSeconds(10);
|
||||
assertThat(properties.getPool().isMetrics()).isEqualTo(true);
|
||||
/*
|
||||
* FIXME: 2.1.0 HttpClientOptions options = httpClient.options();
|
||||
*
|
||||
* PoolResources poolResources = options.getPoolResources();
|
||||
* assertThat(poolResources).isNotNull(); //TODO: howto test
|
||||
* PoolResources
|
||||
*
|
||||
* ClientProxyOptions proxyOptions = options.getProxyOptions();
|
||||
* assertThat(proxyOptions).isNotNull();
|
||||
* assertThat(proxyOptions.getAddress().get().getHostName()).isEqualTo
|
||||
* ("myhost");
|
||||
*
|
||||
* SslContext sslContext = options.sslContext();
|
||||
* assertThat(sslContext).isNotNull();
|
||||
*/
|
||||
// TODO: howto test SslContext
|
||||
|
||||
assertThat(httpClient.configuration().isAcceptGzip()).isTrue();
|
||||
assertThat(httpClient.configuration().loggingHandler()).isNotNull();
|
||||
assertThat(httpClient.configuration().options()).containsKey(ChannelOption.CONNECT_TIMEOUT_MILLIS);
|
||||
assertThat(httpClient.configuration().options().get(ChannelOption.CONNECT_TIMEOUT_MILLIS))
|
||||
.isEqualTo(10);
|
||||
|
||||
assertThat(factory.connectionProvider).isNotNull();
|
||||
// fixed pool
|
||||
assertThat(factory.connectionProvider.maxConnections())
|
||||
.isEqualTo(ConnectionProvider.DEFAULT_POOL_MAX_CONNECTIONS);
|
||||
|
||||
assertThat(factory.proxyProvider).isNotNull();
|
||||
assertThat(factory.proxyProvider.build().getAddress().get().getHostName()).isEqualTo("myhost");
|
||||
|
||||
assertThat(factory.sslConfigured).isTrue();
|
||||
assertThat(factory.insecureTrustManagerSet).isTrue();
|
||||
|
||||
assertThat(context).hasSingleBean(ReactorNettyRequestUpgradeStrategy.class);
|
||||
ReactorNettyRequestUpgradeStrategy upgradeStrategy = context
|
||||
.getBean(ReactorNettyRequestUpgradeStrategy.class);
|
||||
@@ -244,6 +259,8 @@ public class GatewayAutoConfigurationTests {
|
||||
.withPropertyValues("server.http2.enabled=true").run(context -> {
|
||||
assertThat(context).hasSingleBean(GRPCRequestHeadersFilter.class);
|
||||
assertThat(context).hasSingleBean(GRPCResponseHeadersFilter.class);
|
||||
HttpClient httpClient = context.getBean(HttpClient.class);
|
||||
assertThat(httpClient.configuration().protocols()).contains(HttpProtocol.HTTP11, HttpProtocol.H2);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -259,10 +276,86 @@ public class GatewayAutoConfigurationTests {
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void insecureTrustManagerNotEnabledByDefaultWhenHTTP2Enabled() {
|
||||
new ReactiveWebApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(WebFluxAutoConfiguration.class, MetricsAutoConfiguration.class,
|
||||
SimpleMetricsExportAutoConfiguration.class, GatewayAutoConfiguration.class,
|
||||
HttpClientCustomizedConfig.class, ServerPropertiesConfig.class))
|
||||
.withPropertyValues("server.http2.enabled=true").run(context -> {
|
||||
assertThat(context).hasSingleBean(HttpClient.class);
|
||||
CustomHttpClientFactory factory = context.getBean(CustomHttpClientFactory.class);
|
||||
assertThat(factory.insecureTrustManagerSet).isFalse();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customHttpClientWorks() {
|
||||
new ReactiveWebApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(WebFluxAutoConfiguration.class, MetricsAutoConfiguration.class,
|
||||
SimpleMetricsExportAutoConfiguration.class, GatewayAutoConfiguration.class,
|
||||
HttpClientCustomizedConfig.class, CustomHttpClientConfig.class))
|
||||
.run(context -> {
|
||||
assertThat(context).hasSingleBean(HttpClient.class);
|
||||
HttpClient httpClient = context.getBean(HttpClient.class);
|
||||
assertThat(httpClient).isInstanceOf(CustomHttpClient.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(ServerProperties.class)
|
||||
@AutoConfigureBefore(GatewayAutoConfiguration.class)
|
||||
protected static class ServerPropertiesConfig {
|
||||
|
||||
@Bean
|
||||
@Primary
|
||||
CustomHttpClientFactory customHttpClientFactory(HttpClientProperties properties,
|
||||
ServerProperties serverProperties, List<HttpClientCustomizer> customizers) {
|
||||
return new CustomHttpClientFactory(properties, serverProperties, customizers);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected static class CustomHttpClientFactory extends HttpClientFactory {
|
||||
|
||||
boolean insecureTrustManagerSet;
|
||||
|
||||
boolean sslConfigured;
|
||||
|
||||
private ConnectionProvider connectionProvider;
|
||||
|
||||
private ProxyProvider.Builder proxyProvider;
|
||||
|
||||
public CustomHttpClientFactory(HttpClientProperties properties, ServerProperties serverProperties,
|
||||
List<HttpClientCustomizer> customizers) {
|
||||
super(properties, serverProperties, customizers);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ConnectionProvider buildConnectionProvider(HttpClientProperties properties) {
|
||||
connectionProvider = super.buildConnectionProvider(properties);
|
||||
return connectionProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ProxyProvider.Builder configureProxyProvider(HttpClientProperties.Proxy proxy,
|
||||
ProxyProvider.TypeSpec proxySpec) {
|
||||
proxyProvider = super.configureProxyProvider(proxy, proxySpec);
|
||||
return proxyProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configureSslContext(HttpClientProperties.Ssl ssl, SslProvider.SslContextSpec sslContextSpec) {
|
||||
sslConfigured = true;
|
||||
super.configureSslContext(ssl, sslContextSpec);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setTrustManager(SslContextBuilder sslContextBuilder, TrustManagerFactory factory) {
|
||||
insecureTrustManagerSet = factory == InsecureTrustManagerFactory.INSTANCE;
|
||||
super.setTrustManager(sslContextBuilder, factory);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableAutoConfiguration
|
||||
@@ -271,6 +364,33 @@ public class GatewayAutoConfigurationTests {
|
||||
|
||||
}
|
||||
|
||||
@EnableAutoConfiguration
|
||||
@SpringBootConfiguration
|
||||
@EnableConfigurationProperties(ServerProperties.class)
|
||||
@AutoConfigureBefore(GatewayAutoConfiguration.class)
|
||||
protected static class CustomHttpClientConfig {
|
||||
|
||||
@Bean
|
||||
public HttpClient customHttpClient() {
|
||||
return new CustomHttpClient();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected static class CustomHttpClient extends HttpClient {
|
||||
|
||||
@Override
|
||||
public HttpClientConfig configuration() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HttpClient duplicate() {
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableAutoConfiguration
|
||||
@SpringBootConfiguration
|
||||
protected static class RouteLocatorBuilderConfig {
|
||||
|
||||
Reference in New Issue
Block a user