You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hc.apache.org by ol...@apache.org on 2021/09/14 07:56:45 UTC

[httpcomponents-client] 01/01: HTTPCLIENT-2135: support for a distinct handshake timeout (mainly intended for TLS/SSL) by the connection management APIs

This is an automated email from the ASF dual-hosted git repository.

olegk pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/httpcomponents-client.git

commit f911b055bb23cc3f6caa55d57e2111e4461e16a5
Author: Oleg Kalnichevski <ol...@apache.org>
AuthorDate: Sun Sep 12 14:54:32 2021 +0200

    HTTPCLIENT-2135: support for a distinct handshake timeout (mainly intended for TLS/SSL) by the connection management APIs
---
 .../testing/sync/TestConnectionManagement.java     | 14 +++----
 .../hc/client5/http/config/ConnectionConfig.java   | 47 +++++++++++++++++++---
 .../impl/io/BasicHttpClientConnectionManager.java  |  4 +-
 .../io/DefaultHttpClientConnectionOperator.java    | 19 ++++++++-
 .../http/impl/io/MultihomeSocketConnector.java     | 41 +++++++++++--------
 .../io/PoolingHttpClientConnectionManager.java     |  6 ++-
 .../nio/DefaultAsyncClientConnectionOperator.java  | 18 +++++++--
 .../nio/PoolingAsyncClientConnectionManager.java   |  9 ++++-
 .../http/io/HttpClientConnectionOperator.java      | 24 +++++++++++
 .../http/nio/AsyncClientConnectionOperator.java    | 24 +++++++++++
 10 files changed, 168 insertions(+), 38 deletions(-)

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 9b1f8fd..824e4fa 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 class TestConnectionManagement extends LocalServerTestBase {
         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 class TestConnectionManagement extends LocalServerTestBase {
         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 class TestConnectionManagement extends LocalServerTestBase {
 
         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 class TestConnectionManagement extends LocalServerTestBase {
         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 class TestConnectionManagement extends LocalServerTestBase {
         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 class TestConnectionManagement extends LocalServerTestBase {
 
         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 class TestConnectionManagement extends LocalServerTestBase {
 
         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 97ed585..c8f6fb4 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
@@ -48,22 +48,25 @@ public class ConnectionConfig implements Cloneable {
 
     private final Timeout connectTimeout;
     private final Timeout socketTimeout;
+    private final Timeout handshakeTimeout;
     private final TimeValue validateAfterInactivity;
 
     /**
      * Intended for CDI compatibility
      */
     protected ConnectionConfig() {
-        this(DEFAULT_CONNECT_TIMEOUT, null, null);
+        this(DEFAULT_CONNECT_TIMEOUT, null, null, null);
     }
 
     ConnectionConfig(
             final Timeout connectTimeout,
             final Timeout socketTimeout,
+            final Timeout handshakeTimeout,
             final TimeValue validateAfterInactivity) {
         super();
         this.connectTimeout = connectTimeout;
         this.socketTimeout = socketTimeout;
+        this.handshakeTimeout = handshakeTimeout;
         this.validateAfterInactivity = validateAfterInactivity;
     }
 
@@ -82,6 +85,13 @@ public class ConnectionConfig implements Cloneable {
     }
 
     /**
+     * @see Builder#setHandshakeTimeout(Timeout)
+     */
+    public Timeout getHandshakeTimeout() {
+        return handshakeTimeout;
+    }
+
+    /**
      * @see Builder#setValidateAfterInactivity(TimeValue)
      */
     public TimeValue getValidateAfterInactivity() {
@@ -99,6 +109,7 @@ public class ConnectionConfig implements Cloneable {
         builder.append("[");
         builder.append(", connectTimeout=").append(connectTimeout);
         builder.append(", socketTimeout=").append(socketTimeout);
+        builder.append(", handshakeTimeout=").append(handshakeTimeout);
         builder.append(", validateAfterInactivity=").append(validateAfterInactivity);
         builder.append("]");
         return builder.toString();
@@ -112,6 +123,7 @@ public class ConnectionConfig implements Cloneable {
         return new Builder()
                 .setConnectTimeout(config.getConnectTimeout())
                 .setSocketTimeout(config.getSocketTimeout())
+                .setHandshakeTimeout(config.getHandshakeTimeout())
                 .setValidateAfterInactivity(config.getValidateAfterInactivity());
     }
 
@@ -119,6 +131,7 @@ public class ConnectionConfig implements Cloneable {
 
         private Timeout socketTimeout;
         private Timeout connectTimeout;
+        private Timeout handshakeTimeout;
         private TimeValue validateAfterInactivity;
 
         Builder() {
@@ -126,7 +139,6 @@ public class ConnectionConfig implements Cloneable {
             this.connectTimeout = DEFAULT_CONNECT_TIMEOUT;
         }
 
-
         /**
          * @see #setSocketTimeout(Timeout)
          */
@@ -138,7 +150,7 @@ public class ConnectionConfig implements Cloneable {
         /**
          * Determines the default socket timeout value for I/O operations.
          * <p>
-         * Default: {@code null}
+         * Default: {@code null} (undefined)
          * </p>
          *
          * @return the default socket timeout value for I/O operations.
@@ -151,7 +163,8 @@ public class ConnectionConfig implements Cloneable {
         /**
          * 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).
+         * such as {@code SSL} or {@code TLS} protocol negotiation unless
+         * a different timeout value set with {@link #setHandshakeTimeout(Timeout)}.
          * <p>
          * A timeout value of zero is interpreted as an infinite timeout.
          * </p>
@@ -173,11 +186,34 @@ public class ConnectionConfig implements Cloneable {
         }
 
         /**
+         * Determines the timeout used by transport security negotiation exchanges
+         * such as {@code SSL} or {@code TLS} protocol negotiation).
+         * <p>
+         * A timeout value of zero is interpreted as an infinite timeout.
+         * </p>
+         * <p>
+         * Default: {@code null} (undefined)
+         * </p>
+         */
+        public Builder setHandshakeTimeout(final Timeout handshakeTimeout) {
+            this.handshakeTimeout = connectTimeout;
+            return this;
+        }
+
+        /**
+         * @see #setHandshakeTimeout(Timeout)
+         */
+        public Builder setHandshakeTimeout(final long handshakeTimeout, final TimeUnit timeUnit) {
+            this.handshakeTimeout = Timeout.of(handshakeTimeout, timeUnit);
+            return this;
+        }
+
+        /**
          * Defines period of inactivity after which persistent connections must
          * be re-validated prior to being leased to the consumer. Negative values passed
          * to this method disable connection validation.
          * <p>
-         * Default: {@code null}
+         * Default: {@code null} (undefined)
          * </p>
          */
         public Builder setValidateAfterInactivity(final TimeValue validateAfterInactivity) {
@@ -197,6 +233,7 @@ public class ConnectionConfig implements Cloneable {
             return new ConnectionConfig(
                     connectTimeout != null ? connectTimeout : DEFAULT_CONNECT_TIMEOUT,
                     socketTimeout,
+                    handshakeTimeout,
                     validateAfterInactivity);
         }
 
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 b9866c1..a5c0644 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
@@ -359,7 +359,8 @@ public class BasicHttpClientConnectionManager implements HttpClientConnectionMan
             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()) : config.getConnectTimeout();
+        final Timeout handshakeTimeout = config.getHandshakeTimeout();
         final ManagedHttpClientConnection connection = internalEndpoint.getConnection();
         if (LOG.isDebugEnabled()) {
             LOG.debug("{} connecting endpoint to {} ({})", ConnPoolSupport.getId(endpoint), host, connectTimeout);
@@ -369,6 +370,7 @@ public class BasicHttpClientConnectionManager implements HttpClientConnectionMan
                 host,
                 route.getLocalSocketAddress(),
                 connectTimeout,
+                handshakeTimeout,
                 this.socketConfig,
                 context);
         if (LOG.isDebugEnabled()) {
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 27b156e..1909565 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
@@ -50,6 +50,7 @@ import org.apache.hc.core5.http.io.SocketConfig;
 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;
 
 /**
  * Default implementation of {@link HttpClientConnectionOperator} used as default in Http client,
@@ -96,7 +97,8 @@ public class DefaultHttpClientConnectionOperator implements HttpClientConnection
             final ManagedHttpClientConnection conn,
             final HttpHost host,
             final InetSocketAddress localAddress,
-            final TimeValue connectTimeout,
+            final Timeout connectTimeout,
+            final Timeout handshakeTimeout,
             final SocketConfig socketConfig,
             final HttpContext context) throws IOException {
         Args.notNull(conn, "Connection");
@@ -111,7 +113,7 @@ public class DefaultHttpClientConnectionOperator implements HttpClientConnection
         final Socket sock = sf.createSocket(context);
         conn.bind(sock);
 
-        sock.setSoTimeout(socketConfig.getSoTimeout().toMillisecondsIntBound());
+        sock.setSoTimeout((handshakeTimeout != null ? handshakeTimeout : socketConfig.getSoTimeout()).toMillisecondsIntBound());
         sock.setReuseAddress(socketConfig.isSoReuseAddress());
         sock.setTcpNoDelay(socketConfig.isTcpNoDelay());
         sock.setKeepAlive(socketConfig.isSoKeepAlive());
@@ -127,10 +129,23 @@ public class DefaultHttpClientConnectionOperator implements HttpClientConnection
             sock.setSoLinger(true, linger);
         }
         final Socket connectedSocket = multihomeSocketConnector.connect(sock, host, localAddress, connectTimeout, sf, context);
+        connectedSocket.setSoTimeout(socketConfig.getSoTimeout().toMillisecondsIntBound());
         conn.bind(connectedSocket);
     }
 
     @Override
+    public void connect(
+            final ManagedHttpClientConnection conn,
+            final HttpHost host,
+            final InetSocketAddress localAddress,
+            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, timeout, socketConfig, context);
+    }
+
+    @Override
     public void upgrade(
             final ManagedHttpClientConnection conn,
             final HttpHost host,
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/MultihomeSocketConnector.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/MultihomeSocketConnector.java
index a80d22e..0cbca88 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/MultihomeSocketConnector.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/MultihomeSocketConnector.java
@@ -55,6 +55,27 @@ final class MultihomeSocketConnector {
         this.dnsResolver = dnsResolver;
     }
 
+    private Socket doConnect(final Socket socket,
+                             final HttpHost host,
+                             final InetSocketAddress localAddress,
+                             final InetSocketAddress remoteAddress,
+                             final TimeValue connectTimeout,
+                             final ConnectionSocketFactory connectionSocketFactory,
+                             final HttpContext context) throws IOException {
+        try {
+            if (LOG.isDebugEnabled()) {
+                LOG.debug("{} connecting {} to {} ({})", host, localAddress, remoteAddress, connectTimeout);
+            }
+            final Socket connectedSocket = connectionSocketFactory.connectSocket(connectTimeout, socket, host, remoteAddress, localAddress, context);
+            if (LOG.isDebugEnabled()) {
+                LOG.debug("{} connected {}->{}", host, connectedSocket.getLocalSocketAddress(), connectedSocket.getRemoteSocketAddress());
+            }
+            return connectedSocket;
+        } catch (final IOException ex) {
+            throw ConnectExceptionSupport.enhance(ex, host, host.getAddress());
+        }
+    }
+
     public Socket connect(final Socket socket,
                           final HttpHost host,
                           final InetSocketAddress localAddress,
@@ -64,14 +85,7 @@ final class MultihomeSocketConnector {
         final int port = this.schemePortResolver.resolve(host);
         if (host.getAddress() != null) {
             final InetSocketAddress remoteAddress = new InetSocketAddress(host.getAddress(), port);
-            if (LOG.isDebugEnabled()) {
-                LOG.debug("{} connecting {} to {} ({})", host, localAddress, remoteAddress, connectTimeout);
-            }
-            final Socket connectedSocket = connectionSocketFactory.connectSocket(connectTimeout, socket, host, remoteAddress, localAddress, context);
-            if (LOG.isDebugEnabled()) {
-                LOG.debug("{} connected {}->{}", host, connectedSocket.getLocalSocketAddress(), connectedSocket.getRemoteSocketAddress());
-            }
-            return connectedSocket;
+            return doConnect(socket, host, localAddress, remoteAddress, connectTimeout, connectionSocketFactory, context);
         }
 
         if (LOG.isDebugEnabled()) {
@@ -87,18 +101,11 @@ final class MultihomeSocketConnector {
         for (int i = 0; i < remoteAddresses.length; i++) {
             final InetSocketAddress remoteAddress = new InetSocketAddress(remoteAddresses[i], port);
             final boolean last = i == remoteAddresses.length - 1;
-            if (LOG.isDebugEnabled()) {
-                LOG.debug("{} connecting to {}", host, remoteAddress);
-            }
             try {
-                final Socket connectedSocket = connectionSocketFactory.connectSocket(connectTimeout, socket, host, remoteAddress, localAddress, context);
-                if (LOG.isDebugEnabled()) {
-                    LOG.debug("{} connected {}->{}", host, connectedSocket.getLocalSocketAddress(), connectedSocket.getRemoteSocketAddress());
-                }
-                return connectedSocket;
+                return doConnect(socket, host, localAddress, remoteAddress, connectTimeout, connectionSocketFactory, context);
             } catch (final IOException ex) {
                 if (last) {
-                    throw ConnectExceptionSupport.enhance(ex, host, remoteAddresses);
+                    throw ex;
                 }
                 if (LOG.isDebugEnabled()) {
                     LOG.debug("{} connection to {} failed ({}); retrying connection to the next address", host, remoteAddress, ex.getClass());
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 22a29e9..e74c25f 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
@@ -409,7 +409,8 @@ public class PoolingHttpClientConnectionManager
         }
         final SocketConfig socketConfig = resolveSocketConfig(route);
         final ConnectionConfig connectionConfig = resolveConnectionConfig(route);
-        final TimeValue connectTimeout = timeout != null ? timeout : connectionConfig.getConnectTimeout();
+        final Timeout connectTimeout = timeout != null ? Timeout.of(timeout.getDuration(), timeout.getTimeUnit()) : connectionConfig.getConnectTimeout();
+        final Timeout handshakeTimeout = connectionConfig.getHandshakeTimeout();
         if (LOG.isDebugEnabled()) {
             LOG.debug("{} connecting endpoint to {} ({})", ConnPoolSupport.getId(endpoint), host, connectTimeout);
         }
@@ -418,7 +419,8 @@ public class PoolingHttpClientConnectionManager
                 conn,
                 host,
                 route.getLocalSocketAddress(),
-                timeout,
+                connectTimeout,
+                handshakeTimeout,
                 socketConfig,
                 context);
         if (LOG.isDebugEnabled()) {
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 a781e9b..747ddda 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
@@ -73,10 +73,9 @@ final class DefaultAsyncClientConnectionOperator implements AsyncClientConnectio
             final HttpHost host,
             final SocketAddress localAddress,
             final Timeout connectTimeout,
+            final Timeout handshakeTimeout,
             final Object attachment,
             final FutureCallback<ManagedAsyncClientConnection> callback) {
-        Args.notNull(connectionInitiator, "Connection initiator");
-        Args.notNull(host, "Host");
         final ComplexFuture<ManagedAsyncClientConnection> future = new ComplexFuture<>(callback);
         final HttpHost remoteEndpoint = RoutingSupport.normalize(host, schemePortResolver);
         final InetAddress remoteAddress = host.getAddress();
@@ -95,15 +94,17 @@ final class DefaultAsyncClientConnectionOperator implements AsyncClientConnectio
                         final DefaultManagedAsyncClientConnection connection = new DefaultManagedAsyncClientConnection(session);
                         if (tlsStrategy != null && URIScheme.HTTPS.same(host.getSchemeName())) {
                             try {
+                                final Timeout socketTimeout = connection.getSocketTimeout();
                                 tlsStrategy.upgrade(
                                         connection,
                                         host,
                                         attachment,
-                                        null,
+                                        handshakeTimeout != null ? handshakeTimeout : connectTimeout,
                                         new FutureContribution<TransportSecurityLayer>(future) {
 
                                             @Override
                                             public void completed(final TransportSecurityLayer transportSecurityLayer) {
+                                                connection.setSocketTimeout(socketTimeout);
                                                 future.completed(connection);
                                             }
 
@@ -132,6 +133,17 @@ final class DefaultAsyncClientConnectionOperator implements AsyncClientConnectio
     }
 
     @Override
+    public Future<ManagedAsyncClientConnection> connect(
+            final ConnectionInitiator connectionInitiator,
+            final HttpHost host,
+            final SocketAddress localAddress,
+            final Timeout connectTimeout,
+            final Object attachment,
+            final FutureCallback<ManagedAsyncClientConnection> callback) {
+        return connect(connectionInitiator, host, localAddress, connectTimeout, connectTimeout, attachment, callback);
+    }
+
+    @Override
     public void upgrade(
             final ManagedAsyncClientConnection connection,
             final HttpHost host,
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 3bc64ab..7c8089d 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
@@ -400,12 +400,19 @@ public class PoolingAsyncClientConnectionManager implements AsyncClientConnectio
         final InetSocketAddress localAddress = route.getLocalSocketAddress();
         final ConnectionConfig connectionConfig = resolveConnectionConfig(route);
         final Timeout connectTimeout = timeout != null ? timeout : connectionConfig.getConnectTimeout();
+        final Timeout handshakeTimeout = connectionConfig.getHandshakeTimeout();
 
         if (LOG.isDebugEnabled()) {
             LOG.debug("{} connecting endpoint to {} ({})", ConnPoolSupport.getId(endpoint), host, connectTimeout);
         }
         final Future<ManagedAsyncClientConnection> connectFuture = connectionOperator.connect(
-                connectionInitiator, host, localAddress, connectTimeout, attachment, new FutureCallback<ManagedAsyncClientConnection>() {
+                connectionInitiator,
+                host,
+                localAddress,
+                connectTimeout,
+                handshakeTimeout,
+                attachment,
+                new FutureCallback<ManagedAsyncClientConnection>() {
 
                     @Override
                     public void completed(final ManagedAsyncClientConnection connection) {
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 d1bf4fa..74a561f 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.HttpHost;
 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.
@@ -66,6 +67,29 @@ public interface HttpClientConnectionOperator {
             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 context the execution context.
+     *
+     * @since 5.2
+     */
+    default void connect(
+            ManagedHttpClientConnection conn,
+            HttpHost host,
+            InetSocketAddress localAddress,
+            Timeout connectTimeout,
+            Timeout handshakeTimeout,
+            SocketConfig socketConfig,
+            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.
      *
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 af12604..2ad7dee 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,6 +67,30 @@ public interface AsyncClientConnectionOperator {
             Object attachment,
             FutureCallback<ManagedAsyncClientConnection> callback);
 
+    /**
+     * Initiates operation to create a connection to the remote endpoint using
+     * the provided {@link ConnectionInitiator}.
+     *
+     * @param connectionInitiator the connection initiator.
+     * @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 attachment the attachment, which can be any object representing custom parameter
+     *                    of the operation.
+     * @param callback the future result callback.
+     *
+     * @since 5.2
+     */
+    default Future<ManagedAsyncClientConnection> connect(
+            ConnectionInitiator connectionInitiator,
+            HttpHost host,
+            SocketAddress localAddress,
+            Timeout connectTimeout,
+            Timeout handshakeTimeout,
+            Object attachment,
+            FutureCallback<ManagedAsyncClientConnection> callback) {
+        return connect(connectionInitiator, host, localAddress, connectTimeout, handshakeTimeout, attachment, callback);
+    }
 
     /**
      * Upgrades transport security of the given managed connection