Callbacks for Connection initialization
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 c175ca3..8212c83 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
@@ -192,6 +192,7 @@
             final InetAddress address = remoteAddresses[i];
             final boolean last = i == remoteAddresses.length - 1;
             final InetSocketAddress remoteAddress = new InetSocketAddress(address, port);
+            onBeforeSocketConnect(context, endpointHost);
             if (LOG.isDebugEnabled()) {
                 LOG.debug("{} connecting {}->{} ({})", endpointHost, localAddress, remoteAddress, connectTimeout);
             }
@@ -221,6 +222,7 @@
                 }
                 socket.connect(remoteAddress, TimeValue.isPositive(connectTimeout) ? connectTimeout.toMillisecondsIntBound() : 0);
                 conn.bind(socket);
+                onAfterSocketConnect(context, endpointHost);
                 if (LOG.isDebugEnabled()) {
                     LOG.debug("{} {} connected {}->{}", ConnPoolSupport.getId(conn), endpointHost,
                             conn.getLocalAddress(), conn.getRemoteAddress());
@@ -229,11 +231,16 @@
                 final TlsSocketStrategy tlsSocketStrategy = tlsSocketStrategyLookup != null ? tlsSocketStrategyLookup.lookup(endpointHost.getSchemeName()) : null;
                 if (tlsSocketStrategy != null) {
                     final NamedEndpoint tlsName = endpointName != null ? endpointName : endpointHost;
+                    onBeforeTlsHandshake(context, endpointHost);
                     if (LOG.isDebugEnabled()) {
                         LOG.debug("{} {} upgrading to TLS", ConnPoolSupport.getId(conn), tlsName);
                     }
                     final Socket upgradedSocket = tlsSocketStrategy.upgrade(socket, tlsName.getHostName(), tlsName.getPort(), attachment, context);
                     conn.bind(upgradedSocket);
+                    onAfterTlsHandshake(context, endpointHost);
+                    if (LOG.isDebugEnabled()) {
+                        LOG.debug("{} {} upgraded to TLS", ConnPoolSupport.getId(conn), tlsName);
+                    }
                 }
                 return;
             } catch (final RuntimeException ex) {
@@ -278,14 +285,31 @@
         final TlsSocketStrategy tlsSocketStrategy = tlsSocketStrategyLookup != null ? tlsSocketStrategyLookup.lookup(newProtocol) : null;
         if (tlsSocketStrategy != null) {
             final NamedEndpoint tlsName = endpointName != null ? endpointName : endpointHost;
+            onBeforeTlsHandshake(context, endpointHost);
             if (LOG.isDebugEnabled()) {
                 LOG.debug("{} upgrading to TLS {}:{}", ConnPoolSupport.getId(conn), tlsName.getHostName(), tlsName.getPort());
             }
             final SSLSocket upgradedSocket = tlsSocketStrategy.upgrade(socket, tlsName.getHostName(), tlsName.getPort(), attachment, context);
             conn.bind(upgradedSocket);
+            onAfterTlsHandshake(context, endpointHost);
+            if (LOG.isDebugEnabled()) {
+                LOG.debug("{} upgraded to TLS {}:{}", ConnPoolSupport.getId(conn), tlsName.getHostName(), tlsName.getPort());
+            }
         } else {
             throw new UnsupportedSchemeException(newProtocol + " protocol is not supported");
         }
     }
 
+    protected void onBeforeSocketConnect(final HttpContext httpContext, final HttpHost endpointHost) {
+    }
+
+    protected void onAfterSocketConnect(final HttpContext httpContext, final HttpHost endpointHost) {
+    }
+
+    protected void onBeforeTlsHandshake(final HttpContext httpContext, final HttpHost endpointHost) {
+    }
+
+    protected void onAfterTlsHandshake(final HttpContext httpContext, final HttpHost endpointHost) {
+    }
+
 }
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 a60e9eb..76ad4de 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
@@ -206,7 +206,7 @@
     }
 
     @Internal
