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 08:15:33 UTC

[httpcomponents-client] branch master updated (f911b05 -> 4b7057e)

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

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


 discard f911b05  HTTPCLIENT-2135: support for a distinct handshake timeout (mainly intended for TLS/SSL) by the connection management APIs
 discard ce67fa6  Extracted multi-home socket connect logic into a separate utility class; made more consistent with async multi-home connect code
     new e544e07  Extracted multi-home socket connect logic into a separate utility class; made more consistent with async multi-home connect code
     new 4b7057e  HTTPCLIENT-2135: support for a distinct handshake timeout (mainly intended for TLS/SSL) by the connection management APIs

This update added new revisions after undoing existing revisions.
That is to say, some revisions that were in the old version of the
branch are not in the new version.  This situation occurs
when a user --force pushes a change and generates a repository
containing something like this:

 * -- * -- B -- O -- O -- O   (f911b05)
            \
             N -- N -- N   refs/heads/master (4b7057e)

You should already have received notification emails for all of the O
revisions, and so the following emails describe only the N revisions
from the common base, B.

Any revisions marked "omit" are not gone; other references still
refer to them.  Any revisions marked "discard" are gone forever.

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../http/impl/io/MultihomeSocketConnector.java     | 26 +++++++++++-----------
 .../nio/DefaultAsyncClientConnectionOperator.java  |  2 ++
 2 files changed, 15 insertions(+), 13 deletions(-)

[httpcomponents-client] 01/02: Extracted multi-home socket connect logic into a separate utility class; made more consistent with async multi-home connect code

Posted by ol...@apache.org.
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 e544e078e3a3718c79127904d5910d44bc1fdacf
Author: Oleg Kalnichevski <ol...@apache.org>
AuthorDate: Sun Sep 12 13:56:59 2021 +0200

    Extracted multi-home socket connect logic into a separate utility class; made more consistent with async multi-home connect code
