| /* |
| * Copyright 2013-2020 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.validation.constraints.Max; |
| |
| import reactor.netty.resources.ConnectionProvider; |
| import reactor.netty.tcp.SslProvider; |
| import reactor.netty.transport.ProxyProvider; |
| |
| import org.springframework.boot.context.properties.ConfigurationProperties; |
| import org.springframework.boot.web.server.WebServerException; |
| import org.springframework.core.style.ToStringCreator; |
| import org.springframework.util.ResourceUtils; |
| import org.springframework.util.unit.DataSize; |
| //import org.springframework.validation.annotation.Validated; |
| |
| // CHECKSTYLE:OFF |
| /** |
| * Configuration properties for the Netty {@link reactor.netty.http.client.HttpClient}. |
| */ |
| @ConfigurationProperties("spring.cloud.gateway.httpclient") |
| //@Validated |
| @SuppressWarnings("deprecation") |
| public class HttpClientProperties { |
| |
| /** The connect timeout in millis, the default is 45s. */ |
| private Integer connectTimeout; |
| |
| /** The response timeout. */ |
| private Duration responseTimeout; |
| |
| /** The max response header size. */ |
| private DataSize maxHeaderSize; |
| |
| /** The max initial line length. */ |
| private DataSize maxInitialLineLength; |
| |
| /** 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(); |
| |
| /** Websocket configuration for Netty HttpClient. */ |
| private Websocket websocket = new Websocket(); |
| |
| /** Enables wiretap debugging for Netty HttpClient. */ |
| private boolean wiretap; |
| |
| /** Enables compression for Netty HttpClient. */ |
| private boolean compression; |
| |
| public Integer getConnectTimeout() { |
| return connectTimeout; |
| } |
| |
| public void setConnectTimeout(Integer connectTimeout) { |
| this.connectTimeout = connectTimeout; |
| } |
| |
| public Duration getResponseTimeout() { |
| return responseTimeout; |
| } |
| |
| public void setResponseTimeout(Duration responseTimeout) { |
| this.responseTimeout = responseTimeout; |
| } |
| |
| @Max(Integer.MAX_VALUE) |
| public DataSize getMaxHeaderSize() { |
| return maxHeaderSize; |
| } |
| |
| public void setMaxHeaderSize(DataSize maxHeaderSize) { |
| this.maxHeaderSize = maxHeaderSize; |
| } |
| |
| @Max(Integer.MAX_VALUE) |
| public DataSize getMaxInitialLineLength() { |
| return maxInitialLineLength; |
| } |
| |
| public void setMaxInitialLineLength(DataSize maxInitialLineLength) { |
| this.maxInitialLineLength = maxInitialLineLength; |
| } |
| |
| public Pool getPool() { |
| return pool; |
| } |
| |
| public void setPool(Pool pool) { |
| this.pool = pool; |
| } |
| |
| public Proxy getProxy() { |
| return proxy; |
| } |
| |
| public void setProxy(Proxy proxy) { |
| this.proxy = proxy; |
| } |
| |
| public Ssl getSsl() { |
| return ssl; |
| } |
| |
| public void setSsl(Ssl ssl) { |
| this.ssl = ssl; |
| } |
| |
| public Websocket getWebsocket() { |
| return this.websocket; |
| } |
| |
| public void setWebsocket(Websocket websocket) { |
| this.websocket = websocket; |
| } |
| |
| public boolean isWiretap() { |
| return this.wiretap; |
| } |
| |
| public void setWiretap(boolean wiretap) { |
| this.wiretap = wiretap; |
| } |
| |
| public boolean isCompression() { |
| return compression; |
| } |
| |
| public void setCompression(boolean compression) { |
| this.compression = compression; |
| } |
| |
| @Override |
| public String toString() { |
| // @formatter:off |
| return new ToStringCreator(this) |
| .append("connectTimeout", connectTimeout) |
| .append("responseTimeout", responseTimeout) |
| .append("maxHeaderSize", maxHeaderSize) |
| .append("maxInitialLineLength", maxInitialLineLength) |
| .append("pool", pool) |
| .append("proxy", proxy) |
| .append("ssl", ssl) |
| .append("websocket", websocket) |
| .append("wiretap", wiretap) |
| .append("compression", compression) |
| .toString(); |
| // @formatter:on |
| |
| } |
| |
| public static class Pool { |
| |
| /** 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 = ConnectionProvider.DEFAULT_POOL_MAX_CONNECTIONS; |
| |
| /** Only for type FIXED, the maximum time in millis to wait for aquiring. */ |
| private Long acquireTimeout = ConnectionProvider.DEFAULT_POOL_ACQUIRE_TIMEOUT; |
| |
| /** |
| * Time in millis after which the channel will be closed. If NULL, there is no max |
| * idle time. |
| */ |
| private Duration maxIdleTime = null; |
| |
| /** |
| * Duration after which the channel will be closed. If NULL, there is no max life |
| * time. |
| */ |
| private Duration maxLifeTime = null; |
| |
| /** |
| * Perform regular eviction checks in the background at a specified interval. |
| * Disabled by default ({@link Duration#ZERO}) |
| */ |
| private Duration evictionInterval = Duration.ZERO; |
| |
| public PoolType getType() { |
| return type; |
| } |
| |
| public void setType(PoolType type) { |
| this.type = type; |
| } |
| |
| public String getName() { |
| return name; |
| } |
| |
| public void setName(String name) { |
| this.name = name; |
| } |
| |
| public Integer getMaxConnections() { |
| return maxConnections; |
| } |
| |
| public void setMaxConnections(Integer maxConnections) { |
| this.maxConnections = maxConnections; |
| } |
| |
| public Long getAcquireTimeout() { |
| return acquireTimeout; |
| } |
| |
| public void setAcquireTimeout(Long acquireTimeout) { |
| this.acquireTimeout = acquireTimeout; |
| } |
| |
| public Duration getMaxIdleTime() { |
| return maxIdleTime; |
| } |
| |
| public void setMaxIdleTime(Duration maxIdleTime) { |
| this.maxIdleTime = maxIdleTime; |
| } |
| |
| public Duration getMaxLifeTime() { |
| return maxLifeTime; |
| } |
| |
| public void setMaxLifeTime(Duration maxLifeTime) { |
| this.maxLifeTime = maxLifeTime; |
| } |
| |
| public Duration getEvictionInterval() { |
| return evictionInterval; |
| } |
| |
| public void setEvictionInterval(Duration evictionInterval) { |
| this.evictionInterval = evictionInterval; |
| } |
| |
| @Override |
| public String toString() { |
| return "Pool{" + "type=" + type + ", name='" + name + '\'' + ", maxConnections=" + maxConnections |
| + ", acquireTimeout=" + acquireTimeout + ", maxIdleTime=" + maxIdleTime + ", maxLifeTime=" |
| + maxLifeTime + ", evictionInterval=" + evictionInterval + '}'; |
| } |
| |
| public enum PoolType { |
| |
| /** |
| * Elastic pool type. |
| */ |
| ELASTIC, |
| |
| /** |
| * Fixed pool type. |
| */ |
| FIXED, |
| |
| /** |
| * Disabled pool type. |
| */ |
| DISABLED |
| |
| } |
| |
| } |
| |
| public static class Proxy { |
| |
| /** proxyType for proxy configuration of Netty HttpClient. */ |
| private ProxyProvider.Proxy type = ProxyProvider.Proxy.HTTP; |
| |
| /** 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; |
| |
| public ProxyProvider.Proxy getType() { |
| return type; |
| } |
| |
| public void setType(ProxyProvider.Proxy type) { |
| this.type = type; |
| } |
| |
| public String getHost() { |
| return host; |
| } |
| |
| public void setHost(String host) { |
| this.host = host; |
| } |
| |
| public Integer getPort() { |
| return port; |
| } |
| |
| public void setPort(Integer port) { |
| this.port = port; |
| } |
| |
| public String getUsername() { |
| return username; |
| } |
| |
| public void setUsername(String username) { |
| this.username = username; |
| } |
| |
| public String getPassword() { |
| return password; |
| } |
| |
| public void setPassword(String password) { |
| this.password = password; |
| } |
| |
| public String getNonProxyHostsPattern() { |
| return nonProxyHostsPattern; |
| } |
| |
| public void setNonProxyHostsPattern(String nonProxyHostsPattern) { |
| this.nonProxyHostsPattern = nonProxyHostsPattern; |
| } |
| |
| @Override |
| public String toString() { |
| return "Proxy{" + "type='" + type + '\'' + "host='" + host + '\'' + ", port=" + port + ", username='" |
| + username + '\'' + ", password='" + password + '\'' + ", nonProxyHostsPattern='" |
| + nonProxyHostsPattern + '\'' + '}'; |
| } |
| |
| } |
| |
| public static class Ssl { |
| |
| /** |
| * Installs the netty InsecureTrustManagerFactory. This is insecure and not |
| * suitable for production. |
| */ |
| private boolean useInsecureTrustManager = false; |
| |
| /** Trusted certificates for verifying the remote endpoint's certificate. */ |
| private List<String> trustedX509Certificates = new ArrayList<>(); |
| |
| // use netty default SSL timeouts |
| /** SSL handshake timeout. Default to 10000 ms */ |
| private Duration handshakeTimeout = Duration.ofMillis(10000); |
| |
| /** SSL close_notify flush timeout. Default to 3000 ms. */ |
| private Duration closeNotifyFlushTimeout = Duration.ofMillis(3000); |
| |
| /** SSL close_notify read timeout. Default to 0 ms. */ |
| private Duration closeNotifyReadTimeout = Duration.ZERO; |
| |
| /** The default ssl configuration type. Defaults to TCP. */ |
| private SslProvider.DefaultConfigurationType defaultConfigurationType = SslProvider.DefaultConfigurationType.TCP; |
| |
| /** Keystore path for Netty HttpClient. */ |
| private String keyStore; |
| |
| /** Keystore type for Netty HttpClient, default is JKS. */ |
| private String keyStoreType = "JKS"; |
| |
| /** Keystore provider for Netty HttpClient, optional field. */ |
| private String keyStoreProvider; |
| |
| /** Keystore password. */ |
| private String keyStorePassword; |
| |
| /** Key password, default is same as keyStorePassword. */ |
| private String keyPassword; |
| |
| public String getKeyStorePassword() { |
| return keyStorePassword; |
| } |
| |
| public void setKeyStorePassword(String keyStorePassword) { |
| this.keyStorePassword = keyStorePassword; |
| } |
| |
| public String getKeyStoreType() { |
| return keyStoreType; |
| } |
| |
| public void setKeyStoreType(String keyStoreType) { |
| this.keyStoreType = keyStoreType; |
| } |
| |
| public String getKeyStoreProvider() { |
| return keyStoreProvider; |
| } |
| |
| public void setKeyStoreProvider(String keyStoreProvider) { |
| this.keyStoreProvider = keyStoreProvider; |
| } |
| |
| public String getKeyStore() { |
| return keyStore; |
| } |
| |
| public void setKeyStore(String keyStore) { |
| this.keyStore = keyStore; |
| } |
| |
| public String getKeyPassword() { |
| return keyPassword; |
| } |
| |
| public void setKeyPassword(String keyPassword) { |
| this.keyPassword = keyPassword; |
| } |
| |
| public List<String> getTrustedX509Certificates() { |
| return trustedX509Certificates; |
| } |
| |
| public void setTrustedX509Certificates(List<String> trustedX509) { |
| this.trustedX509Certificates = trustedX509; |
| } |
| |
| public X509Certificate[] getTrustedX509CertificatesForTrustManager() { |
| try { |
| CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); |
| ArrayList<Certificate> allCerts = new ArrayList<>(); |
| for (String trustedCert : getTrustedX509Certificates()) { |
| try { |
| URL url = ResourceUtils.getURL(trustedCert); |
| Collection<? extends Certificate> certs = certificateFactory |
| .generateCertificates(url.openStream()); |
| allCerts.addAll(certs); |
| } |
| catch (IOException e) { |
| throw new WebServerException("Could not load certificate '" + trustedCert + "'", e); |
| } |
| } |
| return allCerts.toArray(new X509Certificate[allCerts.size()]); |
| } |
| catch (CertificateException e1) { |
| throw new WebServerException("Could not load CertificateFactory X.509", e1); |
| } |
| } |
| |
| public KeyManagerFactory getKeyManagerFactory() { |
| try { |
| if (getKeyStore() != null && getKeyStore().length() > 0) { |
| KeyManagerFactory keyManagerFactory = KeyManagerFactory |
| .getInstance(KeyManagerFactory.getDefaultAlgorithm()); |
| char[] keyPassword = getKeyPassword() != null ? getKeyPassword().toCharArray() : null; |
| |
| if (keyPassword == null && getKeyStorePassword() != null) { |
| keyPassword = getKeyStorePassword().toCharArray(); |
| } |
| |
| keyManagerFactory.init(this.createKeyStore(), keyPassword); |
| |
| return keyManagerFactory; |
| } |
| |
| return null; |
| } |
| catch (Exception e) { |
| throw new IllegalStateException(e); |
| } |
| } |
| |
| public KeyStore createKeyStore() { |
| try { |
| KeyStore store = getKeyStoreProvider() != null |
| ? KeyStore.getInstance(getKeyStoreType(), getKeyStoreProvider()) |
| : KeyStore.getInstance(getKeyStoreType()); |
| try { |
| URL url = ResourceUtils.getURL(getKeyStore()); |
| store.load(url.openStream(), |
| getKeyStorePassword() != null ? getKeyStorePassword().toCharArray() : null); |
| } |
| catch (Exception e) { |
| throw new WebServerException("Could not load key store ' " + getKeyStore() + "'", e); |
| } |
| |
| return store; |
| } |
| catch (KeyStoreException | NoSuchProviderException e) { |
| throw new WebServerException("Could not load KeyStore for given type and provider", e); |
| } |
| } |
| |
| // TODO: support configuration of other trust manager factories |
| |
| public boolean isUseInsecureTrustManager() { |
| return useInsecureTrustManager; |
| } |
| |
| public void setUseInsecureTrustManager(boolean useInsecureTrustManager) { |
| this.useInsecureTrustManager = useInsecureTrustManager; |
| } |
| |
| public Duration getHandshakeTimeout() { |
| return handshakeTimeout; |
| } |
| |
| public void setHandshakeTimeout(Duration handshakeTimeout) { |
| this.handshakeTimeout = handshakeTimeout; |
| } |
| |
| public Duration getCloseNotifyFlushTimeout() { |
| return closeNotifyFlushTimeout; |
| } |
| |
| public void setCloseNotifyFlushTimeout(Duration closeNotifyFlushTimeout) { |
| this.closeNotifyFlushTimeout = closeNotifyFlushTimeout; |
| } |
| |
| public Duration getCloseNotifyReadTimeout() { |
| return closeNotifyReadTimeout; |
| } |
| |
| public void setCloseNotifyReadTimeout(Duration closeNotifyReadTimeout) { |
| this.closeNotifyReadTimeout = closeNotifyReadTimeout; |
| } |
| |
| public SslProvider.DefaultConfigurationType getDefaultConfigurationType() { |
| return defaultConfigurationType; |
| } |
| |
| public void setDefaultConfigurationType(SslProvider.DefaultConfigurationType defaultConfigurationType) { |
| this.defaultConfigurationType = defaultConfigurationType; |
| } |
| |
| @Override |
| public String toString() { |
| return new ToStringCreator(this).append("useInsecureTrustManager", useInsecureTrustManager) |
| .append("trustedX509Certificates", trustedX509Certificates) |
| .append("handshakeTimeout", handshakeTimeout) |
| .append("closeNotifyFlushTimeout", closeNotifyFlushTimeout) |
| .append("closeNotifyReadTimeout", closeNotifyReadTimeout) |
| .append("defaultConfigurationType", defaultConfigurationType).toString(); |
| } |
| |
| } |
| |
| public static class Websocket { |
| |
| /** Max frame payload length. */ |
| private Integer maxFramePayloadLength; |
| |
| /** Proxy ping frames to downstream services, defaults to true. */ |
| private boolean proxyPing = true; |
| |
| public Integer getMaxFramePayloadLength() { |
| return this.maxFramePayloadLength; |
| } |
| |
| public void setMaxFramePayloadLength(Integer maxFramePayloadLength) { |
| this.maxFramePayloadLength = maxFramePayloadLength; |
| } |
| |
| public boolean isProxyPing() { |
| return proxyPing; |
| } |
| |
| public void setProxyPing(boolean proxyPing) { |
| this.proxyPing = proxyPing; |
| } |
| |
| @Override |
| public String toString() { |
| return new ToStringCreator(this).append("maxFramePayloadLength", maxFramePayloadLength) |
| .append("proxyPing", proxyPing).toString(); |
| } |
| |
| } |
| // CHECKSTYLE:ON |
| } |