-    protected PoolingHttpClientConnectionManager(
+    public PoolingHttpClientConnectionManager(
             final HttpClientConnectionOperator httpClientConnectionOperator,
             final PoolConcurrencyPolicy poolConcurrencyPolicy,
             final PoolReusePolicy poolReusePolicy,
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 dd5c543..7484a5d 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
@@ -34,9 +34,11 @@
 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.HttpClientConnectionOperator;
 import org.apache.hc.client5.http.io.ManagedHttpClientConnection;
 import org.apache.hc.client5.http.ssl.DefaultClientTlsStrategy;
 import org.apache.hc.client5.http.ssl.TlsSocketStrategy;
+import org.apache.hc.core5.annotation.Internal;
 import org.apache.hc.core5.function.Resolver;
 import org.apache.hc.core5.http.HttpHost;
 import org.apache.hc.core5.http.URIScheme;
@@ -94,7 +96,8 @@
         return new PoolingHttpClientConnectionManagerBuilder();
     }
 
-    PoolingHttpClientConnectionManagerBuilder() {
+    @Internal
+    protected PoolingHttpClientConnectionManagerBuilder() {
         super();
     }
 
@@ -273,15 +276,31 @@
         return this;
     }
 
+    @Internal
+    protected HttpClientConnectionOperator createConnectionOperator(
+            final SchemePortResolver schemePortResolver,
+            final DnsResolver dnsResolver,
+            final TlsSocketStrategy tlsSocketStrategy) {
+        return new DefaultHttpClientConnectionOperator(schemePortResolver, dnsResolver,
+                RegistryBuilder.<TlsSocketStrategy>create()
+                        .register(URIScheme.HTTPS.id, tlsSocketStrategy)
+                        .build());
+    }
+
     public PoolingHttpClientConnectionManager build() {
+        final TlsSocketStrategy tlsSocketStrategyCopy;
+        if (tlsSocketStrategy != null) {
+            tlsSocketStrategyCopy = tlsSocketStrategy;
+        } else {
+            if (systemProperties) {
+                tlsSocketStrategyCopy = DefaultClientTlsStrategy.createSystemDefault();
+            } else {
+                tlsSocketStrategyCopy = DefaultClientTlsStrategy.createDefault();
+            }
+        }
+
         final PoolingHttpClientConnectionManager poolingmgr = new PoolingHttpClientConnectionManager(
-                new DefaultHttpClientConnectionOperator(schemePortResolver, dnsResolver,
-                        RegistryBuilder.<TlsSocketStrategy>create()
-                                .register(URIScheme.HTTPS.id, tlsSocketStrategy != null ? tlsSocketStrategy :
-                                        (systemProperties ?
-                                                DefaultClientTlsStrategy.createSystemDefault() :
-                                                DefaultClientTlsStrategy.createDefault()))
-                                .build()),
+                createConnectionOperator(schemePortResolver, dnsResolver, tlsSocketStrategyCopy),
                 poolConcurrencyPolicy,
                 poolReusePolicy,
                 null,
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 2b221e7..1d35313 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
@@ -41,6 +41,7 @@
 import org.apache.hc.client5.http.nio.AsyncClientConnectionOperator;
 import org.apache.hc.client5.http.nio.ManagedAsyncClientConnection;
 import org.apache.hc.client5.http.routing.RoutingSupport;
+import org.apache.hc.core5.annotation.Internal;
 import org.apache.hc.core5.concurrent.CallbackContribution;
 import org.apache.hc.core5.concurrent.ComplexFuture;
 import org.apache.hc.core5.concurrent.FutureCallback;
@@ -59,7 +60,8 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-final class DefaultAsyncClientConnectionOperator implements AsyncClientConnectionOperator {
+@Internal
+public class DefaultAsyncClientConnectionOperator implements AsyncClientConnectionOperator {
 
     private static final Logger LOG = LoggerFactory.getLogger(DefaultAsyncClientConnectionOperator.class);
 
@@ -105,6 +107,7 @@
         final InetAddress remoteAddress = endpointHost.getAddress();
         final TlsConfig tlsConfig = attachment instanceof TlsConfig ? (TlsConfig) attachment : TlsConfig.DEFAULT;
 
+        onBeforeSocketConnect(context, endpointHost);
         if (LOG.isDebugEnabled()) {
             LOG.debug("{} connecting {}->{} ({})", endpointHost, localAddress, remoteAddress, connectTimeout);
         }
@@ -121,6 +124,7 @@
                     @Override
                     public void completed(final IOSession session) {
                         final DefaultManagedAsyncClientConnection connection = new DefaultManagedAsyncClientConnection(session);
+                        onAfterSocketConnect(context, endpointHost);
                         if (LOG.isDebugEnabled()) {
                             LOG.debug("{} {} connected {}->{}", ConnPoolSupport.getId(connection), endpointHost,
                                     connection.getLocalAddress(), connection.getRemoteAddress());
@@ -131,6 +135,7 @@
                                 final Timeout socketTimeout = connection.getSocketTimeout();
                                 final Timeout handshakeTimeout = tlsConfig.getHandshakeTimeout();
                                 final NamedEndpoint tlsName = endpointName != null ? endpointName : endpointHost;
+                                onBeforeTlsHandshake(context, endpointHost);
                                 if (LOG.isDebugEnabled()) {
                                     LOG.debug("{} {} upgrading to TLS", ConnPoolSupport.getId(connection), tlsName);
                                 }
@@ -145,6 +150,10 @@
                                             public void completed(final TransportSecurityLayer transportSecurityLayer) {
                                                 connection.setSocketTimeout(socketTimeout);
                                                 future.completed(connection);
+                                                onAfterTlsHandshake(context, endpointHost);
+                                                if (LOG.isDebugEnabled()) {
+                                                    LOG.debug("{} {} upgraded to TLS", ConnPoolSupport.getId(connection), tlsName);
+                                                }
                                             }
 
                                         });
@@ -214,4 +223,16 @@
         }
     }
 
+    protected void onBeforeSocketConnect(final HttpContext httpContext, final HttpHost endpointHost) {
+    }
+
+    protected void onAfterSocketConnect(final HttpContext httpContext, final HttpHost endpointHost) {
+    }
+
+    protected void onBeforeTlsHandshake(final HttpContext httpContext, final HttpHost endpointHost) {
+    }
+
+    protected void onAfterTlsHandshake(final HttpContext httpContext, final HttpHost endpointHost) {
+    }
+
 }
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 c7a542d..07b5a23 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
@@ -164,7 +164,7 @@
     }
 
     @Internal
-    protected PoolingAsyncClientConnectionManager(
+    public PoolingAsyncClientConnectionManager(
             final AsyncClientConnectionOperator connectionOperator,
             final PoolConcurrencyPolicy poolConcurrencyPolicy,
             final PoolReusePolicy poolReusePolicy,
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 16feeaf..d1a4ce3 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
@@ -32,8 +32,10 @@
 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.nio.AsyncClientConnectionOperator;
 import org.apache.hc.client5.http.ssl.ConscryptClientTlsStrategy;
 import org.apache.hc.client5.http.ssl.DefaultClientTlsStrategy;
+import org.apache.hc.core5.annotation.Internal;
 import org.apache.hc.core5.function.Resolver;
 import org.apache.hc.core5.http.HttpHost;
 import org.apache.hc.core5.http.URIScheme;
@@ -90,7 +92,8 @@
         return new PoolingAsyncClientConnectionManagerBuilder();
     }
 
-    PoolingAsyncClientConnectionManagerBuilder() {
+    @Internal
+    protected PoolingAsyncClientConnectionManagerBuilder() {
         super();
     }
 
@@ -229,6 +232,19 @@
         return this;
     }
 
+    @Internal
+    protected AsyncClientConnectionOperator createConnectionOperator(
+            final TlsStrategy tlsStrategy,
+            final SchemePortResolver schemePortResolver,
+            final DnsResolver dnsResolver) {
+        return new DefaultAsyncClientConnectionOperator(
+                RegistryBuilder.<TlsStrategy>create()
+                        .register(URIScheme.HTTPS.getId(), tlsStrategy)
+                        .build(),
+                schemePortResolver,
+                dnsResolver);
+    }
+
     public PoolingAsyncClientConnectionManager build() {
         final TlsStrategy tlsStrategyCopy;
         if (tlsStrategy != null) {
@@ -249,14 +265,10 @@
             }
         }
         final PoolingAsyncClientConnectionManager poolingmgr = new PoolingAsyncClientConnectionManager(
-                RegistryBuilder.<TlsStrategy>create()
-                        .register(URIScheme.HTTPS.getId(), tlsStrategyCopy)
-                        .build(),
+                createConnectionOperator(tlsStrategyCopy, schemePortResolver, dnsResolver),
                 poolConcurrencyPolicy,
                 poolReusePolicy,
-                null,
-                schemePortResolver,
-                dnsResolver);
+                null);
         poolingmgr.setConnectionConfigResolver(connectionConfigResolver);
         poolingmgr.setTlsConfigResolver(tlsConfigResolver);
         if (maxConnTotal > 0) {