---
 .../impl/io/BasicHttpClientConnectionManager.java  |   6 ++
 .../io/DefaultHttpClientConnectionOperator.java    |  76 ++++---------
 .../http/impl/io/MultihomeSocketConnector.java     | 118 +++++++++++++++++++++
 .../http/impl/nio/MultihomeIOSessionRequester.java |   4 +-
 .../impl/io/TestHttpClientConnectionOperator.java  |   2 +-
 5 files changed, 148 insertions(+), 58 deletions(-)

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 a529dfb..b9866c1 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
@@ -361,6 +361,9 @@ public class BasicHttpClientConnectionManager implements HttpClientConnectionMan
         final ConnectionConfig config = connectionConfig != null ? connectionConfig : ConnectionConfig.DEFAULT;
         final TimeValue connectTimeout = timeout != null ? timeout : config.getConnectTimeout();
         final ManagedHttpClientConnection connection = internalEndpoint.getConnection();
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("{} connecting endpoint to {} ({})", ConnPoolSupport.getId(endpoint), host, connectTimeout);
+        }
         this.connectionOperator.connect(
                 connection,
                 host,
@@ -368,6 +371,9 @@ public class BasicHttpClientConnectionManager implements HttpClientConnectionMan
                 connectTimeout,
                 this.socketConfig,
                 context);
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("{} connected {}", ConnPoolSupport.getId(endpoint), ConnPoolSupport.getId(conn));
+        }
         final Timeout socketTimeout = config.getSocketTimeout();
         if (socketTimeout != null) {
             connection.setSocketTimeout(socketTimeout);
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 14a45f0..27b156e 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
@@ -27,16 +27,13 @@
 package org.apache.hc.client5.http.impl.io;
 
 import java.io.IOException;
-import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.net.Socket;
 
-import org.apache.hc.client5.http.ConnectExceptionSupport;
 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.UnsupportedSchemeException;
-import org.apache.hc.client5.http.impl.ConnPoolSupport;
 import org.apache.hc.client5.http.impl.DefaultSchemePortResolver;
 import org.apache.hc.client5.http.io.HttpClientConnectionOperator;
 import org.apache.hc.client5.http.io.ManagedHttpClientConnection;
@@ -53,8 +50,6 @@ 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.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 /**
  * Default implementation of {@link HttpClientConnectionOperator} used as default in Http client,
@@ -69,11 +64,9 @@ public class DefaultHttpClientConnectionOperator implements HttpClientConnection
 
     static final String SOCKET_FACTORY_REGISTRY = "http.socket-factory-registry";
 
-    private static final Logger LOG = LoggerFactory.getLogger(DefaultHttpClientConnectionOperator.class);
-
     private final Lookup<ConnectionSocketFactory> socketFactoryRegistry;
     private final SchemePortResolver schemePortResolver;
-    private final DnsResolver dnsResolver;
+    private final MultihomeSocketConnector multihomeSocketConnector;
 
     public DefaultHttpClientConnectionOperator(
             final Lookup<ConnectionSocketFactory> socketFactoryRegistry,
@@ -82,10 +75,10 @@ public class DefaultHttpClientConnectionOperator implements HttpClientConnection
         super();
         Args.notNull(socketFactoryRegistry, "Socket factory registry");
         this.socketFactoryRegistry = socketFactoryRegistry;
-        this.schemePortResolver = schemePortResolver != null ? schemePortResolver :
-            DefaultSchemePortResolver.INSTANCE;
-        this.dnsResolver = dnsResolver != null ? dnsResolver :
-            SystemDefaultDnsResolver.INSTANCE;
+        this.schemePortResolver = schemePortResolver != null ? schemePortResolver : DefaultSchemePortResolver.INSTANCE;
+        this.multihomeSocketConnector = new MultihomeSocketConnector(
+                schemePortResolver != null ? schemePortResolver : DefaultSchemePortResolver.INSTANCE,
+                dnsResolver != null ? dnsResolver : SystemDefaultDnsResolver.INSTANCE);
     }
 
     @SuppressWarnings("unchecked")
@@ -115,51 +108,26 @@ public class DefaultHttpClientConnectionOperator implements HttpClientConnection
         if (sf == null) {
             throw new UnsupportedSchemeException(host.getSchemeName() + " protocol is not supported");
         }
-        final InetAddress[] addresses = host.getAddress() != null ?
-                new InetAddress[] { host.getAddress() } : this.dnsResolver.resolve(host.getHostName());
-        final int port = this.schemePortResolver.resolve(host);
-        for (int i = 0; i < addresses.length; i++) {
-            final InetAddress address = addresses[i];
-            final boolean last = i == addresses.length - 1;
-
-            Socket sock = sf.createSocket(context);
-            sock.setSoTimeout(socketConfig.getSoTimeout().toMillisecondsIntBound());
-            sock.setReuseAddress(socketConfig.isSoReuseAddress());
-            sock.setTcpNoDelay(socketConfig.isTcpNoDelay());
-            sock.setKeepAlive(socketConfig.isSoKeepAlive());
-            if (socketConfig.getRcvBufSize() > 0) {
-                sock.setReceiveBufferSize(socketConfig.getRcvBufSize());
-            }
-            if (socketConfig.getSndBufSize() > 0) {
-                sock.setSendBufferSize(socketConfig.getSndBufSize());
-            }
+        final Socket sock = sf.createSocket(context);
+        conn.bind(sock);
 
-            final int linger = socketConfig.getSoLinger().toMillisecondsIntBound();
-            if (linger >= 0) {
-                sock.setSoLinger(true, linger);
-            }
-            conn.bind(sock);
+        sock.setSoTimeout(socketConfig.getSoTimeout().toMillisecondsIntBound());
+        sock.setReuseAddress(socketConfig.isSoReuseAddress());
+        sock.setTcpNoDelay(socketConfig.isTcpNoDelay());
+        sock.setKeepAlive(socketConfig.isSoKeepAlive());
+        if (socketConfig.getRcvBufSize() > 0) {
+            sock.setReceiveBufferSize(socketConfig.getRcvBufSize());
+        }
+        if (socketConfig.getSndBufSize() > 0) {
+            sock.setSendBufferSize(socketConfig.getSndBufSize());
+        }
 
-            final InetSocketAddress remoteAddress = new InetSocketAddress(address, port);
-            if (LOG.isDebugEnabled()) {
-                LOG.debug("{} connecting to {}", ConnPoolSupport.getId(conn), remoteAddress);
-            }
-            try {
-                sock = sf.connectSocket(connectTimeout, sock, host, remoteAddress, localAddress, context);
-                conn.bind(sock);
-                if (LOG.isDebugEnabled()) {
-                    LOG.debug("{} connection established {}", ConnPoolSupport.getId(conn), conn);
-                }
-                return;
-            } catch (final IOException ex) {
-                if (last) {
-                    throw ConnectExceptionSupport.enhance(ex, host, addresses);
-                }
-            }
-            if (LOG.isDebugEnabled()) {
-                LOG.debug("{} connect to {} timed out. Connection will be retried using another IP address", ConnPoolSupport.getId(conn), remoteAddress);
-            }
+        final int linger = socketConfig.getSoLinger().toMillisecondsIntBound();
+        if (linger >= 0) {
+            sock.setSoLinger(true, linger);
         }
+        final Socket connectedSocket = multihomeSocketConnector.connect(sock, host, localAddress, connectTimeout, sf, context);
+        conn.bind(connectedSocket);
     }
 
     @Override
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
new file mode 100644
index 0000000..de05e91
--- /dev/null
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/MultihomeSocketConnector.java
@@ -0,0 +1,118 @@
+/*
+ * ====================================================================
+ * 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
+ * <http://www.apache.org/>.
+ *
+ */
+package org.apache.hc.client5.http.impl.io;
+
+import java.io.IOException;
+import java.net.ConnectException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.util.Arrays;
+
+import org.apache.hc.client5.http.ConnectExceptionSupport;
+import org.apache.hc.client5.http.DnsResolver;
+import org.apache.hc.client5.http.SchemePortResolver;
+import org.apache.hc.client5.http.socket.ConnectionSocketFactory;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.protocol.HttpContext;
+import org.apache.hc.core5.util.TimeValue;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+final class MultihomeSocketConnector {
+
+    private static final Logger LOG = LoggerFactory.getLogger(MultihomeSocketConnector.class);
+
+    private final SchemePortResolver schemePortResolver;
+    private final DnsResolver dnsResolver;
+
+    MultihomeSocketConnector(final SchemePortResolver schemePortResolver, final DnsResolver dnsResolver) {
+        this.schemePortResolver = schemePortResolver;
+        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 {
+        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;
+    }
+
+    public Socket connect(final Socket socket,
+                          final HttpHost host,
+                          final InetSocketAddress localAddress,
+                          final TimeValue connectTimeout,
+                          final ConnectionSocketFactory connectionSocketFactory,
+                          final HttpContext context) throws IOException {
+        final int port = this.schemePortResolver.resolve(host);
+        if (host.getAddress() != null) {
+            final InetSocketAddress remoteAddress = new InetSocketAddress(host.getAddress(), port);
+            try {
+                return doConnect(socket, host, localAddress, remoteAddress, connectTimeout, connectionSocketFactory, context);
+            } catch (final IOException ex) {
+                throw ConnectExceptionSupport.enhance(ex, host, host.getAddress());
+            }
+        }
+
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("{} resolving remote address", host);
+        }
+
+        final InetAddress[] remoteAddresses = this.dnsResolver.resolve(host.getHostName());
+
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("{} resolved to {}", host, Arrays.asList(remoteAddresses));
+        }
+
+        for (int i = 0; i < remoteAddresses.length; i++) {
+            final InetSocketAddress remoteAddress = new InetSocketAddress(remoteAddresses[i], port);
+            final boolean last = i == remoteAddresses.length - 1;
+            try {
+                return doConnect(socket, host, localAddress, remoteAddress, connectTimeout, connectionSocketFactory, context);
+            } catch (final IOException ex) {
+                if (last) {
+                    throw ConnectExceptionSupport.enhance(ex, host, remoteAddresses);
+                }
+                if (LOG.isDebugEnabled()) {
+                    LOG.debug("{} connection to {} failed ({}); retrying connection to the next address", host, remoteAddress, ex.getClass());
+                }
+            }
+        }
+        throw ConnectExceptionSupport.enhance(new ConnectException(), host, remoteAddresses);
+    }
+
+}
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/nio/MultihomeIOSessionRequester.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/nio/MultihomeIOSessionRequester.java
index 4d90b85..e5b70e9 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/nio/MultihomeIOSessionRequester.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/nio/MultihomeIOSessionRequester.java
@@ -113,9 +113,7 @@ final class MultihomeIOSessionRequester {
                             @Override
                             public void completed(final IOSession session) {
                                 if (LOG.isDebugEnabled()) {
-                                    if (LOG.isDebugEnabled()) {
-                                        LOG.debug("{} connected {} {}->{}", remoteEndpoint, session.getId(), session.getLocalAddress(), session.getRemoteAddress());
-                                    }
+                                    LOG.debug("{} connected {} {}->{}", remoteEndpoint, session.getId(), session.getLocalAddress(), session.getRemoteAddress());
                                 }
                                 future.completed(session);
                             }
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 34bb77a..6becde8 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
@@ -209,7 +209,7 @@ public class TestHttpClientConnectionOperator {
                 new InetSocketAddress(ip2, 80),
                 localAddress,
                 context);
-        Mockito.verify(conn, Mockito.times(3)).bind(socket);
+        Mockito.verify(conn, Mockito.times(2)).bind(socket);
     }
 
     @Test

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

Posted by ol...@apache.org.
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 4b7057ed73ca287665d4fef2b23532a3547a3fd4
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 ++++++++-
 .../io/PoolingHttpClientConnectionManager.java     |  6 ++-
 .../nio/DefaultAsyncClientConnectionOperator.java  | 16 +++++++-
 .../nio/PoolingAsyncClientConnectionManager.java   |  9 ++++-
 .../http/io/HttpClientConnectionOperator.java      | 24 +++++++++++
 .../http/nio/AsyncClientConnectionOperator.java    | 24 +++++++++++
 9 files changed, 144 insertions(+), 19 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/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..a5dc368 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,6 +73,7 @@ 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");
@@ -95,15 +96,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 +135,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