From b10d43f2bb810913c421eb1ad6fe08b277692750 Mon Sep 17 00:00:00 2001 From: Oleg Kalnichevski Date: Sun, 12 Sep 2021 14:54:32 +0200 Subject: [PATCH] HTTPCLIENT-2135: TLS configuration on a per-host basis --- .../async/TestHttp1RequestReExecution.java | 7 +- .../testing/async/TestHttpAsyncMinimal.java | 28 +-- .../async/TestHttpMinimalReactive.java | 28 +-- ...chingHttpAsyncClientCompatibilityTest.java | 14 +- .../HttpAsyncClientCompatibilityTest.java | 5 +- .../sync/TestConnectionManagement.java | 14 +- .../client5/http/config/ConnectionConfig.java | 7 +- .../hc/client5/http/config/TlsConfig.java | 203 ++++++++++++++++++ .../impl/async/HttpAsyncClientBuilder.java | 15 +- .../HttpAsyncClientEventHandlerFactory.java | 7 +- .../http/impl/async/HttpAsyncClients.java | 65 +++++- .../impl/async/InternalHttpAsyncClient.java | 10 +- .../async/InternalHttpAsyncExecRuntime.java | 20 +- .../impl/async/MinimalHttpAsyncClient.java | 12 +- .../io/BasicHttpClientConnectionManager.java | 21 +- .../DefaultHttpClientConnectionOperator.java | 34 ++- .../PoolingHttpClientConnectionManager.java | 51 +++-- ...ingHttpClientConnectionManagerBuilder.java | 25 +++ .../DefaultAsyncClientConnectionOperator.java | 9 +- .../PoolingAsyncClientConnectionManager.java | 38 +++- ...ngAsyncClientConnectionManagerBuilder.java | 24 +++ .../http/io/HttpClientConnectionOperator.java | 44 ++++ .../nio/AsyncClientConnectionOperator.java | 1 - .../http/socket/ConnectionSocketFactory.java | 32 +++ .../LayeredConnectionSocketFactory.java | 26 +++ .../http/ssl/AbstractClientTlsStrategy.java | 12 +- .../http/ssl/SSLConnectionSocketFactory.java | 50 ++++- .../AsyncClientFullDuplexExchange.java | 9 +- .../AsyncClientH2FullDuplexExchange.java | 17 +- .../examples/AsyncClientH2Multiplexing.java | 17 +- .../examples/AsyncClientH2ServerPush.java | 23 +- .../examples/AsyncClientHttp1Pipelining.java | 17 +- .../http/examples/AsyncClientTlsAlpn.java | 2 - .../ReactiveClientFullDuplexExchange.java | 9 +- .../TestBasicHttpClientConnectionManager.java | 65 +++++- .../io/TestHttpClientConnectionOperator.java | 39 +++- ...estPoolingHttpClientConnectionManager.java | 64 +++++- 37 files changed, 866 insertions(+), 198 deletions(-) create mode 100644 httpclient5/src/main/java/org/apache/hc/client5/http/config/TlsConfig.java diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp1RequestReExecution.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp1RequestReExecution.java index 4a7f00278..cb7a46588 100644 --- a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp1RequestReExecution.java +++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp1RequestReExecution.java @@ -34,6 +34,7 @@ import org.apache.hc.client5.http.async.methods.SimpleHttpResponse; import org.apache.hc.client5.http.async.methods.SimpleRequestBuilder; import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.config.TlsConfig; import org.apache.hc.client5.http.impl.DefaultHttpRequestRetryStrategy; import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient; import org.apache.hc.client5.http.impl.async.HttpAsyncClientBuilder; @@ -102,12 +103,14 @@ protected void after() { @Override protected void before() throws Throwable { + connManager.setDefaultTlsConfig(TlsConfig.custom() + .setVersionPolicy(version.greaterEquals(HttpVersion.HTTP_2) ? HttpVersionPolicy.FORCE_HTTP_2 : HttpVersionPolicy.FORCE_HTTP_1) + .build()); clientBuilder = HttpAsyncClientBuilder.create() .setDefaultRequestConfig(RequestConfig.custom() .setConnectionRequestTimeout(TIMEOUT) .build()) - .setConnectionManager(connManager) - .setVersionPolicy(version.greaterEquals(HttpVersion.HTTP_2) ? HttpVersionPolicy.FORCE_HTTP_2 : HttpVersionPolicy.FORCE_HTTP_1); + .setConnectionManager(connManager); } }; diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttpAsyncMinimal.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttpAsyncMinimal.java index 5c5c073af..d4ccb3179 100644 --- a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttpAsyncMinimal.java +++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttpAsyncMinimal.java @@ -34,9 +34,9 @@ import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; +import org.apache.hc.client5.http.config.TlsConfig; import org.apache.hc.client5.http.impl.async.HttpAsyncClients; import org.apache.hc.client5.http.impl.async.MinimalHttpAsyncClient; -import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManager; import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBuilder; import org.apache.hc.client5.http.protocol.HttpClientContext; import org.apache.hc.client5.http.ssl.DefaultClientTlsStrategy; @@ -85,19 +85,19 @@ public TestHttpAsyncMinimal(final HttpVersion version, final URIScheme scheme) { @Override protected MinimalHttpAsyncClient createClient() throws Exception { - final PoolingAsyncClientConnectionManager connectionManager = PoolingAsyncClientConnectionManagerBuilder.create() - .setTlsStrategy(new DefaultClientTlsStrategy(SSLTestContexts.createClientSSLContext())) - .build(); - final IOReactorConfig ioReactorConfig = IOReactorConfig.custom() - .setSoTimeout(TIMEOUT) - .build(); - if (version.greaterEquals(HttpVersion.HTTP_2)) { - return HttpAsyncClients.createMinimal( - HttpVersionPolicy.FORCE_HTTP_2, H2Config.DEFAULT, Http1Config.DEFAULT, ioReactorConfig, connectionManager); - } else { - return HttpAsyncClients.createMinimal( - HttpVersionPolicy.FORCE_HTTP_1, H2Config.DEFAULT, Http1Config.DEFAULT, ioReactorConfig, connectionManager); - } + return HttpAsyncClients.createMinimal( + H2Config.DEFAULT, + Http1Config.DEFAULT, + IOReactorConfig.custom() + .setSoTimeout(TIMEOUT) + .build(), + PoolingAsyncClientConnectionManagerBuilder.create() + .setTlsStrategy(new DefaultClientTlsStrategy(SSLTestContexts.createClientSSLContext())) + .setDefaultTlsConfig(TlsConfig.custom() + .setVersionPolicy(version.greaterEquals(HttpVersion.HTTP_2) + ? HttpVersionPolicy.FORCE_HTTP_2 : HttpVersionPolicy.FORCE_HTTP_1) + .build()) + .build()); } @Override diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttpMinimalReactive.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttpMinimalReactive.java index 5cbad4354..24749dcb5 100644 --- a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttpMinimalReactive.java +++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttpMinimalReactive.java @@ -34,9 +34,9 @@ import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; +import org.apache.hc.client5.http.config.TlsConfig; import org.apache.hc.client5.http.impl.async.HttpAsyncClients; import org.apache.hc.client5.http.impl.async.MinimalHttpAsyncClient; -import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManager; import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBuilder; import org.apache.hc.client5.http.protocol.HttpClientContext; import org.apache.hc.client5.http.ssl.DefaultClientTlsStrategy; @@ -85,19 +85,19 @@ public TestHttpMinimalReactive(final HttpVersion version, final URIScheme scheme @Override protected MinimalHttpAsyncClient createClient() throws Exception { - final PoolingAsyncClientConnectionManager connectionManager = PoolingAsyncClientConnectionManagerBuilder.create() - .setTlsStrategy(new DefaultClientTlsStrategy(SSLTestContexts.createClientSSLContext())) - .build(); - final IOReactorConfig ioReactorConfig = IOReactorConfig.custom() - .setSoTimeout(TIMEOUT) - .build(); - if (version.greaterEquals(HttpVersion.HTTP_2)) { - return HttpAsyncClients.createMinimal( - HttpVersionPolicy.FORCE_HTTP_2, H2Config.DEFAULT, Http1Config.DEFAULT, ioReactorConfig, connectionManager); - } else { - return HttpAsyncClients.createMinimal( - HttpVersionPolicy.FORCE_HTTP_1, H2Config.DEFAULT, Http1Config.DEFAULT, ioReactorConfig, connectionManager); - } + return HttpAsyncClients.createMinimal( + H2Config.DEFAULT, + Http1Config.DEFAULT, + IOReactorConfig.custom() + .setSoTimeout(TIMEOUT) + .build(), + PoolingAsyncClientConnectionManagerBuilder.create() + .setTlsStrategy(new DefaultClientTlsStrategy(SSLTestContexts.createClientSSLContext())) + .setDefaultTlsConfig(TlsConfig.custom() + .setVersionPolicy(version.greaterEquals(HttpVersion.HTTP_2) + ? HttpVersionPolicy.FORCE_HTTP_2 : HttpVersionPolicy.FORCE_HTTP_1) + .build()) + .build()); } @Override diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/external/CachingHttpAsyncClientCompatibilityTest.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/external/CachingHttpAsyncClientCompatibilityTest.java index 72a219da9..c4f309027 100644 --- a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/external/CachingHttpAsyncClientCompatibilityTest.java +++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/external/CachingHttpAsyncClientCompatibilityTest.java @@ -35,13 +35,12 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -import javax.net.ssl.SSLContext; - import org.apache.hc.client5.http.async.methods.SimpleHttpRequest; import org.apache.hc.client5.http.async.methods.SimpleHttpResponse; import org.apache.hc.client5.http.async.methods.SimpleRequestBuilder; import org.apache.hc.client5.http.cache.CacheResponseStatus; import org.apache.hc.client5.http.cache.HttpCacheContext; +import org.apache.hc.client5.http.config.TlsConfig; import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient; import org.apache.hc.client5.http.impl.cache.CacheConfig; import org.apache.hc.client5.http.impl.cache.CachingHttpAsyncClients; @@ -88,17 +87,20 @@ HttpVersion.HTTP_2_0, new HttpHost("http", "localhost", 8080)) CachingHttpAsyncClientCompatibilityTest(final HttpVersion protocolVersion, final HttpHost target) throws Exception { this.protocolVersion = protocolVersion; this.target = target; - final SSLContext sslContext = SSLContexts.custom() - .loadTrustMaterial(getClass().getResource("/test-ca.keystore"), "nopassword".toCharArray()).build(); this.connManager = PoolingAsyncClientConnectionManagerBuilder.create() - .setTlsStrategy(new DefaultClientTlsStrategy(sslContext)) + .setTlsStrategy(new DefaultClientTlsStrategy(SSLContexts.custom() + .loadTrustMaterial(getClass().getResource("/test-ca.keystore"), "nopassword".toCharArray()) + .build())) + .setDefaultTlsConfig(TlsConfig.custom() + .setVersionPolicy(this.protocolVersion == HttpVersion.HTTP_2 ? + HttpVersionPolicy.FORCE_HTTP_2 : HttpVersionPolicy.FORCE_HTTP_1) + .build()) .build(); this.client = CachingHttpAsyncClients.custom() .setCacheConfig(CacheConfig.custom() .setMaxObjectSize(20480) .build()) .setResourceFactory(HeapResourceFactory.INSTANCE) - .setVersionPolicy(this.protocolVersion == HttpVersion.HTTP_2 ? HttpVersionPolicy.FORCE_HTTP_2 : HttpVersionPolicy.FORCE_HTTP_1) .setConnectionManager(this.connManager) .build(); } diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/external/HttpAsyncClientCompatibilityTest.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/external/HttpAsyncClientCompatibilityTest.java index c5903c5a4..ee3317be0 100644 --- a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/external/HttpAsyncClientCompatibilityTest.java +++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/external/HttpAsyncClientCompatibilityTest.java @@ -40,6 +40,7 @@ import org.apache.hc.client5.http.auth.Credentials; import org.apache.hc.client5.http.auth.UsernamePasswordCredentials; import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.config.TlsConfig; import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient; import org.apache.hc.client5.http.impl.async.HttpAsyncClients; import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider; @@ -139,9 +140,11 @@ public static void main(final String... args) throws Exception { .loadTrustMaterial(getClass().getResource("/test-ca.keystore"), "nopassword".toCharArray()).build(); this.connManager = PoolingAsyncClientConnectionManagerBuilder.create() .setTlsStrategy(new DefaultClientTlsStrategy(sslContext)) + .setDefaultTlsConfig(TlsConfig.custom() + .setVersionPolicy(versionPolicy) + .build()) .build(); this.client = HttpAsyncClients.custom() - .setVersionPolicy(this.versionPolicy) .setConnectionManager(this.connManager) .setProxy(this.proxy) .setDefaultRequestConfig(requestConfig) diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/sync/TestConnectionManagement.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/sync/TestConnectionManagement.java index 9b1f8fd11..824e4fa57 100644 --- a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/sync/TestConnectionManagement.java +++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/sync/TestConnectionManagement.java @@ -82,7 +82,7 @@ public void testReleaseConnection() throws Exception { final LeaseRequest leaseRequest1 = this.connManager.lease("id1", route,null); final ConnectionEndpoint endpoint1 = leaseRequest1.get(Timeout.ZERO_MILLISECONDS); - this.connManager.connect(endpoint1, TimeValue.NEG_ONE_MILLISECOND, context); + this.connManager.connect(endpoint1, null, context); final HttpProcessor httpProcessor = new DefaultHttpProcessor( new RequestTargetHost(), new RequestContent(), new RequestConnControl()); @@ -104,7 +104,7 @@ public void testReleaseConnection() throws Exception { final ConnectionEndpoint endpoint2 = leaseRequest3.get(Timeout.ZERO_MILLISECONDS); Assert.assertFalse(endpoint2.isConnected()); - this.connManager.connect(endpoint2, TimeValue.NEG_ONE_MILLISECOND, context); + this.connManager.connect(endpoint2, null, context); try (final ClassicHttpResponse response2 = endpoint2.execute("id2", request, exec, context)) { Assert.assertEquals(HttpStatus.SC_OK, response2.getCode()); @@ -145,7 +145,7 @@ public void testReleaseConnectionWithTimeLimits() throws Exception { final LeaseRequest leaseRequest1 = this.connManager.lease("id1", route,null); final ConnectionEndpoint endpoint1 = leaseRequest1.get(Timeout.ZERO_MILLISECONDS); - this.connManager.connect(endpoint1, TimeValue.NEG_ONE_MILLISECOND, context); + this.connManager.connect(endpoint1, null, context); final HttpProcessor httpProcessor = new DefaultHttpProcessor( new RequestTargetHost(), new RequestContent(), new RequestConnControl()); @@ -168,7 +168,7 @@ public void testReleaseConnectionWithTimeLimits() throws Exception { final ConnectionEndpoint endpoint2 = leaseRequest3.get(Timeout.ZERO_MILLISECONDS); Assert.assertFalse(endpoint2.isConnected()); - this.connManager.connect(endpoint2, TimeValue.NEG_ONE_MILLISECOND, context); + this.connManager.connect(endpoint2, null, context); try (final ClassicHttpResponse response2 = endpoint2.execute("id2", request, exec, context)) { Assert.assertEquals(HttpStatus.SC_OK, response2.getCode()); @@ -193,7 +193,7 @@ public void testReleaseConnectionWithTimeLimits() throws Exception { Assert.assertFalse(endpoint4.isConnected()); // repeat the communication, no need to prepare the request again - this.connManager.connect(endpoint4, TimeValue.NEG_ONE_MILLISECOND, context); + this.connManager.connect(endpoint4, null, context); try (final ClassicHttpResponse response4 = endpoint4.execute("id4", request, exec, context)) { Assert.assertEquals(HttpStatus.SC_OK, response4.getCode()); @@ -213,7 +213,7 @@ public void testCloseExpiredIdleConnections() throws Exception { final LeaseRequest leaseRequest1 = this.connManager.lease("id1", route,null); final ConnectionEndpoint endpoint1 = leaseRequest1.get(Timeout.ZERO_MILLISECONDS); - this.connManager.connect(endpoint1, TimeValue.NEG_ONE_MILLISECOND, context); + this.connManager.connect(endpoint1, null, context); Assert.assertEquals(1, this.connManager.getTotalStats().getLeased()); Assert.assertEquals(1, this.connManager.getStats(route).getLeased()); @@ -262,7 +262,7 @@ public void testCloseExpiredTTLConnections() throws Exception { final LeaseRequest leaseRequest1 = this.connManager.lease("id1", route,null); final ConnectionEndpoint endpoint1 = leaseRequest1.get(Timeout.ZERO_MILLISECONDS); - this.connManager.connect(endpoint1, TimeValue.NEG_ONE_MILLISECOND, context); + this.connManager.connect(endpoint1, null, context); Assert.assertEquals(1, this.connManager.getTotalStats().getLeased()); Assert.assertEquals(1, this.connManager.getStats(route).getLeased()); diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/config/ConnectionConfig.java b/httpclient5/src/main/java/org/apache/hc/client5/http/config/ConnectionConfig.java index 97ed58521..38ead6f53 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/config/ConnectionConfig.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/config/ConnectionConfig.java @@ -126,7 +126,6 @@ public static class Builder { this.connectTimeout = DEFAULT_CONNECT_TIMEOUT; } - /** * @see #setSocketTimeout(Timeout) */ @@ -138,7 +137,7 @@ public Builder setSocketTimeout(final int soTimeout, final TimeUnit timeUnit) { /** * Determines the default socket timeout value for I/O operations. *

- * Default: {@code null} + * Default: {@code null} (undefined) *

* * @return the default socket timeout value for I/O operations. @@ -150,8 +149,6 @@ public Builder setSocketTimeout(final Timeout soTimeout) { /** * Determines the timeout until a new connection is fully established. - * This may also include transport security negotiation exchanges - * such as {@code SSL} or {@code TLS} protocol negotiation). *

* A timeout value of zero is interpreted as an infinite timeout. *

@@ -177,7 +174,7 @@ public Builder setConnectTimeout(final long connectTimeout, final TimeUnit timeU * be re-validated prior to being leased to the consumer. Negative values passed * to this method disable connection validation. *

- * Default: {@code null} + * Default: {@code null} (undefined) *

*/ public Builder setValidateAfterInactivity(final TimeValue validateAfterInactivity) { diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/config/TlsConfig.java b/httpclient5/src/main/java/org/apache/hc/client5/http/config/TlsConfig.java new file mode 100644 index 000000000..7a014e263 --- /dev/null +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/config/TlsConfig.java @@ -0,0 +1,203 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * http://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. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ + +package org.apache.hc.client5.http.config; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import org.apache.hc.core5.annotation.Contract; +import org.apache.hc.core5.annotation.ThreadingBehavior; +import org.apache.hc.core5.http2.HttpVersionPolicy; +import org.apache.hc.core5.util.Timeout; + +/** + * Immutable class encapsulating TLS protocol settings. + * + * @since 5.2 + */ +@Contract(threading = ThreadingBehavior.IMMUTABLE) +public class TlsConfig implements Cloneable { + + public static final TlsConfig DEFAULT = new Builder().build(); + + private final Timeout handshakeTimeout; + private final String[] supportedProtocols; + private final String[] supportedCipherSuites; + private final HttpVersionPolicy httpVersionPolicy; + + /** + * Intended for CDI compatibility + */ + protected TlsConfig() { + this(null, null, null, null); + } + + TlsConfig( + final Timeout handshakeTimeout, + final String[] supportedProtocols, + final String[] supportedCipherSuites, + final HttpVersionPolicy httpVersionPolicy) { + super(); + this.handshakeTimeout = handshakeTimeout; + this.supportedProtocols = supportedProtocols; + this.supportedCipherSuites = supportedCipherSuites; + this.httpVersionPolicy = httpVersionPolicy; + } + + /** + * @see Builder#setHandshakeTimeout(Timeout) + */ + public Timeout getHandshakeTimeout() { + return handshakeTimeout; + } + + /** + * @see Builder#setSupportedProtocols(String...) + */ + public String[] getSupportedProtocols() { + return supportedProtocols != null ? supportedProtocols.clone() : null; + } + + /** + * @see Builder#setSupportedCipherSuites(String...) + */ + public String[] getSupportedCipherSuites() { + return supportedCipherSuites != null ? supportedCipherSuites.clone() : null; + } + + /** + * @see Builder#setVersionPolicy(HttpVersionPolicy) + */ + public HttpVersionPolicy getHttpVersionPolicy() { + return httpVersionPolicy; + } + + @Override + protected TlsConfig clone() throws CloneNotSupportedException { + return (TlsConfig) super.clone(); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("["); + builder.append("handshakeTimeout=").append(handshakeTimeout); + builder.append(", supportedProtocols=").append(Arrays.asList(supportedProtocols)); + builder.append(", supportedCipherSuites=").append(Arrays.asList(supportedCipherSuites)); + builder.append(", httpVersionPolicy=").append(httpVersionPolicy); + builder.append("]"); + return builder.toString(); + } + + public static TlsConfig.Builder custom() { + return new Builder(); + } + + public static TlsConfig.Builder copy(final TlsConfig config) { + return new Builder() + .setHandshakeTimeout(config.getHandshakeTimeout()) + .setSupportedProtocols(config.getSupportedProtocols()) + .setSupportedCipherSuites(config.getSupportedCipherSuites()) + .setVersionPolicy(config.getHttpVersionPolicy()); + } + + public static class Builder { + + private Timeout handshakeTimeout; + private String[] supportedProtocols; + private String[] supportedCipherSuites; + private HttpVersionPolicy versionPolicy; + + /** + * Determines the timeout used by TLS session negotiation exchanges (session handshake). + *

+ * A timeout value of zero is interpreted as an infinite timeout. + *

+ *

+ * Default: {@code null} (undefined) + *

+ */ + public Builder setHandshakeTimeout(final Timeout handshakeTimeout) { + this.handshakeTimeout = handshakeTimeout; + return this; + } + + /** + * @see #setHandshakeTimeout(Timeout) + */ + public Builder setHandshakeTimeout(final long handshakeTimeout, final TimeUnit timeUnit) { + this.handshakeTimeout = Timeout.of(handshakeTimeout, timeUnit); + return this; + } + + /** + * Determines supported TLS protocols. + *

+ * Default: {@code null} (undefined) + *

+ */ + public Builder setSupportedProtocols(final String... supportedProtocols) { + this.supportedProtocols = supportedProtocols; + return this; + } + + /** + * Determines supported cipher suites. + *

+ * Default: {@code null} (undefined) + *

+ */ + public Builder setSupportedCipherSuites(final String... supportedCipherSuites) { + this.supportedCipherSuites = supportedCipherSuites; + return this; + } + + /** + * Determines the HTTP protocol policy. By default, connections are expected to use TLS ALPN + * extension to negotiate the application protocol to be used by both endpoints. + *

+ *

+ * Default: {@link HttpVersionPolicy#NEGOTIATE} + *

+ */ + public Builder setVersionPolicy(final HttpVersionPolicy versionPolicy) { + this.versionPolicy = versionPolicy; + return this; + } + + public TlsConfig build() { + return new TlsConfig( + handshakeTimeout, + supportedProtocols, + supportedCipherSuites, + versionPolicy != null ? versionPolicy : HttpVersionPolicy.NEGOTIATE); + } + + } + +} diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClientBuilder.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClientBuilder.java index 6127ce08e..394a228fc 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClientBuilder.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClientBuilder.java @@ -47,6 +47,7 @@ import org.apache.hc.client5.http.auth.CredentialsProvider; import org.apache.hc.client5.http.auth.StandardAuthScheme; import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.config.TlsConfig; import org.apache.hc.client5.http.cookie.BasicCookieStore; import org.apache.hc.client5.http.cookie.CookieSpecFactory; import org.apache.hc.client5.http.cookie.CookieStore; @@ -196,7 +197,11 @@ private ExecInterceptorEntry( } - private HttpVersionPolicy versionPolicy; + /** + * @deprecated TLS should be configured by the connection manager + */ + @Deprecated + private TlsConfig tlsConfig; private AsyncClientConnectionManager connManager; private boolean connManagerShared; private IOReactorConfig ioReactorConfig; @@ -254,9 +259,12 @@ protected HttpAsyncClientBuilder() { /** * Sets HTTP protocol version policy. + * + * @deprecated Use {@link TlsConfig} and connection nanager methods */ + @Deprecated public final HttpAsyncClientBuilder setVersionPolicy(final HttpVersionPolicy versionPolicy) { - this.versionPolicy = versionPolicy; + this.tlsConfig = versionPolicy != null ? TlsConfig.custom().setVersionPolicy(versionPolicy).build() : null; return this; } @@ -903,7 +911,6 @@ public CloseableHttpAsyncClient build() { final IOEventHandlerFactory ioEventHandlerFactory = new HttpAsyncClientEventHandlerFactory( new DefaultHttpProcessor(new H2RequestContent(), new H2RequestTargetHost(), new H2RequestConnControl()), (request, context) -> pushConsumerRegistry.get(request), - versionPolicy != null ? versionPolicy : HttpVersionPolicy.NEGOTIATE, h2Config != null ? h2Config : H2Config.DEFAULT, h1Config != null ? h1Config : Http1Config.DEFAULT, charCodingConfig != null ? charCodingConfig : CharCodingConfig.DEFAULT, @@ -986,7 +993,7 @@ public CloseableHttpAsyncClient build() { threadFactory != null ? threadFactory : new DefaultThreadFactory("httpclient-main", true), connManagerCopy, routePlannerCopy, - versionPolicy != null ? versionPolicy : HttpVersionPolicy.NEGOTIATE, + tlsConfig, cookieSpecRegistryCopy, authSchemeRegistryCopy, cookieStoreCopy, diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClientEventHandlerFactory.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClientEventHandlerFactory.java index e543421a0..15e98de5b 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClientEventHandlerFactory.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClientEventHandlerFactory.java @@ -74,7 +74,6 @@ class HttpAsyncClientEventHandlerFactory implements IOEventHandlerFactory { private final HttpProcessor httpProcessor; private final HandlerFactory exchangeHandlerFactory; - private final HttpVersionPolicy versionPolicy; private final H2Config h2Config; private final Http1Config h1Config; private final CharCodingConfig charCodingConfig; @@ -85,14 +84,12 @@ class HttpAsyncClientEventHandlerFactory implements IOEventHandlerFactory { HttpAsyncClientEventHandlerFactory( final HttpProcessor httpProcessor, final HandlerFactory exchangeHandlerFactory, - final HttpVersionPolicy versionPolicy, final H2Config h2Config, final Http1Config h1Config, final CharCodingConfig charCodingConfig, final ConnectionReuseStrategy connectionReuseStrategy) { this.httpProcessor = Args.notNull(httpProcessor, "HTTP processor"); this.exchangeHandlerFactory = exchangeHandlerFactory; - this.versionPolicy = versionPolicy != null ? versionPolicy : HttpVersionPolicy.NEGOTIATE; this.h2Config = h2Config != null ? h2Config : H2Config.DEFAULT; this.h1Config = h1Config != null ? h1Config : Http1Config.DEFAULT; this.charCodingConfig = charCodingConfig != null ? charCodingConfig : CharCodingConfig.DEFAULT; @@ -238,7 +235,7 @@ public void onOutputFlowControl(final HttpConnection connection, final int strea ioSession, http1StreamHandlerFactory, http2StreamHandlerFactory, - attachment instanceof HttpVersionPolicy ? (HttpVersionPolicy) attachment : versionPolicy); + attachment instanceof HttpVersionPolicy ? (HttpVersionPolicy) attachment : null); } final ClientHttp1StreamDuplexerFactory http1StreamHandlerFactory = new ClientHttp1StreamDuplexerFactory( httpProcessor, @@ -258,7 +255,7 @@ public void onOutputFlowControl(final HttpConnection connection, final int strea ioSession, http1StreamHandlerFactory, http2StreamHandlerFactory, - attachment instanceof HttpVersionPolicy ? (HttpVersionPolicy) attachment : versionPolicy); + attachment instanceof HttpVersionPolicy ? (HttpVersionPolicy) attachment : null); } } diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClients.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClients.java index f7214eda5..ea8ec3a46 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClients.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClients.java @@ -30,6 +30,7 @@ import org.apache.hc.client5.http.DnsResolver; import org.apache.hc.client5.http.SchemePortResolver; import org.apache.hc.client5.http.SystemDefaultDnsResolver; +import org.apache.hc.client5.http.config.TlsConfig; import org.apache.hc.client5.http.impl.DefaultClientConnectionReuseStrategy; import org.apache.hc.client5.http.impl.DefaultSchemePortResolver; import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBuilder; @@ -122,26 +123,29 @@ private static HttpProcessor createMinimalProtocolProcessor() { private static MinimalHttpAsyncClient createMinimalHttpAsyncClientImpl( final IOEventHandlerFactory eventHandlerFactory, final AsyncPushConsumerRegistry pushConsumerRegistry, - final HttpVersionPolicy versionPolicy, final IOReactorConfig ioReactorConfig, final AsyncClientConnectionManager connmgr, - final SchemePortResolver schemePortResolver) { + final SchemePortResolver schemePortResolver, + final TlsConfig tlsConfig) { return new MinimalHttpAsyncClient( eventHandlerFactory, pushConsumerRegistry, - versionPolicy, ioReactorConfig, new DefaultThreadFactory("httpclient-main", true), new DefaultThreadFactory("httpclient-dispatch", true), connmgr, - schemePortResolver); + schemePortResolver, + tlsConfig); } /** * Creates {@link MinimalHttpAsyncClient} instance optimized for * HTTP/1.1 and HTTP/2 message transport without advanced HTTP protocol * functionality. + * + * @deprecated Use {@link #createMinimal(H2Config, Http1Config, IOReactorConfig, AsyncClientConnectionManager)} */ + @Deprecated public static MinimalHttpAsyncClient createMinimal( final HttpVersionPolicy versionPolicy, final H2Config h2Config, @@ -153,16 +157,60 @@ public static MinimalHttpAsyncClient createMinimal( new HttpAsyncClientEventHandlerFactory( createMinimalProtocolProcessor(), (request, context) -> pushConsumerRegistry.get(request), - versionPolicy, h2Config, h1Config, CharCodingConfig.DEFAULT, DefaultClientConnectionReuseStrategy.INSTANCE), pushConsumerRegistry, - versionPolicy, ioReactorConfig, connmgr, - DefaultSchemePortResolver.INSTANCE); + DefaultSchemePortResolver.INSTANCE, + versionPolicy != null ? TlsConfig.custom().setVersionPolicy(versionPolicy).build() : null); + } + + /** + * Creates {@link MinimalHttpAsyncClient} instance optimized for + * HTTP/1.1 and HTTP/2 message transport without advanced HTTP protocol + * functionality. + * + * @since 5.2 + */ + public static MinimalHttpAsyncClient createMinimal( + final H2Config h2Config, + final Http1Config h1Config, + final IOReactorConfig ioReactorConfig, + final AsyncClientConnectionManager connmgr) { + final AsyncPushConsumerRegistry pushConsumerRegistry = new AsyncPushConsumerRegistry(); + return createMinimalHttpAsyncClientImpl( + new HttpAsyncClientEventHandlerFactory( + createMinimalProtocolProcessor(), + (request, context) -> pushConsumerRegistry.get(request), + h2Config, + h1Config, + CharCodingConfig.DEFAULT, + DefaultClientConnectionReuseStrategy.INSTANCE), + pushConsumerRegistry, + ioReactorConfig, + connmgr, + DefaultSchemePortResolver.INSTANCE, + null); + } + + /** + * Creates {@link MinimalHttpAsyncClient} instance optimized for + * HTTP/1.1 and HTTP/2 message transport without advanced HTTP protocol + * functionality. + * + * @deprecated Use {@link #createMinimal(H2Config, Http1Config, IOReactorConfig)} + */ + @Deprecated + public static MinimalHttpAsyncClient createMinimal( + final HttpVersionPolicy versionPolicy, + final H2Config h2Config, + final Http1Config h1Config, + final IOReactorConfig ioReactorConfig) { + return createMinimal(versionPolicy, h2Config, h1Config, ioReactorConfig, + PoolingAsyncClientConnectionManagerBuilder.create().build()); } /** @@ -171,11 +219,10 @@ public static MinimalHttpAsyncClient createMinimal( * functionality. */ public static MinimalHttpAsyncClient createMinimal( - final HttpVersionPolicy versionPolicy, final H2Config h2Config, final Http1Config h1Config, final IOReactorConfig ioReactorConfig) { - return createMinimal(versionPolicy, h2Config, h1Config, ioReactorConfig, + return createMinimal(h2Config, h1Config, ioReactorConfig, PoolingAsyncClientConnectionManagerBuilder.create().build()); } diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalHttpAsyncClient.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalHttpAsyncClient.java index 2dc8a4218..746fe6224 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalHttpAsyncClient.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalHttpAsyncClient.java @@ -35,6 +35,7 @@ import org.apache.hc.client5.http.auth.AuthSchemeFactory; import org.apache.hc.client5.http.auth.CredentialsProvider; import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.config.TlsConfig; import org.apache.hc.client5.http.cookie.CookieSpecFactory; import org.apache.hc.client5.http.cookie.CookieStore; import org.apache.hc.client5.http.nio.AsyncClientConnectionManager; @@ -48,7 +49,6 @@ import org.apache.hc.core5.http.config.Lookup; import org.apache.hc.core5.http.nio.AsyncPushConsumer; import org.apache.hc.core5.http.nio.HandlerFactory; -import org.apache.hc.core5.http2.HttpVersionPolicy; import org.apache.hc.core5.reactor.DefaultConnectingIOReactor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -71,7 +71,7 @@ public final class InternalHttpAsyncClient extends InternalAbstractHttpAsyncClie private static final Logger LOG = LoggerFactory.getLogger(InternalHttpAsyncClient.class); private final AsyncClientConnectionManager manager; private final HttpRoutePlanner routePlanner; - private final HttpVersionPolicy versionPolicy; + private final TlsConfig tlsConfig; InternalHttpAsyncClient( final DefaultConnectingIOReactor ioReactor, @@ -80,7 +80,7 @@ public final class InternalHttpAsyncClient extends InternalAbstractHttpAsyncClie final ThreadFactory threadFactory, final AsyncClientConnectionManager manager, final HttpRoutePlanner routePlanner, - final HttpVersionPolicy versionPolicy, + final TlsConfig tlsConfig, final Lookup cookieSpecRegistry, final Lookup authSchemeRegistry, final CookieStore cookieStore, @@ -91,12 +91,12 @@ public final class InternalHttpAsyncClient extends InternalAbstractHttpAsyncClie cookieSpecRegistry, authSchemeRegistry, cookieStore, credentialsProvider, defaultConfig, closeables); this.manager = manager; this.routePlanner = routePlanner; - this.versionPolicy = versionPolicy; + this.tlsConfig = tlsConfig; } @Override AsyncExecRuntime createAsyncExecRuntime(final HandlerFactory pushHandlerFactory) { - return new InternalHttpAsyncExecRuntime(LOG, manager, getConnectionInitiator(), pushHandlerFactory, versionPolicy); + return new InternalHttpAsyncExecRuntime(LOG, manager, getConnectionInitiator(), pushHandlerFactory, tlsConfig); } @Override diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalHttpAsyncExecRuntime.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalHttpAsyncExecRuntime.java index 390340340..ad7c56501 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalHttpAsyncExecRuntime.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalHttpAsyncExecRuntime.java @@ -33,6 +33,7 @@ import org.apache.hc.client5.http.HttpRoute; import org.apache.hc.client5.http.async.AsyncExecRuntime; import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.config.TlsConfig; import org.apache.hc.client5.http.impl.ConnPoolSupport; import org.apache.hc.client5.http.impl.Operations; import org.apache.hc.client5.http.nio.AsyncClientConnectionManager; @@ -44,7 +45,6 @@ import org.apache.hc.core5.http.nio.AsyncClientExchangeHandler; import org.apache.hc.core5.http.nio.AsyncPushConsumer; import org.apache.hc.core5.http.nio.HandlerFactory; -import org.apache.hc.core5.http2.HttpVersionPolicy; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.reactor.ConnectionInitiator; import org.apache.hc.core5.util.TimeValue; @@ -57,7 +57,11 @@ class InternalHttpAsyncExecRuntime implements AsyncExecRuntime { private final AsyncClientConnectionManager manager; private final ConnectionInitiator connectionInitiator; private final HandlerFactory pushHandlerFactory; - private final HttpVersionPolicy versionPolicy; + /** + * @deprecated TLS should be configured by the connection manager + */ + @Deprecated + private final TlsConfig tlsConfig; private final AtomicReference endpointRef; private volatile boolean reusable; private volatile Object state; @@ -68,14 +72,14 @@ class InternalHttpAsyncExecRuntime implements AsyncExecRuntime { final AsyncClientConnectionManager manager, final ConnectionInitiator connectionInitiator, final HandlerFactory pushHandlerFactory, - final HttpVersionPolicy versionPolicy) { + final TlsConfig tlsConfig) { super(); this.log = log; this.manager = manager; this.connectionInitiator = connectionInitiator; this.pushHandlerFactory = pushHandlerFactory; - this.versionPolicy = versionPolicy; - this.endpointRef = new AtomicReference<>(); + this.tlsConfig = tlsConfig; + this.endpointRef = new AtomicReference<>(null); this.validDuration = TimeValue.NEG_ONE_MILLISECOND; } @@ -213,7 +217,7 @@ public Cancellable connectEndpoint( endpoint, connectionInitiator, connectTimeout, - versionPolicy, + tlsConfig, context, new CallbackContribution(callback) { @@ -242,7 +246,7 @@ public void upgradeTls(final HttpClientContext context, final FutureCallback(callback) { + manager.upgrade(endpoint, tlsConfig, context, new CallbackContribution(callback) { @Override public void completed(final AsyncConnectionEndpoint endpoint) { @@ -320,7 +324,7 @@ public void markConnectionNonReusable() { @Override public AsyncExecRuntime fork() { - return new InternalHttpAsyncExecRuntime(log, manager, connectionInitiator, pushHandlerFactory, versionPolicy); + return new InternalHttpAsyncExecRuntime(log, manager, connectionInitiator, pushHandlerFactory, tlsConfig); } } diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/MinimalHttpAsyncClient.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/MinimalHttpAsyncClient.java index 6a7bb4e94..5521eaf53 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/MinimalHttpAsyncClient.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/MinimalHttpAsyncClient.java @@ -39,6 +39,7 @@ import org.apache.hc.client5.http.SchemePortResolver; import org.apache.hc.client5.http.config.Configurable; import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.config.TlsConfig; import org.apache.hc.client5.http.impl.ConnPoolSupport; import org.apache.hc.client5.http.impl.DefaultSchemePortResolver; import org.apache.hc.client5.http.impl.ExecSupport; @@ -69,7 +70,6 @@ import org.apache.hc.core5.http.nio.RequestChannel; import org.apache.hc.core5.http.nio.command.ShutdownCommand; import org.apache.hc.core5.http.protocol.HttpContext; -import org.apache.hc.core5.http2.HttpVersionPolicy; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.io.Closer; import org.apache.hc.core5.reactor.Command; @@ -101,17 +101,17 @@ public final class MinimalHttpAsyncClient extends AbstractMinimalHttpAsyncClient private static final Logger LOG = LoggerFactory.getLogger(MinimalHttpAsyncClient.class); private final AsyncClientConnectionManager manager; private final SchemePortResolver schemePortResolver; - private final HttpVersionPolicy versionPolicy; + private final TlsConfig tlsConfig; MinimalHttpAsyncClient( final IOEventHandlerFactory eventHandlerFactory, final AsyncPushConsumerRegistry pushConsumerRegistry, - final HttpVersionPolicy versionPolicy, final IOReactorConfig reactorConfig, final ThreadFactory threadFactory, final ThreadFactory workerThreadFactory, final AsyncClientConnectionManager manager, - final SchemePortResolver schemePortResolver) { + final SchemePortResolver schemePortResolver, + final TlsConfig tlsConfig) { super(new DefaultConnectingIOReactor( eventHandlerFactory, reactorConfig, @@ -124,7 +124,7 @@ public final class MinimalHttpAsyncClient extends AbstractMinimalHttpAsyncClient threadFactory); this.manager = manager; this.schemePortResolver = schemePortResolver != null ? schemePortResolver : DefaultSchemePortResolver.INSTANCE; - this.versionPolicy = versionPolicy != null ? versionPolicy : HttpVersionPolicy.NEGOTIATE; + this.tlsConfig = tlsConfig; } private Future leaseEndpoint( @@ -153,7 +153,7 @@ public void completed(final AsyncConnectionEndpoint connectionEndpoint) { connectionEndpoint, getConnectionInitiator(), connectTimeout, - versionPolicy, + tlsConfig, clientContext, new FutureCallback() { diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/BasicHttpClientConnectionManager.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/BasicHttpClientConnectionManager.java index b9866c1a1..a67b3ad91 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/BasicHttpClientConnectionManager.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/BasicHttpClientConnectionManager.java @@ -39,6 +39,7 @@ import org.apache.hc.client5.http.HttpRoute; import org.apache.hc.client5.http.SchemePortResolver; import org.apache.hc.client5.http.config.ConnectionConfig; +import org.apache.hc.client5.http.config.TlsConfig; import org.apache.hc.client5.http.impl.ConnPoolSupport; import org.apache.hc.client5.http.impl.ConnectionShutdownException; import org.apache.hc.client5.http.io.ConnectionEndpoint; @@ -109,6 +110,7 @@ public class BasicHttpClientConnectionManager implements HttpClientConnectionMan private boolean leased; private SocketConfig socketConfig; private ConnectionConfig connectionConfig; + private TlsConfig tlsConfig; private final AtomicBoolean closed; @@ -142,6 +144,8 @@ public BasicHttpClientConnectionManager( this.id = String.format("ep-%010d", COUNT.getAndIncrement()); this.expiry = Long.MAX_VALUE; this.socketConfig = SocketConfig.DEFAULT; + this.connectionConfig = ConnectionConfig.DEFAULT; + this.tlsConfig = TlsConfig.DEFAULT; this.closed = new AtomicBoolean(false); this.validateAfterInactivity = TimeValue.ofSeconds(2L); } @@ -203,6 +207,13 @@ public synchronized void setConnectionConfig(final ConnectionConfig connectionCo this.connectionConfig = connectionConfig != null ? connectionConfig : ConnectionConfig.DEFAULT; } + /** + * @since 5.2 + */ + public synchronized void setTlsConfig(final TlsConfig tlsConfig) { + this.tlsConfig = tlsConfig != null ? tlsConfig : TlsConfig.DEFAULT; + } + public LeaseRequest lease(final String id, final HttpRoute route, final Object state) { return lease(id, route, Timeout.DISABLED, state); } @@ -358,8 +369,7 @@ public synchronized void connect(final ConnectionEndpoint endpoint, final TimeVa } else { host = route.getTargetHost(); } - final ConnectionConfig config = connectionConfig != null ? connectionConfig : ConnectionConfig.DEFAULT; - final TimeValue connectTimeout = timeout != null ? timeout : config.getConnectTimeout(); + final Timeout connectTimeout = timeout != null ? Timeout.of(timeout.getDuration(), timeout.getTimeUnit()) : connectionConfig.getConnectTimeout(); final ManagedHttpClientConnection connection = internalEndpoint.getConnection(); if (LOG.isDebugEnabled()) { LOG.debug("{} connecting endpoint to {} ({})", ConnPoolSupport.getId(endpoint), host, connectTimeout); @@ -369,12 +379,13 @@ public synchronized void connect(final ConnectionEndpoint endpoint, final TimeVa host, route.getLocalSocketAddress(), connectTimeout, - this.socketConfig, + socketConfig, + tlsConfig, context); if (LOG.isDebugEnabled()) { LOG.debug("{} connected {}", ConnPoolSupport.getId(endpoint), ConnPoolSupport.getId(conn)); } - final Timeout socketTimeout = config.getSocketTimeout(); + final Timeout socketTimeout = connectionConfig.getSocketTimeout(); if (socketTimeout != null) { connection.setSocketTimeout(socketTimeout); } @@ -387,9 +398,11 @@ public synchronized void upgrade( Args.notNull(endpoint, "Endpoint"); Args.notNull(route, "HTTP route"); final InternalConnectionEndpoint internalEndpoint = cast(endpoint); + final ConnectionConfig config = connectionConfig != null ? connectionConfig : ConnectionConfig.DEFAULT; this.connectionOperator.upgrade( internalEndpoint.getConnection(), internalEndpoint.getRoute().getTargetHost(), + tlsConfig, context); } diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/DefaultHttpClientConnectionOperator.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/DefaultHttpClientConnectionOperator.java index 926f58878..8f7eef802 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/DefaultHttpClientConnectionOperator.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/DefaultHttpClientConnectionOperator.java @@ -54,6 +54,7 @@ import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.TimeValue; +import org.apache.hc.core5.util.Timeout; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -107,6 +108,19 @@ public void connect( final TimeValue connectTimeout, final SocketConfig socketConfig, final HttpContext context) throws IOException { + final Timeout timeout = connectTimeout != null ? Timeout.of(connectTimeout.getDuration(), connectTimeout.getTimeUnit()) : null; + connect(conn, host, localAddress, timeout, socketConfig, null, context); + } + + @Override + public void connect( + final ManagedHttpClientConnection conn, + final HttpHost host, + final InetSocketAddress localAddress, + final Timeout connectTimeout, + final SocketConfig socketConfig, + final Object attachment, + final HttpContext context) throws IOException { Args.notNull(conn, "Connection"); Args.notNull(host, "Host"); Args.notNull(socketConfig, "Socket config"); @@ -131,13 +145,17 @@ public void connect( } } + final Timeout soTimeout = socketConfig.getSoTimeout(); + final int port = this.schemePortResolver.resolve(host); for (int i = 0; i < remoteAddresses.length; i++) { final InetAddress address = remoteAddresses[i]; final boolean last = i == remoteAddresses.length - 1; Socket sock = sf.createSocket(context); - sock.setSoTimeout(socketConfig.getSoTimeout().toMillisecondsIntBound()); + if (soTimeout != null) { + sock.setSoTimeout(soTimeout.toMillisecondsIntBound()); + } sock.setReuseAddress(socketConfig.isSoReuseAddress()); sock.setTcpNoDelay(socketConfig.isTcpNoDelay()); sock.setKeepAlive(socketConfig.isSoKeepAlive()); @@ -160,8 +178,9 @@ public void connect( host.getHostName(), host.getPort(), localAddress, remoteAddress, connectTimeout); } try { - sock = sf.connectSocket(connectTimeout, sock, host, remoteAddress, localAddress, context); + sock = sf.connectSocket(sock, host, remoteAddress, localAddress, connectTimeout, attachment, context); conn.bind(sock); + conn.setSocketTimeout(soTimeout); if (LOG.isDebugEnabled()) { LOG.debug("{}:{} connected {}->{} as {}", host.getHostName(), host.getPort(), localAddress, remoteAddress, ConnPoolSupport.getId(conn)); @@ -189,6 +208,15 @@ public void upgrade( final ManagedHttpClientConnection conn, final HttpHost host, final HttpContext context) throws IOException { + upgrade(conn, host, null, context); + } + + @Override + public void upgrade( + final ManagedHttpClientConnection conn, + final HttpHost host, + final Object attachment, + final HttpContext context) throws IOException { final HttpClientContext clientContext = HttpClientContext.adapt(context); final Lookup registry = getSocketFactoryRegistry(clientContext); final ConnectionSocketFactory sf = registry.lookup(host.getSchemeName()); @@ -206,7 +234,7 @@ public void upgrade( throw new ConnectionClosedException("Connection is closed"); } final int port = this.schemePortResolver.resolve(host); - sock = lsf.createLayeredSocket(sock, host.getHostName(), port, context); + sock = lsf.createLayeredSocket(sock, host.getHostName(), port, attachment, context); conn.bind(sock); } diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/PoolingHttpClientConnectionManager.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/PoolingHttpClientConnectionManager.java index 22a29e980..8c415fc70 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/PoolingHttpClientConnectionManager.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/PoolingHttpClientConnectionManager.java @@ -39,6 +39,7 @@ import org.apache.hc.client5.http.HttpRoute; import org.apache.hc.client5.http.SchemePortResolver; import org.apache.hc.client5.http.config.ConnectionConfig; +import org.apache.hc.client5.http.config.TlsConfig; import org.apache.hc.client5.http.impl.ConnPoolSupport; import org.apache.hc.client5.http.impl.ConnectionShutdownException; import org.apache.hc.client5.http.io.ConnectionEndpoint; @@ -115,6 +116,7 @@ public class PoolingHttpClientConnectionManager private volatile Resolver socketConfigResolver; private volatile Resolver connectionConfigResolver; + private volatile Resolver tlsConfigResolver; public PoolingHttpClientConnectionManager() { this(RegistryBuilder.create() @@ -241,16 +243,22 @@ private InternalConnectionEndpoint cast(final ConnectionEndpoint endpoint) { throw new IllegalStateException("Unexpected endpoint class: " + endpoint.getClass()); } + private SocketConfig resolveSocketConfig(final HttpRoute route) { + final Resolver resolver = this.socketConfigResolver; + final SocketConfig socketConfig = resolver != null ? resolver.resolve(route) : null; + return socketConfig != null ? socketConfig : SocketConfig.DEFAULT; + } + private ConnectionConfig resolveConnectionConfig(final HttpRoute route) { final Resolver resolver = this.connectionConfigResolver; final ConnectionConfig connectionConfig = resolver != null ? resolver.resolve(route) : null; return connectionConfig != null ? connectionConfig : ConnectionConfig.DEFAULT; } - private SocketConfig resolveSocketConfig(final HttpRoute route) { - final Resolver resolver = this.socketConfigResolver; - final SocketConfig socketConfig = resolver != null ? resolver.resolve(route) : null; - return socketConfig != null ? socketConfig : SocketConfig.DEFAULT; + private TlsConfig resolveTlsConfig(final HttpHost host) { + final Resolver resolver = this.tlsConfigResolver; + final TlsConfig tlsConfig = resolver != null ? resolver.resolve(host) : null; + return tlsConfig != null ? tlsConfig : TlsConfig.DEFAULT; } private TimeValue resolveValidateAfterInactivity(final ConnectionConfig connectionConfig) { @@ -401,15 +409,11 @@ public void connect(final ConnectionEndpoint endpoint, final TimeValue timeout, poolEntry.assignConnection(connFactory.createConnection(null)); } final HttpRoute route = poolEntry.getRoute(); - final HttpHost host; - if (route.getProxyHost() != null) { - host = route.getProxyHost(); - } else { - host = route.getTargetHost(); - } + final HttpHost host = route.getProxyHost() != null ? route.getProxyHost() : route.getTargetHost(); final SocketConfig socketConfig = resolveSocketConfig(route); final ConnectionConfig connectionConfig = resolveConnectionConfig(route); - final TimeValue connectTimeout = timeout != null ? timeout : connectionConfig.getConnectTimeout(); + final TlsConfig tlsConfig = resolveTlsConfig(host); + final Timeout connectTimeout = timeout != null ? Timeout.of(timeout.getDuration(), timeout.getTimeUnit()) : connectionConfig.getConnectTimeout(); if (LOG.isDebugEnabled()) { LOG.debug("{} connecting endpoint to {} ({})", ConnPoolSupport.getId(endpoint), host, connectTimeout); } @@ -418,8 +422,9 @@ public void connect(final ConnectionEndpoint endpoint, final TimeValue timeout, conn, host, route.getLocalSocketAddress(), - timeout, + connectTimeout, socketConfig, + tlsConfig, context); if (LOG.isDebugEnabled()) { LOG.debug("{} connected {}", ConnPoolSupport.getId(endpoint), ConnPoolSupport.getId(conn)); @@ -436,7 +441,9 @@ public void upgrade(final ConnectionEndpoint endpoint, final HttpContext context final InternalConnectionEndpoint internalEndpoint = cast(endpoint); final PoolEntry poolEntry = internalEndpoint.getValidatedPoolEntry(); final HttpRoute route = poolEntry.getRoute(); - this.connectionOperator.upgrade(poolEntry.getConnection(), route.getTargetHost(), context); + final HttpHost host = route.getProxyHost() != null ? route.getProxyHost() : route.getTargetHost(); + final TlsConfig tlsConfig = resolveTlsConfig(host); + this.connectionOperator.upgrade(poolEntry.getConnection(), route.getTargetHost(), tlsConfig, context); } @Override @@ -533,6 +540,24 @@ public void setConnectionConfigResolver(final Resolver config; + } + + /** + * Sets {@link Resolver} of {@link TlsConfig} on a per host basis. + * + * @since 5.2 + */ + public void setTlsConfigResolver(final Resolver tlsConfigResolver) { + this.tlsConfigResolver = tlsConfigResolver; + } + /** * @deprecated Use custom {@link #setConnectionConfigResolver(Resolver)} */ diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/PoolingHttpClientConnectionManagerBuilder.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/PoolingHttpClientConnectionManagerBuilder.java index e52f9acc7..c150e23c1 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/PoolingHttpClientConnectionManagerBuilder.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/PoolingHttpClientConnectionManagerBuilder.java @@ -31,12 +31,14 @@ import org.apache.hc.client5.http.HttpRoute; import org.apache.hc.client5.http.SchemePortResolver; import org.apache.hc.client5.http.config.ConnectionConfig; +import org.apache.hc.client5.http.config.TlsConfig; import org.apache.hc.client5.http.io.ManagedHttpClientConnection; import org.apache.hc.client5.http.socket.ConnectionSocketFactory; import org.apache.hc.client5.http.socket.LayeredConnectionSocketFactory; import org.apache.hc.client5.http.socket.PlainConnectionSocketFactory; import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory; import org.apache.hc.core5.function.Resolver; +import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.http.config.RegistryBuilder; import org.apache.hc.core5.http.io.HttpConnectionFactory; @@ -81,6 +83,7 @@ public class PoolingHttpClientConnectionManagerBuilder { private PoolReusePolicy poolReusePolicy; private Resolver socketConfigResolver; private Resolver connectionConfigResolver; + private Resolver tlsConfigResolver; private boolean systemProperties; @@ -203,6 +206,27 @@ public final PoolingHttpClientConnectionManagerBuilder setConnectionConfigResolv return this; } + /** + * Assigns the same {@link TlsConfig} for all hosts. + * + * @since 5.2 + */ + public final PoolingHttpClientConnectionManagerBuilder setDefaultTlsConfig(final TlsConfig config) { + this.tlsConfigResolver = (host) -> config; + return this; + } + + /** + * Assigns {@link Resolver} of {@link TlsConfig} on a per host basis. + * + * @since 5.2 + */ + public final PoolingHttpClientConnectionManagerBuilder setTlsConfigResolver( + final Resolver tlsConfigResolver) { + this.tlsConfigResolver = tlsConfigResolver; + return this; + } + /** * Sets maximum time to live for persistent connections */ @@ -251,6 +275,7 @@ public PoolingHttpClientConnectionManager build() { connectionFactory); poolingmgr.setSocketConfigResolver(socketConfigResolver); poolingmgr.setConnectionConfigResolver(connectionConfigResolver); + poolingmgr.setTlsConfigResolver(tlsConfigResolver); if (maxConnTotal > 0) { poolingmgr.setMaxTotal(maxConnTotal); } diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/nio/DefaultAsyncClientConnectionOperator.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/nio/DefaultAsyncClientConnectionOperator.java index a781e9bb9..130eb6bf0 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/nio/DefaultAsyncClientConnectionOperator.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/nio/DefaultAsyncClientConnectionOperator.java @@ -34,6 +34,7 @@ import org.apache.hc.client5.http.DnsResolver; import org.apache.hc.client5.http.SchemePortResolver; +import org.apache.hc.client5.http.config.TlsConfig; import org.apache.hc.client5.http.impl.DefaultSchemePortResolver; import org.apache.hc.client5.http.nio.AsyncClientConnectionOperator; import org.apache.hc.client5.http.nio.ManagedAsyncClientConnection; @@ -81,13 +82,14 @@ public Future connect( final HttpHost remoteEndpoint = RoutingSupport.normalize(host, schemePortResolver); final InetAddress remoteAddress = host.getAddress(); final TlsStrategy tlsStrategy = tlsStrategyLookup != null ? tlsStrategyLookup.lookup(host.getSchemeName()) : null; + final TlsConfig tlsConfig = attachment instanceof TlsConfig ? (TlsConfig) attachment : TlsConfig.DEFAULT; final Future sessionFuture = sessionRequester.connect( connectionInitiator, remoteEndpoint, remoteAddress != null ? new InetSocketAddress(remoteAddress, remoteEndpoint.getPort()) : null, localAddress, connectTimeout, - attachment, + tlsConfig.getHttpVersionPolicy(), new FutureCallback() { @Override @@ -95,15 +97,18 @@ public void completed(final IOSession session) { final DefaultManagedAsyncClientConnection connection = new DefaultManagedAsyncClientConnection(session); if (tlsStrategy != null && URIScheme.HTTPS.same(host.getSchemeName())) { try { + final Timeout socketTimeout = connection.getSocketTimeout(); + final Timeout handshakeTimeout = tlsConfig.getHandshakeTimeout(); tlsStrategy.upgrade( connection, host, attachment, - null, + handshakeTimeout != null ? handshakeTimeout : connectTimeout, new FutureContribution(future) { @Override public void completed(final TransportSecurityLayer transportSecurityLayer) { + connection.setSocketTimeout(socketTimeout); future.completed(connection); } diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/nio/PoolingAsyncClientConnectionManager.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/nio/PoolingAsyncClientConnectionManager.java index 3617b592e..d5071544a 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/nio/PoolingAsyncClientConnectionManager.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/nio/PoolingAsyncClientConnectionManager.java @@ -41,6 +41,7 @@ import org.apache.hc.client5.http.HttpRoute; import org.apache.hc.client5.http.SchemePortResolver; import org.apache.hc.client5.http.config.ConnectionConfig; +import org.apache.hc.client5.http.config.TlsConfig; import org.apache.hc.client5.http.impl.ConnPoolSupport; import org.apache.hc.client5.http.impl.ConnectionShutdownException; import org.apache.hc.client5.http.nio.AsyncClientConnectionManager; @@ -123,6 +124,7 @@ public class PoolingAsyncClientConnectionManager implements AsyncClientConnectio private final AtomicBoolean closed; private volatile Resolver connectionConfigResolver; + private volatile Resolver tlsConfigResolver; public PoolingAsyncClientConnectionManager() { this(RegistryBuilder.create() @@ -227,6 +229,15 @@ private ConnectionConfig resolveConnectionConfig(final HttpRoute route) { return connectionConfig != null ? connectionConfig : ConnectionConfig.DEFAULT; } + private TlsConfig resolveTlsConfig(final HttpHost host, final Object attachment) { + if (attachment instanceof TlsConfig) { + return (TlsConfig) attachment; + } + final Resolver resolver = this.tlsConfigResolver; + final TlsConfig tlsConfig = resolver != null ? resolver.resolve(host) : null; + return tlsConfig != null ? tlsConfig : TlsConfig.DEFAULT; + } + @Override public Future lease( final String id, @@ -400,6 +411,7 @@ public Future connect( } final InetSocketAddress localAddress = route.getLocalSocketAddress(); final ConnectionConfig connectionConfig = resolveConnectionConfig(route); + final TlsConfig tlsConfig = resolveTlsConfig(host, attachment); final Timeout connectTimeout = timeout != null ? timeout : connectionConfig.getConnectTimeout(); if (LOG.isDebugEnabled()) { @@ -410,7 +422,9 @@ public Future connect( host, localAddress, connectTimeout, - route.isTunnelled() ? HttpVersionPolicy.FORCE_HTTP_1 : attachment, + route.isTunnelled() ? TlsConfig.copy(tlsConfig) + .setVersionPolicy(HttpVersionPolicy.FORCE_HTTP_1) + .build() : tlsConfig, new FutureCallback() { @Override @@ -455,10 +469,12 @@ public void upgrade( final InternalConnectionEndpoint internalEndpoint = cast(endpoint); final PoolEntry poolEntry = internalEndpoint.getValidatedPoolEntry(); final HttpRoute route = poolEntry.getRoute(); + final HttpHost host = route.getProxyHost() != null ? route.getProxyHost() : route.getTargetHost(); + final TlsConfig tlsConfig = resolveTlsConfig(host, attachment); connectionOperator.upgrade( poolEntry.getConnection(), route.getTargetHost(), - attachment, + attachment != null ? attachment : tlsConfig, new CallbackContribution(callback) { @Override @@ -566,6 +582,24 @@ public void setConnectionConfigResolver(final Resolver config; + } + + /** + * Sets {@link Resolver} of {@link TlsConfig} on a per host basis. + * + * @since 5.2 + */ + public void setTlsConfigResolver(final Resolver tlsConfigResolver) { + this.tlsConfigResolver = tlsConfigResolver; + } + /** * @deprecated Use custom {@link #setConnectionConfigResolver(Resolver)} */ diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/nio/PoolingAsyncClientConnectionManagerBuilder.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/nio/PoolingAsyncClientConnectionManagerBuilder.java index a8d44b0fa..122e0004e 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/nio/PoolingAsyncClientConnectionManagerBuilder.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/nio/PoolingAsyncClientConnectionManagerBuilder.java @@ -31,9 +31,11 @@ import org.apache.hc.client5.http.HttpRoute; import org.apache.hc.client5.http.SchemePortResolver; import org.apache.hc.client5.http.config.ConnectionConfig; +import org.apache.hc.client5.http.config.TlsConfig; import org.apache.hc.client5.http.ssl.ConscryptClientTlsStrategy; import org.apache.hc.client5.http.ssl.DefaultClientTlsStrategy; import org.apache.hc.core5.function.Resolver; +import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.config.RegistryBuilder; import org.apache.hc.core5.http.io.SocketConfig; import org.apache.hc.core5.http.nio.ssl.TlsStrategy; @@ -83,6 +85,7 @@ public class PoolingAsyncClientConnectionManagerBuilder { private Resolver socketConfigResolver; private Resolver connectionConfigResolver; + private Resolver tlsConfigResolver; private TimeValue timeToLive; public static PoolingAsyncClientConnectionManagerBuilder create() { @@ -171,6 +174,26 @@ public final PoolingAsyncClientConnectionManagerBuilder setConnectionConfigResol return this; } + /** + * Assigns the same {@link TlsConfig} for all hosts. + * + * @since 5.2 + */ + public final PoolingAsyncClientConnectionManagerBuilder setDefaultTlsConfig(final TlsConfig config) { + this.tlsConfigResolver = (host) -> config; + return this; + } + + /** + * Assigns {@link Resolver} of {@link TlsConfig} on a per host basis. + * + * @since 5.2 + */ + public final PoolingAsyncClientConnectionManagerBuilder setTlsConfigResolver( + final Resolver tlsConfigResolver) { + this.tlsConfigResolver = tlsConfigResolver; + return this; + } /** * Sets maximum time to live for persistent connections @@ -232,6 +255,7 @@ public PoolingAsyncClientConnectionManager build() { schemePortResolver, dnsResolver); poolingmgr.setConnectionConfigResolver(connectionConfigResolver); + poolingmgr.setTlsConfigResolver(tlsConfigResolver); if (maxConnTotal > 0) { poolingmgr.setMaxTotal(maxConnTotal); } diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/io/HttpClientConnectionOperator.java b/httpclient5/src/main/java/org/apache/hc/client5/http/io/HttpClientConnectionOperator.java index d1bf4fa92..f533fef22 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/io/HttpClientConnectionOperator.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/io/HttpClientConnectionOperator.java @@ -37,6 +37,7 @@ import org.apache.hc.core5.http.io.SocketConfig; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.util.TimeValue; +import org.apache.hc.core5.util.Timeout; /** * Connection operator that performs connection connect and upgrade operations. @@ -65,6 +66,30 @@ void connect( SocketConfig socketConfig, HttpContext context) throws IOException; + /** + * Connect the given managed connection to the remote endpoint. + * + * @param conn the managed connection. + * @param host the address of the opposite endpoint. + * @param localAddress the address of the local endpoint. + * @param connectTimeout the timeout of the connect operation. + * @param socketConfig the socket configuration. + * @param attachment connect request attachment. + * @param context the execution context. + * + * @since 5.2 + */ + default void connect( + ManagedHttpClientConnection conn, + HttpHost host, + InetSocketAddress localAddress, + Timeout connectTimeout, + SocketConfig socketConfig, + Object attachment, + HttpContext context) throws IOException { + connect(conn, host, localAddress, connectTimeout, socketConfig, context); + } + /** * Upgrades transport security of the given managed connection * by using the TLS security protocol. @@ -78,4 +103,23 @@ void upgrade( HttpHost host, HttpContext context) throws IOException; + /** + * Upgrades transport security of the given managed connection + * by using the TLS security protocol. + * + * @param conn the managed connection. + * @param host the address of the opposite endpoint with TLS security. + * @param attachment connect request attachment. + * @param context the execution context. + * + * @since 5.2 + */ + default void upgrade( + ManagedHttpClientConnection conn, + HttpHost host, + Object attachment, + HttpContext context) throws IOException { + upgrade(conn, host, context); + } + } diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/nio/AsyncClientConnectionOperator.java b/httpclient5/src/main/java/org/apache/hc/client5/http/nio/AsyncClientConnectionOperator.java index af1260429..f368e79ee 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/nio/AsyncClientConnectionOperator.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/nio/AsyncClientConnectionOperator.java @@ -67,7 +67,6 @@ Future connect( Object attachment, FutureCallback callback); - /** * Upgrades transport security of the given managed connection * by using the TLS security protocol. diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/socket/ConnectionSocketFactory.java b/httpclient5/src/main/java/org/apache/hc/client5/http/socket/ConnectionSocketFactory.java index 88cafd07c..f8d2e6fa2 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/socket/ConnectionSocketFactory.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/socket/ConnectionSocketFactory.java @@ -36,6 +36,7 @@ import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.util.TimeValue; +import org.apache.hc.core5.util.Timeout; /** * A factory for creating and connecting connection sockets. @@ -81,4 +82,35 @@ Socket connectSocket( InetSocketAddress localAddress, HttpContext context) throws IOException; + /** + * Connects the socket to the target host with the given resolved remote address. + * + * @param socket the socket to connect, as obtained from {@link #createSocket(HttpContext)}. + * {@code null} indicates that a new socket should be created and connected. + * @param host target host as specified by the caller (end user). + * @param remoteAddress the resolved remote address to connect to. + * @param localAddress the local address to bind the socket to, or {@code null} for any. + * @param connectTimeout connect timeout. + * @param attachment connect request attachment. + * @param context the actual HTTP context. + * + * @return the connected socket. The returned object may be different + * from the {@code sock} argument if this factory supports + * a layered protocol. + * + * @throws IOException if an I/O error occurs + * + * @since 5.2 + */ + default Socket connectSocket( + Socket socket, + HttpHost host, + InetSocketAddress remoteAddress, + InetSocketAddress localAddress, + Timeout connectTimeout, + Object attachment, + HttpContext context) throws IOException { + return connectSocket(connectTimeout, socket, host, remoteAddress, localAddress, context); + } + } diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/socket/LayeredConnectionSocketFactory.java b/httpclient5/src/main/java/org/apache/hc/client5/http/socket/LayeredConnectionSocketFactory.java index d35c296e1..36dfa80f8 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/socket/LayeredConnectionSocketFactory.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/socket/LayeredConnectionSocketFactory.java @@ -62,4 +62,30 @@ Socket createLayeredSocket( int port, HttpContext context) throws IOException; + /** + * Returns a socket connected to the given host that is layered over an + * existing socket. Used primarily for creating secure sockets through + * proxies. + * + * @param socket the existing socket + * @param target the name of the target host. + * @param port the port to connect to on the target host. + * @param context the actual HTTP context. + * @param attachment connect request attachment. + * + * @return Socket a new socket + * + * @throws IOException if an I/O error occurs while creating the socket + * + * @since 5.2 + */ + default Socket createLayeredSocket( + Socket socket, + String target, + int port, + Object attachment, + HttpContext context) throws IOException { + return createLayeredSocket(socket, target, port, context); + } + } diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/ssl/AbstractClientTlsStrategy.java b/httpclient5/src/main/java/org/apache/hc/client5/http/ssl/AbstractClientTlsStrategy.java index 45a66f71b..21c72c34a 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/ssl/AbstractClientTlsStrategy.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/ssl/AbstractClientTlsStrategy.java @@ -38,6 +38,7 @@ import javax.net.ssl.SSLParameters; import javax.net.ssl.SSLSession; +import org.apache.hc.client5.http.config.TlsConfig; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.concurrent.FutureCallback; @@ -109,17 +110,23 @@ public void upgrade( final FutureCallback callback) { tlsSession.startTls(sslContext, endpoint, sslBufferManagement, (e, sslEngine) -> { - final HttpVersionPolicy versionPolicy = attachment instanceof HttpVersionPolicy ? - (HttpVersionPolicy) attachment : HttpVersionPolicy.NEGOTIATE; + final TlsConfig tlsConfig = attachment instanceof TlsConfig ? (TlsConfig) attachment : TlsConfig.DEFAULT; + final HttpVersionPolicy versionPolicy = tlsConfig.getHttpVersionPolicy(); final SSLParameters sslParameters = sslEngine.getSSLParameters(); + final String[] supportedProtocols = tlsConfig.getSupportedProtocols(); if (supportedProtocols != null) { sslParameters.setProtocols(supportedProtocols); + } else if (this.supportedProtocols != null) { + sslParameters.setProtocols(this.supportedProtocols); } else if (versionPolicy != HttpVersionPolicy.FORCE_HTTP_1) { sslParameters.setProtocols(TLS.excludeWeak(sslParameters.getProtocols())); } + final String[] supportedCipherSuites = tlsConfig.getSupportedCipherSuites(); if (supportedCipherSuites != null) { sslParameters.setCipherSuites(supportedCipherSuites); + } else if (this.supportedCipherSuites != null) { + sslParameters.setCipherSuites(this.supportedCipherSuites); } else if (versionPolicy == HttpVersionPolicy.FORCE_HTTP_2) { sslParameters.setCipherSuites(TlsCiphers.excludeH2Blacklisted(sslParameters.getCipherSuites())); } @@ -135,6 +142,7 @@ public void upgrade( if (LOG.isDebugEnabled()) { LOG.debug("Enabled protocols: {}", Arrays.asList(sslEngine.getEnabledProtocols())); LOG.debug("Enabled cipher suites:{}", Arrays.asList(sslEngine.getEnabledCipherSuites())); + LOG.debug("Starting handshake ({})", handshakeTimeout); } }, (e, sslEngine) -> { verifySession(endpoint.getHostName(), sslEngine.getSession()); diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/ssl/SSLConnectionSocketFactory.java b/httpclient5/src/main/java/org/apache/hc/client5/http/ssl/SSLConnectionSocketFactory.java index 2a29e91c3..59a8df5f6 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/ssl/SSLConnectionSocketFactory.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/ssl/SSLConnectionSocketFactory.java @@ -47,6 +47,7 @@ import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocket; +import org.apache.hc.client5.http.config.TlsConfig; import org.apache.hc.client5.http.socket.LayeredConnectionSocketFactory; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.ThreadingBehavior; @@ -60,6 +61,7 @@ import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.Asserts; import org.apache.hc.core5.util.TimeValue; +import org.apache.hc.core5.util.Timeout; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -200,6 +202,19 @@ public Socket connectSocket( final InetSocketAddress remoteAddress, final InetSocketAddress localAddress, final HttpContext context) throws IOException { + final Timeout timeout = connectTimeout != null ? Timeout.of(connectTimeout.getDuration(), connectTimeout.getTimeUnit()) : null; + return connectSocket(socket, host, remoteAddress, localAddress, timeout, timeout, context); + } + + @Override + public Socket connectSocket( + final Socket socket, + final HttpHost host, + final InetSocketAddress remoteAddress, + final InetSocketAddress localAddress, + final Timeout connectTimeout, + final Object attachment, + final HttpContext context) throws IOException { Args.notNull(host, "HTTP host"); Args.notNull(remoteAddress, "Remote address"); final Socket sock = socket != null ? socket : createSocket(context); @@ -214,7 +229,7 @@ public Socket connectSocket( // only to this library try { AccessController.doPrivileged((PrivilegedExceptionAction) () -> { - sock.connect(remoteAddress, connectTimeout != null ? connectTimeout.toMillisecondsIntBound() : 0); + sock.connect(remoteAddress, Timeout.defaultsToDisabled(connectTimeout).toMillisecondsIntBound()); return null; }); } catch (final PrivilegedActionException e) { @@ -230,12 +245,10 @@ public Socket connectSocket( // Setup SSL layering if necessary if (sock instanceof SSLSocket) { final SSLSocket sslsock = (SSLSocket) sock; - LOG.debug("Starting handshake"); - sslsock.startHandshake(); - verifyHostname(sslsock, host.getHostName()); + executeHandshake(sslsock, host.getHostName(), attachment); return sock; } - return createLayeredSocket(sock, host.getHostName(), remoteAddress.getPort(), context); + return createLayeredSocket(sock, host.getHostName(), remoteAddress.getPort(), attachment, context); } @Override @@ -244,11 +257,27 @@ public Socket createLayeredSocket( final String target, final int port, final HttpContext context) throws IOException { + return createLayeredSocket(socket, target, port, context); + } + + @Override + public Socket createLayeredSocket( + final Socket socket, + final String target, + final int port, + final Object attachment, + final HttpContext context) throws IOException { final SSLSocket sslsock = (SSLSocket) this.socketFactory.createSocket( socket, target, port, true); + executeHandshake(sslsock, target, attachment); + return sslsock; + } + + private void executeHandshake(final SSLSocket sslsock, final String target, final Object attachment) throws IOException { + final TlsConfig tlsConfig = attachment instanceof TlsConfig ? (TlsConfig) attachment : TlsConfig.DEFAULT; if (supportedProtocols != null) { sslsock.setEnabledProtocols(supportedProtocols); } else { @@ -259,17 +288,20 @@ public Socket createLayeredSocket( } else { sslsock.setEnabledCipherSuites(TlsCiphers.excludeWeak(sslsock.getEnabledCipherSuites())); } + final Timeout handshakeTimeout = tlsConfig.getHandshakeTimeout(); + if (handshakeTimeout != null) { + sslsock.setSoTimeout(handshakeTimeout.toMillisecondsIntBound()); + } + + prepareSocket(sslsock); if (LOG.isDebugEnabled()) { LOG.debug("Enabled protocols: {}", (Object) sslsock.getEnabledProtocols()); LOG.debug("Enabled cipher suites: {}", (Object) sslsock.getEnabledCipherSuites()); + LOG.debug("Starting handshake ({})", handshakeTimeout); } - - prepareSocket(sslsock); - LOG.debug("Starting handshake"); sslsock.startHandshake(); verifyHostname(sslsock, target); - return sslsock; } private void verifyHostname(final SSLSocket sslsock, final String hostname) throws IOException { diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/examples/AsyncClientFullDuplexExchange.java b/httpclient5/src/test/java/org/apache/hc/client5/http/examples/AsyncClientFullDuplexExchange.java index a396ca322..a30661cd6 100644 --- a/httpclient5/src/test/java/org/apache/hc/client5/http/examples/AsyncClientFullDuplexExchange.java +++ b/httpclient5/src/test/java/org/apache/hc/client5/http/examples/AsyncClientFullDuplexExchange.java @@ -52,11 +52,9 @@ import org.apache.hc.core5.http.nio.support.BasicResponseConsumer; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.http.support.BasicRequestBuilder; -import org.apache.hc.core5.http2.HttpVersionPolicy; import org.apache.hc.core5.http2.config.H2Config; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.reactor.IOReactorConfig; -import org.apache.hc.core5.util.Timeout; /** * This example demonstrates a full-duplex, streaming HTTP/1.1 message exchange. @@ -65,15 +63,10 @@ public class AsyncClientFullDuplexExchange { public static void main(final String[] args) throws Exception { - final IOReactorConfig ioReactorConfig = IOReactorConfig.custom() - .setSoTimeout(Timeout.ofSeconds(5)) - .build(); - final MinimalHttpAsyncClient client = HttpAsyncClients.createMinimal( - HttpVersionPolicy.NEGOTIATE, H2Config.DEFAULT, Http1Config.DEFAULT, - ioReactorConfig); + IOReactorConfig.DEFAULT); client.start(); diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/examples/AsyncClientH2FullDuplexExchange.java b/httpclient5/src/test/java/org/apache/hc/client5/http/examples/AsyncClientH2FullDuplexExchange.java index aa90bb214..b221c7ff5 100644 --- a/httpclient5/src/test/java/org/apache/hc/client5/http/examples/AsyncClientH2FullDuplexExchange.java +++ b/httpclient5/src/test/java/org/apache/hc/client5/http/examples/AsyncClientH2FullDuplexExchange.java @@ -32,13 +32,16 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import org.apache.hc.client5.http.config.TlsConfig; import org.apache.hc.client5.http.impl.async.HttpAsyncClients; import org.apache.hc.client5.http.impl.async.MinimalHttpAsyncClient; +import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBuilder; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpResponse; +import org.apache.hc.core5.http.config.Http1Config; import org.apache.hc.core5.http.message.BasicHttpRequest; import org.apache.hc.core5.http.message.StatusLine; import org.apache.hc.core5.http.nio.AsyncClientExchangeHandler; @@ -55,7 +58,6 @@ import org.apache.hc.core5.http2.config.H2Config; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.reactor.IOReactorConfig; -import org.apache.hc.core5.util.Timeout; /** * This example demonstrates a full-duplex, streaming HTTP/2 message exchange. @@ -64,12 +66,15 @@ public class AsyncClientH2FullDuplexExchange { public static void main(final String[] args) throws Exception { - final IOReactorConfig ioReactorConfig = IOReactorConfig.custom() - .setSoTimeout(Timeout.ofSeconds(5)) - .build(); - final MinimalHttpAsyncClient client = HttpAsyncClients.createMinimal( - HttpVersionPolicy.FORCE_HTTP_2, H2Config.DEFAULT, null, ioReactorConfig); + H2Config.DEFAULT, + Http1Config.DEFAULT, + IOReactorConfig.DEFAULT, + PoolingAsyncClientConnectionManagerBuilder.create() + .setDefaultTlsConfig(TlsConfig.custom() + .setVersionPolicy(HttpVersionPolicy.FORCE_HTTP_2) + .build()) + .build()); client.start(); diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/examples/AsyncClientH2Multiplexing.java b/httpclient5/src/test/java/org/apache/hc/client5/http/examples/AsyncClientH2Multiplexing.java index 9a4077fb1..442dc669f 100644 --- a/httpclient5/src/test/java/org/apache/hc/client5/http/examples/AsyncClientH2Multiplexing.java +++ b/httpclient5/src/test/java/org/apache/hc/client5/http/examples/AsyncClientH2Multiplexing.java @@ -35,17 +35,19 @@ import org.apache.hc.client5.http.async.methods.SimpleRequestBuilder; import org.apache.hc.client5.http.async.methods.SimpleRequestProducer; import org.apache.hc.client5.http.async.methods.SimpleResponseConsumer; +import org.apache.hc.client5.http.config.TlsConfig; import org.apache.hc.client5.http.impl.async.HttpAsyncClients; import org.apache.hc.client5.http.impl.async.MinimalHttpAsyncClient; +import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBuilder; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.http.HttpHost; +import org.apache.hc.core5.http.config.Http1Config; import org.apache.hc.core5.http.message.StatusLine; import org.apache.hc.core5.http.nio.AsyncClientEndpoint; import org.apache.hc.core5.http2.HttpVersionPolicy; import org.apache.hc.core5.http2.config.H2Config; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.reactor.IOReactorConfig; -import org.apache.hc.core5.util.Timeout; /** * This example demonstrates concurrent (multiplexed) execution of multiple @@ -55,12 +57,15 @@ public class AsyncClientH2Multiplexing { public static void main(final String[] args) throws Exception { - final IOReactorConfig ioReactorConfig = IOReactorConfig.custom() - .setSoTimeout(Timeout.ofSeconds(5)) - .build(); - final MinimalHttpAsyncClient client = HttpAsyncClients.createMinimal( - HttpVersionPolicy.FORCE_HTTP_2, H2Config.DEFAULT, null, ioReactorConfig); + H2Config.DEFAULT, + Http1Config.DEFAULT, + IOReactorConfig.DEFAULT, + PoolingAsyncClientConnectionManagerBuilder.create() + .setDefaultTlsConfig(TlsConfig.custom() + .setVersionPolicy(HttpVersionPolicy.FORCE_HTTP_2) + .build()) + .build()); client.start(); diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/examples/AsyncClientH2ServerPush.java b/httpclient5/src/test/java/org/apache/hc/client5/http/examples/AsyncClientH2ServerPush.java index 2881d9b0a..dc36ade3d 100644 --- a/httpclient5/src/test/java/org/apache/hc/client5/http/examples/AsyncClientH2ServerPush.java +++ b/httpclient5/src/test/java/org/apache/hc/client5/http/examples/AsyncClientH2ServerPush.java @@ -33,8 +33,10 @@ import org.apache.hc.client5.http.async.methods.AbstractBinPushConsumer; import org.apache.hc.client5.http.async.methods.AbstractCharResponseConsumer; +import org.apache.hc.client5.http.config.TlsConfig; import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient; import org.apache.hc.client5.http.impl.async.HttpAsyncClients; +import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBuilder; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.HttpException; import org.apache.hc.core5.http.HttpRequest; @@ -46,8 +48,6 @@ import org.apache.hc.core5.http2.HttpVersionPolicy; import org.apache.hc.core5.http2.config.H2Config; import org.apache.hc.core5.io.CloseMode; -import org.apache.hc.core5.reactor.IOReactorConfig; -import org.apache.hc.core5.util.Timeout; /** * This example demonstrates handling of HTTP/2 message exchanges pushed by the server. @@ -56,18 +56,15 @@ public class AsyncClientH2ServerPush { public static void main(final String[] args) throws Exception { - final IOReactorConfig ioReactorConfig = IOReactorConfig.custom() - .setSoTimeout(Timeout.ofSeconds(5)) - .build(); - - final H2Config h2Config = H2Config.custom() - .setPushEnabled(true) - .build(); - final CloseableHttpAsyncClient client = HttpAsyncClients.custom() - .setIOReactorConfig(ioReactorConfig) - .setVersionPolicy(HttpVersionPolicy.FORCE_HTTP_2) - .setH2Config(h2Config) + .setH2Config(H2Config.custom() + .setPushEnabled(true) + .build()) + .setConnectionManager(PoolingAsyncClientConnectionManagerBuilder.create() + .setDefaultTlsConfig(TlsConfig.custom() + .setVersionPolicy(HttpVersionPolicy.FORCE_HTTP_2) + .build()) + .build()) .build(); client.start(); diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/examples/AsyncClientHttp1Pipelining.java b/httpclient5/src/test/java/org/apache/hc/client5/http/examples/AsyncClientHttp1Pipelining.java index 548c4ed1e..4e9395583 100644 --- a/httpclient5/src/test/java/org/apache/hc/client5/http/examples/AsyncClientHttp1Pipelining.java +++ b/httpclient5/src/test/java/org/apache/hc/client5/http/examples/AsyncClientHttp1Pipelining.java @@ -35,17 +35,19 @@ import org.apache.hc.client5.http.async.methods.SimpleRequestBuilder; import org.apache.hc.client5.http.async.methods.SimpleRequestProducer; import org.apache.hc.client5.http.async.methods.SimpleResponseConsumer; +import org.apache.hc.client5.http.config.TlsConfig; import org.apache.hc.client5.http.impl.async.HttpAsyncClients; import org.apache.hc.client5.http.impl.async.MinimalHttpAsyncClient; +import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBuilder; import org.apache.hc.core5.concurrent.FutureCallback; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.config.Http1Config; import org.apache.hc.core5.http.message.StatusLine; import org.apache.hc.core5.http.nio.AsyncClientEndpoint; import org.apache.hc.core5.http2.HttpVersionPolicy; +import org.apache.hc.core5.http2.config.H2Config; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.reactor.IOReactorConfig; -import org.apache.hc.core5.util.Timeout; /** * This example demonstrates pipelined execution of multiple HTTP/1.1 message exchanges. @@ -54,12 +56,15 @@ public class AsyncClientHttp1Pipelining { public static void main(final String[] args) throws Exception { - final IOReactorConfig ioReactorConfig = IOReactorConfig.custom() - .setSoTimeout(Timeout.ofSeconds(5)) - .build(); - final MinimalHttpAsyncClient client = HttpAsyncClients.createMinimal( - HttpVersionPolicy.FORCE_HTTP_1, null, Http1Config.DEFAULT, ioReactorConfig); + H2Config.DEFAULT, + Http1Config.DEFAULT, + IOReactorConfig.DEFAULT, + PoolingAsyncClientConnectionManagerBuilder.create() + .setDefaultTlsConfig(TlsConfig.custom() + .setVersionPolicy(HttpVersionPolicy.FORCE_HTTP_1) + .build()) + .build()); client.start(); diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/examples/AsyncClientTlsAlpn.java b/httpclient5/src/test/java/org/apache/hc/client5/http/examples/AsyncClientTlsAlpn.java index 22c1e377b..97db267b7 100644 --- a/httpclient5/src/test/java/org/apache/hc/client5/http/examples/AsyncClientTlsAlpn.java +++ b/httpclient5/src/test/java/org/apache/hc/client5/http/examples/AsyncClientTlsAlpn.java @@ -45,7 +45,6 @@ import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.message.StatusLine; import org.apache.hc.core5.http.nio.ssl.TlsStrategy; -import org.apache.hc.core5.http2.HttpVersionPolicy; import org.apache.hc.core5.io.CloseMode; /** @@ -74,7 +73,6 @@ public TlsDetails create(final SSLEngine sslEngine) { .setTlsStrategy(tlsStrategy) .build(); try (final CloseableHttpAsyncClient client = HttpAsyncClients.custom() - .setVersionPolicy(HttpVersionPolicy.NEGOTIATE) .setConnectionManager(cm) .build()) { diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/examples/ReactiveClientFullDuplexExchange.java b/httpclient5/src/test/java/org/apache/hc/client5/http/examples/ReactiveClientFullDuplexExchange.java index 9bb6630a0..e4fcf5644 100644 --- a/httpclient5/src/test/java/org/apache/hc/client5/http/examples/ReactiveClientFullDuplexExchange.java +++ b/httpclient5/src/test/java/org/apache/hc/client5/http/examples/ReactiveClientFullDuplexExchange.java @@ -40,13 +40,11 @@ import org.apache.hc.core5.http.Message; import org.apache.hc.core5.http.config.Http1Config; import org.apache.hc.core5.http.nio.support.BasicRequestProducer; -import org.apache.hc.core5.http2.HttpVersionPolicy; import org.apache.hc.core5.http2.config.H2Config; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.reactive.ReactiveEntityProducer; import org.apache.hc.core5.reactive.ReactiveResponseConsumer; import org.apache.hc.core5.reactor.IOReactorConfig; -import org.apache.hc.core5.util.Timeout; import org.reactivestreams.Publisher; import io.reactivex.Flowable; @@ -59,15 +57,10 @@ public class ReactiveClientFullDuplexExchange { public static void main(final String[] args) throws Exception { - final IOReactorConfig ioReactorConfig = IOReactorConfig.custom() - .setSoTimeout(Timeout.ofSeconds(5)) - .build(); - final MinimalHttpAsyncClient client = HttpAsyncClients.createMinimal( - HttpVersionPolicy.NEGOTIATE, H2Config.DEFAULT, Http1Config.DEFAULT, - ioReactorConfig); + IOReactorConfig.DEFAULT); client.start(); diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/io/TestBasicHttpClientConnectionManager.java b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/io/TestBasicHttpClientConnectionManager.java index 76f199d77..1733f4ebf 100644 --- a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/io/TestBasicHttpClientConnectionManager.java +++ b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/io/TestBasicHttpClientConnectionManager.java @@ -30,10 +30,13 @@ import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Socket; +import java.util.concurrent.TimeUnit; import org.apache.hc.client5.http.DnsResolver; import org.apache.hc.client5.http.HttpRoute; import org.apache.hc.client5.http.SchemePortResolver; +import org.apache.hc.client5.http.config.ConnectionConfig; +import org.apache.hc.client5.http.config.TlsConfig; import org.apache.hc.client5.http.io.ConnectionEndpoint; import org.apache.hc.client5.http.io.LeaseRequest; import org.apache.hc.client5.http.io.ManagedHttpClientConnection; @@ -361,26 +364,55 @@ public void testTargetConnect() throws Exception { mgr.setSocketConfig(sconfig); + final ConnectionConfig connectionConfig = ConnectionConfig.custom() + .setConnectTimeout(234, TimeUnit.MILLISECONDS) + .build(); + mgr.setConnectionConfig(connectionConfig); + final TlsConfig tlsConfig = TlsConfig.custom() + .setHandshakeTimeout(345, TimeUnit.MILLISECONDS) + .build(); + mgr.setTlsConfig(tlsConfig); + Mockito.when(dnsResolver.resolve("somehost")).thenReturn(new InetAddress[] {remote}); Mockito.when(schemePortResolver.resolve(target)).thenReturn(8443); Mockito.when(socketFactoryRegistry.lookup("https")).thenReturn(plainSocketFactory); Mockito.when(plainSocketFactory.createSocket(Mockito.any())).thenReturn(socket); Mockito.when(plainSocketFactory.connectSocket( - Mockito.any(), Mockito.eq(socket), Mockito.any(), Mockito.any(), Mockito.any(), + Mockito.any(), + Mockito.any(), Mockito.any())).thenReturn(socket); - mgr.connect(endpoint1, TimeValue.ofMilliseconds(123), context); + mgr.connect(endpoint1, null, context); Mockito.verify(dnsResolver, Mockito.times(1)).resolve("somehost"); Mockito.verify(schemePortResolver, Mockito.times(1)).resolve(target); Mockito.verify(plainSocketFactory, Mockito.times(1)).createSocket(context); - Mockito.verify(plainSocketFactory, Mockito.times(1)).connectSocket(TimeValue.ofMilliseconds(123), socket, target, + Mockito.verify(plainSocketFactory, Mockito.times(1)).connectSocket( + socket, + target, new InetSocketAddress(remote, 8443), - new InetSocketAddress(local, 0), context); + new InetSocketAddress(local, 0), + Timeout.ofMilliseconds(234), + tlsConfig, + context); + + mgr.connect(endpoint1, TimeValue.ofMilliseconds(123), context); + + Mockito.verify(dnsResolver, Mockito.times(2)).resolve("somehost"); + Mockito.verify(schemePortResolver, Mockito.times(2)).resolve(target); + Mockito.verify(plainSocketFactory, Mockito.times(2)).createSocket(context); + Mockito.verify(plainSocketFactory, Mockito.times(1)).connectSocket( + socket, + target, + new InetSocketAddress(remote, 8443), + new InetSocketAddress(local, 0), + Timeout.ofMilliseconds(123), + tlsConfig, + context); } @Test @@ -402,6 +434,15 @@ public void testProxyConnectAndUpgrade() throws Exception { mgr.setSocketConfig(sconfig); + final ConnectionConfig connectionConfig = ConnectionConfig.custom() + .setConnectTimeout(234, TimeUnit.MILLISECONDS) + .build(); + mgr.setConnectionConfig(connectionConfig); + final TlsConfig tlsConfig = TlsConfig.custom() + .setHandshakeTimeout(345, TimeUnit.MILLISECONDS) + .build(); + mgr.setTlsConfig(tlsConfig); + Mockito.when(dnsResolver.resolve("someproxy")).thenReturn(new InetAddress[] {remote}); Mockito.when(schemePortResolver.resolve(proxy)).thenReturn(8080); Mockito.when(schemePortResolver.resolve(target)).thenReturn(8443); @@ -409,21 +450,27 @@ public void testProxyConnectAndUpgrade() throws Exception { Mockito.when(socketFactoryRegistry.lookup("https")).thenReturn(sslSocketFactory); Mockito.when(plainSocketFactory.createSocket(Mockito.any())).thenReturn(socket); Mockito.when(plainSocketFactory.connectSocket( - Mockito.any(), Mockito.eq(socket), Mockito.any(), Mockito.any(), Mockito.any(), + Mockito.any(), + Mockito.any(), Mockito.any())).thenReturn(socket); - mgr.connect(endpoint1, TimeValue.ofMilliseconds(123), context); + mgr.connect(endpoint1, null, context); Mockito.verify(dnsResolver, Mockito.times(1)).resolve("someproxy"); Mockito.verify(schemePortResolver, Mockito.times(1)).resolve(proxy); Mockito.verify(plainSocketFactory, Mockito.times(1)).createSocket(context); - Mockito.verify(plainSocketFactory, Mockito.times(1)).connectSocket(TimeValue.ofMilliseconds(123), socket, proxy, + Mockito.verify(plainSocketFactory, Mockito.times(1)).connectSocket( + socket, + proxy, new InetSocketAddress(remote, 8080), - new InetSocketAddress(local, 0), context); + new InetSocketAddress(local, 0), + Timeout.ofMilliseconds(234), + tlsConfig, + context); Mockito.when(conn.getSocket()).thenReturn(socket); @@ -431,7 +478,7 @@ public void testProxyConnectAndUpgrade() throws Exception { Mockito.verify(schemePortResolver, Mockito.times(1)).resolve(target); Mockito.verify(sslSocketFactory, Mockito.times(1)).createLayeredSocket( - socket, "somehost", 8443, context); + socket, "somehost", 8443, tlsConfig, context); } } diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/io/TestHttpClientConnectionOperator.java b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/io/TestHttpClientConnectionOperator.java index 34bb77a58..f83c787dc 100644 --- a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/io/TestHttpClientConnectionOperator.java +++ b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/io/TestHttpClientConnectionOperator.java @@ -39,6 +39,7 @@ import org.apache.hc.client5.http.HttpHostConnectException; import org.apache.hc.client5.http.SchemePortResolver; import org.apache.hc.client5.http.UnsupportedSchemeException; +import org.apache.hc.client5.http.config.TlsConfig; import org.apache.hc.client5.http.io.ManagedHttpClientConnection; import org.apache.hc.client5.http.socket.ConnectionSocketFactory; import org.apache.hc.client5.http.socket.LayeredConnectionSocketFactory; @@ -48,6 +49,7 @@ import org.apache.hc.core5.http.protocol.BasicHttpContext; import org.apache.hc.core5.http.protocol.HttpContext; import org.apache.hc.core5.util.TimeValue; +import org.apache.hc.core5.util.Timeout; import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -96,6 +98,7 @@ public void testConnect() throws Exception { Mockito.any(), Mockito.any(), Mockito.any(), + Mockito.any(), Mockito.any())).thenReturn(socket); final SocketConfig socketConfig = SocketConfig.custom() @@ -105,8 +108,11 @@ public void testConnect() throws Exception { .setTcpNoDelay(true) .setSoLinger(50, TimeUnit.MILLISECONDS) .build(); + final TlsConfig tlsConfig = TlsConfig.custom() + .build(); final InetSocketAddress localAddress = new InetSocketAddress(local, 0); - connectionOperator.connect(conn, host, localAddress, TimeValue.ofMilliseconds(1000), socketConfig, context); + connectionOperator.connect(conn, host, localAddress, + Timeout.ofMilliseconds(123), socketConfig, tlsConfig, context); Mockito.verify(socket).setKeepAlive(true); Mockito.verify(socket).setReuseAddress(true); @@ -115,11 +121,12 @@ public void testConnect() throws Exception { Mockito.verify(socket).setTcpNoDelay(true); Mockito.verify(plainSocketFactory).connectSocket( - TimeValue.ofMilliseconds(1000), socket, host, new InetSocketAddress(ip1, 80), localAddress, + Timeout.ofMilliseconds(123), + tlsConfig, context); Mockito.verify(conn, Mockito.times(2)).bind(socket); } @@ -141,6 +148,7 @@ public void testConnectTimeout() throws Exception { Mockito.any(), Mockito.any(), Mockito.any(), + Mockito.any(), Mockito.any())).thenThrow(new SocketTimeoutException()); Assert.assertThrows(ConnectTimeoutException.class, () -> @@ -165,6 +173,7 @@ public void testConnectFailure() throws Exception { Mockito.any(), Mockito.any(), Mockito.any(), + Mockito.any(), Mockito.any())).thenThrow(new ConnectException()); Assert.assertThrows(HttpHostConnectException.class, () -> @@ -185,29 +194,35 @@ public void testConnectFailover() throws Exception { Mockito.when(schemePortResolver.resolve(host)).thenReturn(80); Mockito.when(plainSocketFactory.createSocket(Mockito.any())).thenReturn(socket); Mockito.when(plainSocketFactory.connectSocket( - Mockito.any(), Mockito.any(), Mockito.any(), Mockito.eq(new InetSocketAddress(ip1, 80)), Mockito.any(), + Mockito.any(), + Mockito.any(), Mockito.any())).thenThrow(new ConnectException()); Mockito.when(plainSocketFactory.connectSocket( - Mockito.any(), Mockito.any(), Mockito.any(), Mockito.eq(new InetSocketAddress(ip2, 80)), Mockito.any(), + Mockito.any(), + Mockito.any(), Mockito.any())).thenReturn(socket); final InetSocketAddress localAddress = new InetSocketAddress(local, 0); - connectionOperator.connect(conn, host, localAddress, TimeValue.ofMilliseconds(1000), SocketConfig.DEFAULT, context); + final TlsConfig tlsConfig = TlsConfig.custom() + .build(); + connectionOperator.connect(conn, host, localAddress, + Timeout.ofMilliseconds(123), SocketConfig.DEFAULT, tlsConfig, context); Mockito.verify(plainSocketFactory).connectSocket( - TimeValue.ofMilliseconds(1000), socket, host, new InetSocketAddress(ip2, 80), localAddress, + Timeout.ofMilliseconds(123), + tlsConfig, context); Mockito.verify(conn, Mockito.times(3)).bind(socket); } @@ -228,17 +243,22 @@ public void testConnectExplicitAddress() throws Exception { Mockito.any(), Mockito.any(), Mockito.any(), + Mockito.any(), Mockito.any())).thenReturn(socket); final InetSocketAddress localAddress = new InetSocketAddress(local, 0); - connectionOperator.connect(conn, host, localAddress, TimeValue.ofMilliseconds(1000), SocketConfig.DEFAULT, context); + final TlsConfig tlsConfig = TlsConfig.custom() + .build(); + connectionOperator.connect(conn, host, localAddress, + Timeout.ofMilliseconds(123), SocketConfig.DEFAULT, tlsConfig, context); Mockito.verify(plainSocketFactory).connectSocket( - TimeValue.ofMilliseconds(1000), socket, host, new InetSocketAddress(ip, 80), localAddress, + Timeout.ofMilliseconds(123), + tlsConfig, context); Mockito.verify(dnsResolver, Mockito.never()).resolve(Mockito.anyString()); Mockito.verify(conn, Mockito.times(2)).bind(socket); @@ -258,9 +278,10 @@ public void testUpgrade() throws Exception { Mockito.any(), Mockito.eq("somehost"), Mockito.eq(443), + Mockito.eq(Timeout.ofMilliseconds(345)), Mockito.any())).thenReturn(socket); - connectionOperator.upgrade(conn, host, context); + connectionOperator.upgrade(conn, host, Timeout.ofMilliseconds(345), context); Mockito.verify(conn).bind(socket); } diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/io/TestPoolingHttpClientConnectionManager.java b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/io/TestPoolingHttpClientConnectionManager.java index 29dbdd9b9..f837d2840 100644 --- a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/io/TestPoolingHttpClientConnectionManager.java +++ b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/io/TestPoolingHttpClientConnectionManager.java @@ -37,6 +37,8 @@ import org.apache.hc.client5.http.DnsResolver; import org.apache.hc.client5.http.HttpRoute; import org.apache.hc.client5.http.SchemePortResolver; +import org.apache.hc.client5.http.config.ConnectionConfig; +import org.apache.hc.client5.http.config.TlsConfig; import org.apache.hc.client5.http.io.ConnectionEndpoint; import org.apache.hc.client5.http.io.LeaseRequest; import org.apache.hc.client5.http.io.ManagedHttpClientConnection; @@ -247,26 +249,55 @@ public void testTargetConnect() throws Exception { mgr.setDefaultSocketConfig(sconfig); + final ConnectionConfig connectionConfig = ConnectionConfig.custom() + .setConnectTimeout(234, TimeUnit.MILLISECONDS) + .build(); + mgr.setDefaultConnectionConfig(connectionConfig); + final TlsConfig tlsConfig = TlsConfig.custom() + .setHandshakeTimeout(345, TimeUnit.MILLISECONDS) + .build(); + mgr.setDefaultTlsConfig(tlsConfig); + Mockito.when(dnsResolver.resolve("somehost")).thenReturn(new InetAddress[]{remote}); Mockito.when(schemePortResolver.resolve(target)).thenReturn(8443); Mockito.when(socketFactoryRegistry.lookup("https")).thenReturn(plainSocketFactory); Mockito.when(plainSocketFactory.createSocket(Mockito.any())).thenReturn(socket); Mockito.when(plainSocketFactory.connectSocket( - Mockito.any(), Mockito.eq(socket), Mockito.any(), Mockito.any(), Mockito.any(), + Mockito.any(), + Mockito.any(), Mockito.any())).thenReturn(socket); - mgr.connect(endpoint1, TimeValue.ofMilliseconds(123), context); + mgr.connect(endpoint1, null, context); Mockito.verify(dnsResolver, Mockito.times(1)).resolve("somehost"); Mockito.verify(schemePortResolver, Mockito.times(1)).resolve(target); Mockito.verify(plainSocketFactory, Mockito.times(1)).createSocket(context); - Mockito.verify(plainSocketFactory, Mockito.times(1)).connectSocket(TimeValue.ofMilliseconds(123), socket, target, + Mockito.verify(plainSocketFactory, Mockito.times(1)).connectSocket( + socket, + target, new InetSocketAddress(remote, 8443), - new InetSocketAddress(local, 0), context); + new InetSocketAddress(local, 0), + Timeout.ofMilliseconds(234), + tlsConfig, + context); + + mgr.connect(endpoint1, TimeValue.ofMilliseconds(123), context); + + Mockito.verify(dnsResolver, Mockito.times(2)).resolve("somehost"); + Mockito.verify(schemePortResolver, Mockito.times(2)).resolve(target); + Mockito.verify(plainSocketFactory, Mockito.times(2)).createSocket(context); + Mockito.verify(plainSocketFactory, Mockito.times(1)).connectSocket( + socket, + target, + new InetSocketAddress(remote, 8443), + new InetSocketAddress(local, 0), + Timeout.ofMilliseconds(123), + tlsConfig, + context); } @Test @@ -301,6 +332,15 @@ public void testProxyConnectAndUpgrade() throws Exception { mgr.setDefaultSocketConfig(sconfig); + final ConnectionConfig connectionConfig = ConnectionConfig.custom() + .setConnectTimeout(234, TimeUnit.MILLISECONDS) + .build(); + mgr.setDefaultConnectionConfig(connectionConfig); + final TlsConfig tlsConfig = TlsConfig.custom() + .setHandshakeTimeout(345, TimeUnit.MILLISECONDS) + .build(); + mgr.setDefaultTlsConfig(tlsConfig); + Mockito.when(dnsResolver.resolve("someproxy")).thenReturn(new InetAddress[] {remote}); Mockito.when(schemePortResolver.resolve(proxy)).thenReturn(8080); Mockito.when(schemePortResolver.resolve(target)).thenReturn(8443); @@ -308,21 +348,27 @@ public void testProxyConnectAndUpgrade() throws Exception { Mockito.when(socketFactoryRegistry.lookup("https")).thenReturn(sslsf); Mockito.when(plainsf.createSocket(Mockito.any())).thenReturn(mockSock); Mockito.when(plainsf.connectSocket( - Mockito.any(), Mockito.eq(mockSock), Mockito.any(), Mockito.any(), Mockito.any(), + Mockito.any(), + Mockito.any(), Mockito.any())).thenReturn(mockSock); - mgr.connect(endpoint1, TimeValue.ofMilliseconds(123), context); + mgr.connect(endpoint1, null, context); Mockito.verify(dnsResolver, Mockito.times(1)).resolve("someproxy"); Mockito.verify(schemePortResolver, Mockito.times(1)).resolve(proxy); Mockito.verify(plainsf, Mockito.times(1)).createSocket(context); - Mockito.verify(plainsf, Mockito.times(1)).connectSocket(TimeValue.ofMilliseconds(123), mockSock, proxy, + Mockito.verify(plainsf, Mockito.times(1)).connectSocket( + mockSock, + proxy, new InetSocketAddress(remote, 8080), - new InetSocketAddress(local, 0), context); + new InetSocketAddress(local, 0), + Timeout.ofMilliseconds(234), + tlsConfig, + context); Mockito.when(conn.isOpen()).thenReturn(true); Mockito.when(conn.getSocket()).thenReturn(mockSock); @@ -331,7 +377,7 @@ public void testProxyConnectAndUpgrade() throws Exception { Mockito.verify(schemePortResolver, Mockito.times(1)).resolve(target); Mockito.verify(sslsf, Mockito.times(1)).createLayeredSocket( - mockSock, "somehost", 8443, context); + mockSock, "somehost", 8443, tlsConfig, context); } }