HTTPCLIENT-2120: support for H2 via HTTP/1.1 proxy
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 7773bbe..763d9c3 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
@@ -88,7 +88,14 @@
                         new HttpHost("http", "localhost", 8080), null, null),
                 new HttpAsyncClientCompatibilityTest(
                         HttpVersionPolicy.FORCE_HTTP_2,
-                        new HttpHost("https", "localhost", 8443), null, null)
+                        new HttpHost("https", "localhost", 8443), null, null),
+                new HttpAsyncClientCompatibilityTest(
+                        HttpVersionPolicy.NEGOTIATE,
+                        new HttpHost("https", "test-httpd", 8443), new HttpHost("localhost", 8888), null),
+                new HttpAsyncClientCompatibilityTest(
+                        HttpVersionPolicy.NEGOTIATE,
+                        new HttpHost("https", "test-httpd", 8443), new HttpHost("localhost", 8889),
+                        new UsernamePasswordCredentials("squid", "nopassword".toCharArray()))
         };
         for (final HttpAsyncClientCompatibilityTest test: tests) {
             try {
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/async/AsyncExecRuntime.java b/httpclient5/src/main/java/org/apache/hc/client5/http/async/AsyncExecRuntime.java
index d7b6a6e..237a5e2 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/async/AsyncExecRuntime.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/async/AsyncExecRuntime.java
@@ -114,6 +114,21 @@
     void upgradeTls(HttpClientContext context);
 
     /**
+     * Upgrades transport security of the active connection by using the TLS security protocol.
+     *
+     * @param context the execution context.
+     *
+     * @since 5.2
+     */
+    default void upgradeTls(HttpClientContext context,
+                            FutureCallback<AsyncExecRuntime> callback) {
+        upgradeTls(context);
+        if (callback != null) {
+            callback.completed(this);
+        }
+    }
+
+    /**
      * Validates the connection making sure it can be used to execute requests.
      *
      * @return {@code true} if the connection is valid, {@code false}.
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncConnectExec.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncConnectExec.java
index fca3b74..0ca255e 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncConnectExec.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncConnectExec.java
@@ -187,137 +187,152 @@
         final CancellableDependency operation = scope.cancellableDependency;
         final HttpClientContext clientContext = scope.clientContext;
 
-        int step;
-        do {
-            final HttpRoute fact = tracker.toRoute();
-            step = routeDirector.nextStep(route, fact);
-            switch (step) {
-                case HttpRouteDirector.CONNECT_TARGET:
-                    operation.setDependency(execRuntime.connectEndpoint(clientContext, new FutureCallback<AsyncExecRuntime>() {
+        final HttpRoute fact = tracker.toRoute();
+        final int step = routeDirector.nextStep(route, fact);
+
+        switch (step) {
+            case HttpRouteDirector.CONNECT_TARGET:
+                operation.setDependency(execRuntime.connectEndpoint(clientContext, new FutureCallback<AsyncExecRuntime>() {
+
+                    @Override
+                    public void completed(final AsyncExecRuntime execRuntime) {
+                        tracker.connectTarget(route.isSecure());
+                        if (LOG.isDebugEnabled()) {
+                            LOG.debug("{} connected to target", exchangeId);
+                        }
+                        proceedToNextHop(state, request, entityProducer, scope, chain, asyncExecCallback);
+                    }
+
+                    @Override
+                    public void failed(final Exception ex) {
+                        asyncExecCallback.failed(ex);
+                    }
+
+                    @Override
+                    public void cancelled() {
+                        asyncExecCallback.failed(new InterruptedIOException());
+                    }
+
+                }));
+                break;
+
+            case HttpRouteDirector.CONNECT_PROXY:
+                operation.setDependency(execRuntime.connectEndpoint(clientContext, new FutureCallback<AsyncExecRuntime>() {
+
+                    @Override
+                    public void completed(final AsyncExecRuntime execRuntime) {
+                        final HttpHost proxy  = route.getProxyHost();
+                        tracker.connectProxy(proxy, route.isSecure() && !route.isTunnelled());
+                        if (LOG.isDebugEnabled()) {
+                            LOG.debug("{} connected to proxy", exchangeId);
+                        }
+                        proceedToNextHop(state, request, entityProducer, scope, chain, asyncExecCallback);
+                    }
+
+                    @Override
+                    public void failed(final Exception ex) {
+                        asyncExecCallback.failed(ex);
+                    }
+
+                    @Override
+                    public void cancelled() {
+                        asyncExecCallback.failed(new InterruptedIOException());
+                    }
+
+                }));
+                break;
+
+            case HttpRouteDirector.TUNNEL_TARGET:
+                try {
+                    final HttpHost proxy = route.getProxyHost();
+                    final HttpHost target = route.getTargetHost();
+                    createTunnel(state, proxy ,target, scope, chain, new AsyncExecCallback() {
 
                         @Override
-                        public void completed(final AsyncExecRuntime execRuntime) {
-                            tracker.connectTarget(route.isSecure());
+                        public AsyncDataConsumer handleResponse(
+                                final HttpResponse response,
+                                final EntityDetails entityDetails) throws HttpException, IOException {
+                            return asyncExecCallback.handleResponse(response, entityDetails);
+                        }
+
+                        @Override
+                        public void handleInformationResponse(
+                                final HttpResponse response) throws HttpException, IOException {
+                            asyncExecCallback.handleInformationResponse(response);
+                        }
+
+                        @Override
+                        public void completed() {
                             if (LOG.isDebugEnabled()) {
-                                LOG.debug("{} connected to target", exchangeId);
+                                LOG.debug("{} tunnel to target created", exchangeId);
                             }
+                            tracker.tunnelTarget(false);
                             proceedToNextHop(state, request, entityProducer, scope, chain, asyncExecCallback);
                         }
 
                         @Override
-                        public void failed(final Exception ex) {
-                            asyncExecCallback.failed(ex);
+                        public void failed(final Exception cause) {
+                            asyncExecCallback.failed(cause);
                         }
 
-                        @Override
-                        public void cancelled() {
-                            asyncExecCallback.failed(new InterruptedIOException());
+                    });
+                } catch (final HttpException | IOException ex) {
+                    asyncExecCallback.failed(ex);
+                }
+                break;
+
+            case HttpRouteDirector.TUNNEL_PROXY:
+                // The most simple example for this case is a proxy chain
+                // of two proxies, where P1 must be tunnelled to P2.
+                // route: Source -> P1 -> P2 -> Target (3 hops)
+                // fact:  Source -> P1 -> Target       (2 hops)
+                asyncExecCallback.failed(new HttpException("Proxy chains are not supported"));
+                break;
+
+            case HttpRouteDirector.LAYER_PROTOCOL:
+                execRuntime.upgradeTls(clientContext, new FutureCallback<AsyncExecRuntime>() {
+
+                    @Override
+                    public void completed(final AsyncExecRuntime asyncExecRuntime) {
+                        if (LOG.isDebugEnabled()) {
+                            LOG.debug("{} upgraded to TLS", exchangeId);
                         }
+                        tracker.layerProtocol(route.isSecure());
+                        proceedToNextHop(state, request, entityProducer, scope, chain, asyncExecCallback);
+                    }
 
-                    }));
-                    return;
-
-                case HttpRouteDirector.CONNECT_PROXY:
-                    operation.setDependency(execRuntime.connectEndpoint(clientContext, new FutureCallback<AsyncExecRuntime>() {
-
-                        @Override
-                        public void completed(final AsyncExecRuntime execRuntime) {
-                            final HttpHost proxy  = route.getProxyHost();
-                            tracker.connectProxy(proxy, route.isSecure() && !route.isTunnelled());
-                            if (LOG.isDebugEnabled()) {
-                                LOG.debug("{} connected to proxy", exchangeId);
-                            }
-                            proceedToNextHop(state, request, entityProducer, scope, chain, asyncExecCallback);
-                        }
-
-                        @Override
-                        public void failed(final Exception ex) {
-                            asyncExecCallback.failed(ex);
-                        }
-
-                        @Override
-                        public void cancelled() {
-                            asyncExecCallback.failed(new InterruptedIOException());
-                        }
-
-                    }));
-                    return;
-
-                case HttpRouteDirector.TUNNEL_TARGET:
-                    try {
-                        final HttpHost proxy = route.getProxyHost();
-                        final HttpHost target = route.getTargetHost();
-                        createTunnel(state, proxy ,target, scope, chain, new AsyncExecCallback() {
-
-                            @Override
-                            public AsyncDataConsumer handleResponse(
-                                    final HttpResponse response,
-                                    final EntityDetails entityDetails) throws HttpException, IOException {
-                                return asyncExecCallback.handleResponse(response, entityDetails);
-                            }
-
-                            @Override
-                            public void handleInformationResponse(
-                                    final HttpResponse response) throws HttpException, IOException {
-                                asyncExecCallback.handleInformationResponse(response);
-                            }
-
-                            @Override
-                            public void completed() {
-                                if (LOG.isDebugEnabled()) {
-                                    LOG.debug("{} tunnel to target created", exchangeId);
-                                }
-                                tracker.tunnelTarget(false);
-                                proceedToNextHop(state, request, entityProducer, scope, chain, asyncExecCallback);
-                            }
-
-                            @Override
-                            public void failed(final Exception cause) {
-                                asyncExecCallback.failed(cause);
-                            }
-
-                        });
-                    } catch (final HttpException | IOException ex) {
+                    @Override
+                    public void failed(final Exception ex) {
                         asyncExecCallback.failed(ex);
                     }
-                    return;
 
-                case HttpRouteDirector.TUNNEL_PROXY:
-                    // The most simple example for this case is a proxy chain
-                    // of two proxies, where P1 must be tunnelled to P2.
-                    // route: Source -> P1 -> P2 -> Target (3 hops)
-                    // fact:  Source -> P1 -> Target       (2 hops)
-                    asyncExecCallback.failed(new HttpException("Proxy chains are not supported"));
-                    return;
-
-                case HttpRouteDirector.LAYER_PROTOCOL:
-                    execRuntime.upgradeTls(clientContext);
-                    if (LOG.isDebugEnabled()) {
-                        LOG.debug("{} upgraded to TLS", exchangeId);
+                    @Override
+                    public void cancelled() {
+                        asyncExecCallback.failed(new InterruptedIOException());
                     }
-                    tracker.layerProtocol(route.isSecure());
-                    break;
 
-                case HttpRouteDirector.UNREACHABLE:
-                    asyncExecCallback.failed(new HttpException("Unable to establish route: " +
-                            "planned = " + route + "; current = " + fact));
-                    return;
+                });
+                break;
 
-                case HttpRouteDirector.COMPLETE:
-                    if (LOG.isDebugEnabled()) {
-                        LOG.debug("{} route fully established", exchangeId);
-                    }
-                    try {
-                        chain.proceed(request, entityProducer, scope, asyncExecCallback);
-                    } catch (final HttpException | IOException ex) {
-                        asyncExecCallback.failed(ex);
-                    }
-                    break;
+            case HttpRouteDirector.UNREACHABLE:
+                asyncExecCallback.failed(new HttpException("Unable to establish route: " +
+                        "planned = " + route + "; current = " + fact));
+                break;
 
-                default:
-                    throw new IllegalStateException("Unknown step indicator "  + step + " from RouteDirector.");
-            }
-        } while (step > HttpRouteDirector.COMPLETE);
+            case HttpRouteDirector.COMPLETE:
+                if (LOG.isDebugEnabled()) {
+                    LOG.debug("{} route fully established", exchangeId);
+                }
+                try {
+                    chain.proceed(request, entityProducer, scope, asyncExecCallback);
+                } catch (final HttpException | IOException ex) {
+                    asyncExecCallback.failed(ex);
+                }
+                break;
+
+            default:
+                throw new IllegalStateException("Unknown step indicator "  + step + " from RouteDirector.");
+        }
     }
 
     private void createTunnel(
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalH2AsyncExecRuntime.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalH2AsyncExecRuntime.java
index 724d1f6..3bd2007 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalH2AsyncExecRuntime.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalH2AsyncExecRuntime.java
@@ -220,6 +220,11 @@
     }
 
     @Override
+    public void upgradeTls(final HttpClientContext context, final FutureCallback<AsyncExecRuntime> callback) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
     public Cancellable execute(
             final String id,
             final AsyncClientExchangeHandler exchangeHandler, final HttpClientContext context) {
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 6286e75..bbc34fa 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
@@ -38,6 +38,7 @@
 import org.apache.hc.client5.http.nio.AsyncClientConnectionManager;
 import org.apache.hc.client5.http.nio.AsyncConnectionEndpoint;
 import org.apache.hc.client5.http.protocol.HttpClientContext;
+import org.apache.hc.core5.concurrent.CallbackContribution;
 import org.apache.hc.core5.concurrent.Cancellable;
 import org.apache.hc.core5.concurrent.FutureCallback;
 import org.apache.hc.core5.http.nio.AsyncClientExchangeHandler;
@@ -214,24 +215,16 @@
                 connectTimeout,
                 versionPolicy,
                 context,
-                new FutureCallback<AsyncConnectionEndpoint>() {
+                new CallbackContribution<AsyncConnectionEndpoint>(callback) {
 
                     @Override
                     public void completed(final AsyncConnectionEndpoint endpoint) {
                         if (log.isDebugEnabled()) {
                             log.debug("{} endpoint connected", ConnPoolSupport.getId(endpoint));
                         }
-                        callback.completed(InternalHttpAsyncExecRuntime.this);
-                    }
-
-                    @Override
-                    public void failed(final Exception ex) {
-                        callback.failed(ex);
-                    }
-
-                    @Override
-                    public void cancelled() {
-                        callback.cancelled();
+                        if (callback != null) {
+                            callback.completed(InternalHttpAsyncExecRuntime.this);
+                        }
                     }
 
         }));
@@ -240,11 +233,25 @@
 
     @Override
     public void upgradeTls(final HttpClientContext context) {
+        upgradeTls(context, null);
+    }
+
+    @Override
+    public void upgradeTls(final HttpClientContext context, final FutureCallback<AsyncExecRuntime> callback) {
         final AsyncConnectionEndpoint endpoint = ensureValid();
         if (log.isDebugEnabled()) {
             log.debug("{} upgrading endpoint", ConnPoolSupport.getId(endpoint));
         }
-        manager.upgrade(endpoint, versionPolicy, context);
+        manager.upgrade(endpoint, versionPolicy, context, new CallbackContribution<AsyncConnectionEndpoint>(callback) {
+
+            @Override
+            public void completed(final AsyncConnectionEndpoint endpoint) {
+                if (callback != null) {
+                    callback.completed(InternalHttpAsyncExecRuntime.this);
+                }
+            }
+
+        });
     }
 
     @Override
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 65c9186..a781e9b 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
@@ -38,13 +38,17 @@
 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.concurrent.CallbackContribution;
 import org.apache.hc.core5.concurrent.ComplexFuture;
 import org.apache.hc.core5.concurrent.FutureCallback;
+import org.apache.hc.core5.concurrent.FutureContribution;
 import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.URIScheme;
 import org.apache.hc.core5.http.config.Lookup;
 import org.apache.hc.core5.http.nio.ssl.TlsStrategy;
 import org.apache.hc.core5.reactor.ConnectionInitiator;
 import org.apache.hc.core5.reactor.IOSession;
+import org.apache.hc.core5.reactor.ssl.TransportSecurityLayer;
 import org.apache.hc.core5.util.Args;
 import org.apache.hc.core5.util.Timeout;
 
@@ -89,19 +93,27 @@
                     @Override
                     public void completed(final IOSession session) {
                         final DefaultManagedAsyncClientConnection connection = new DefaultManagedAsyncClientConnection(session);
-                        if (tlsStrategy != null) {
+                        if (tlsStrategy != null && URIScheme.HTTPS.same(host.getSchemeName())) {
                             try {
                                 tlsStrategy.upgrade(
                                         connection,
                                         host,
                                         attachment,
-                                        connectTimeout, null);
+                                        null,
+                                        new FutureContribution<TransportSecurityLayer>(future) {
+
+                                            @Override
+                                            public void completed(final TransportSecurityLayer transportSecurityLayer) {
+                                                future.completed(connection);
+                                            }
+
+                                        });
                             } catch (final Exception ex) {
                                 future.failed(ex);
-                                return;
                             }
+                        } else {
+                            future.completed(connection);
                         }
-                        future.completed(connection);
                     }
 
                     @Override
@@ -120,7 +132,19 @@
     }
 
     @Override
-    public void upgrade(final ManagedAsyncClientConnection connection, final HttpHost host, final Object attachment) {
+    public void upgrade(
+            final ManagedAsyncClientConnection connection,
+            final HttpHost host,
+            final Object attachment) {
+        upgrade(connection, host, attachment, null);
+    }
+
+    @Override
+    public void upgrade(
+            final ManagedAsyncClientConnection connection,
+            final HttpHost host,
+            final Object attachment,
+            final FutureCallback<ManagedAsyncClientConnection> callback) {
         final TlsStrategy tlsStrategy = tlsStrategyLookup != null ? tlsStrategyLookup.lookup(host.getSchemeName()) : null;
         if (tlsStrategy != null) {
             tlsStrategy.upgrade(
@@ -128,8 +152,18 @@
                     host,
                     attachment,
                     null,
-                    null);
+                    new CallbackContribution<TransportSecurityLayer>(callback) {
+
+                        @Override
+                        public void completed(final TransportSecurityLayer transportSecurityLayer) {
+                            if (callback != null) {
+                                callback.completed(connection);
+                            }
+                        }
+
+                    });
         }
 
     }
+
 }
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/nio/DefaultManagedAsyncClientConnection.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/nio/DefaultManagedAsyncClientConnection.java
index 5aa8439..a2bfc0b 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/nio/DefaultManagedAsyncClientConnection.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/nio/DefaultManagedAsyncClientConnection.java
@@ -35,6 +35,7 @@
 import javax.net.ssl.SSLSession;
 
 import org.apache.hc.client5.http.nio.ManagedAsyncClientConnection;
+import org.apache.hc.core5.concurrent.FutureCallback;
 import org.apache.hc.core5.http.EndpointDetails;
 import org.apache.hc.core5.http.HttpConnection;
 import org.apache.hc.core5.http.HttpVersion;
@@ -45,6 +46,7 @@
 import org.apache.hc.core5.reactor.Command;
 import org.apache.hc.core5.reactor.IOEventHandler;
 import org.apache.hc.core5.reactor.IOSession;
+import org.apache.hc.core5.reactor.ProtocolIOSession;
 import org.apache.hc.core5.reactor.ssl.SSLBufferMode;
 import org.apache.hc.core5.reactor.ssl.SSLSessionInitializer;
 import org.apache.hc.core5.reactor.ssl.SSLSessionVerifier;
@@ -144,19 +146,31 @@
             final SSLBufferMode sslBufferMode,
             final SSLSessionInitializer initializer,
             final SSLSessionVerifier verifier,
-            final Timeout handshakeTimeout) throws UnsupportedOperationException {
+            final Timeout handshakeTimeout,
+            final FutureCallback<TransportSecurityLayer> callback) throws UnsupportedOperationException {
         if (LOG.isDebugEnabled()) {
             LOG.debug("{} start TLS", getId());
         }
         if (ioSession instanceof TransportSecurityLayer) {
             ((TransportSecurityLayer) ioSession).startTls(sslContext, endpoint, sslBufferMode, initializer, verifier,
-                handshakeTimeout);
+                handshakeTimeout, callback);
         } else {
             throw new UnsupportedOperationException("TLS upgrade not supported");
         }
     }
 
     @Override
+    public void startTls(
+            final SSLContext sslContext,
+            final NamedEndpoint endpoint,
+            final SSLBufferMode sslBufferMode,
+            final SSLSessionInitializer initializer,
+            final SSLSessionVerifier verifier,
+            final Timeout handshakeTimeout) throws UnsupportedOperationException {
+        startTls(sslContext, endpoint, sslBufferMode, initializer, verifier, handshakeTimeout, null);
+    }
+
+    @Override
     public TlsDetails getTlsDetails() {
         return ioSession instanceof TransportSecurityLayer ? ((TransportSecurityLayer) ioSession).getTlsDetails() : null;
     }
@@ -185,4 +199,14 @@
         ioSession.setSocketTimeout(socketTimeout);
     }
 
+    @Override
+    public void switchProtocol(final String protocolId,
+                               final FutureCallback<ProtocolIOSession> callback) throws UnsupportedOperationException {
+        if (ioSession instanceof ProtocolIOSession) {
+            ((ProtocolIOSession) ioSession).switchProtocol(protocolId, callback);
+        } else {
+            throw new UnsupportedOperationException("Protocol switch not supported");
+        }
+    }
+
 }
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 3915e3a..3bc64ab 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
@@ -52,6 +52,7 @@
 import org.apache.hc.core5.annotation.Internal;
 import org.apache.hc.core5.annotation.ThreadingBehavior;
 import org.apache.hc.core5.concurrent.BasicFuture;
+import org.apache.hc.core5.concurrent.CallbackContribution;
 import org.apache.hc.core5.concurrent.ComplexFuture;
 import org.apache.hc.core5.concurrent.FutureCallback;
 import org.apache.hc.core5.function.Resolver;
@@ -68,6 +69,7 @@
 import org.apache.hc.core5.http.protocol.HttpContext;
 import org.apache.hc.core5.http2.nio.command.PingCommand;
 import org.apache.hc.core5.http2.nio.support.BasicPingHandler;
+import org.apache.hc.core5.http2.ssl.ApplicationProtocol;
 import org.apache.hc.core5.io.CloseMode;
 import org.apache.hc.core5.pool.ConnPoolControl;
 import org.apache.hc.core5.pool.LaxConnPool;
@@ -79,6 +81,8 @@
 import org.apache.hc.core5.pool.StrictConnPool;
 import org.apache.hc.core5.reactor.Command;
 import org.apache.hc.core5.reactor.ConnectionInitiator;
+import org.apache.hc.core5.reactor.ProtocolIOSession;
+import org.apache.hc.core5.reactor.ssl.TlsDetails;
 import org.apache.hc.core5.util.Args;
 import org.apache.hc.core5.util.Asserts;
 import org.apache.hc.core5.util.Identifiable;
@@ -439,16 +443,48 @@
     public void upgrade(
             final AsyncConnectionEndpoint endpoint,
             final Object attachment,
-            final HttpContext context) {
+            final HttpContext context,
+            final FutureCallback<AsyncConnectionEndpoint> callback) {
         Args.notNull(endpoint, "Managed endpoint");
         final InternalConnectionEndpoint internalEndpoint = cast(endpoint);
         final PoolEntry<HttpRoute, ManagedAsyncClientConnection> poolEntry = internalEndpoint.getValidatedPoolEntry();
         final HttpRoute route = poolEntry.getRoute();
-        final ManagedAsyncClientConnection connection = poolEntry.getConnection();
-        connectionOperator.upgrade(poolEntry.getConnection(), route.getTargetHost(), attachment);
-        if (LOG.isDebugEnabled()) {
-            LOG.debug("{} upgraded {}", ConnPoolSupport.getId(internalEndpoint), ConnPoolSupport.getId(connection));
-        }
+        connectionOperator.upgrade(
+                poolEntry.getConnection(),
+                route.getTargetHost(),
+                attachment,
+                new CallbackContribution<ManagedAsyncClientConnection>(callback) {
+
+                    @Override
+                    public void completed(final ManagedAsyncClientConnection connection) {
+                        if (LOG.isDebugEnabled()) {
+                            LOG.debug("{} upgraded {}", ConnPoolSupport.getId(internalEndpoint), ConnPoolSupport.getId(connection));
+                        }
+                        final TlsDetails tlsDetails = connection.getTlsDetails();
+                        if (tlsDetails != null && ApplicationProtocol.HTTP_2.id.equals(tlsDetails.getApplicationProtocol())) {
+                            connection.switchProtocol(ApplicationProtocol.HTTP_2.id, new CallbackContribution<ProtocolIOSession>(callback) {
+
+                                @Override
+                                public void completed(final ProtocolIOSession protocolIOSession) {
+                                    if (callback != null) {
+                                        callback.completed(endpoint);
+                                    }
+                                }
+
+                            });
+                        } else {
+                            if (callback != null) {
+                                callback.completed(endpoint);
+                            }
+                        }
+                    }
+
+                });
+    }
+
+    @Override
+    public void upgrade(final AsyncConnectionEndpoint endpoint, final Object attachment, final HttpContext context) {
+        upgrade(endpoint, attachment, context, null);
     }
 
     @Override
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/nio/AsyncClientConnectionManager.java b/httpclient5/src/main/java/org/apache/hc/client5/http/nio/AsyncClientConnectionManager.java
index 984fdd8..7195da7 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/nio/AsyncClientConnectionManager.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/nio/AsyncClientConnectionManager.java
@@ -131,4 +131,25 @@
             Object attachment,
             HttpContext context);
 
+    /**
+     * Upgrades transport security of the given endpoint by using the TLS security protocol.
+     *
+     * @param endpoint      the managed endpoint.
+     * @param attachment the attachment the upgrade attachment object.
+     * @param context the actual HTTP context.
+     * @param callback result callback.
+     *
+     * @since 5.2
+     */
+    default void upgrade(
+            AsyncConnectionEndpoint endpoint,
+            Object attachment,
+            HttpContext context,
+            FutureCallback<AsyncConnectionEndpoint> callback) {
+        upgrade(endpoint, attachment, context);
+        if (callback != null) {
+            callback.completed(endpoint);
+        }
+    }
+
 }
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 a4a8bbf..af12604 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
@@ -79,4 +79,26 @@
      */
     void upgrade(ManagedAsyncClientConnection conn, HttpHost host, Object attachment);
 
+    /**
+     * 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 the attachment, which can be any object representing custom parameter
+     *                    of the operation.
+     *
+     * @since 5.2
+     */
+    default void upgrade(
+            ManagedAsyncClientConnection conn,
+            HttpHost host,
+            Object attachment,
+            FutureCallback<ManagedAsyncClientConnection> callback) {
+        upgrade(conn, host, attachment);
+        if (callback != null) {
+            callback.completed(conn);
+        }
+    }
+
 }
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/nio/ManagedAsyncClientConnection.java b/httpclient5/src/main/java/org/apache/hc/client5/http/nio/ManagedAsyncClientConnection.java
index dad4f4a..1366001 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/nio/ManagedAsyncClientConnection.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/nio/ManagedAsyncClientConnection.java
@@ -28,8 +28,10 @@
 package org.apache.hc.client5.http.nio;
 
 import org.apache.hc.core5.annotation.Internal;
+import org.apache.hc.core5.concurrent.FutureCallback;
 import org.apache.hc.core5.http.HttpConnection;
 import org.apache.hc.core5.reactor.Command;
+import org.apache.hc.core5.reactor.ProtocolIOSession;
 import org.apache.hc.core5.reactor.ssl.TransportSecurityLayer;
 
 /**
@@ -59,4 +61,17 @@
      */
     void activate();
 
+    /**
+     * Switches this I/O session to the application protocol with the given ID.
+     * @param protocolId the application protocol ID
+     * @param callback the result callback
+     * @throws UnsupportedOperationException if application protocol switch
+     * is not supported.
+     *
+     * @since 5.2
+     */
+    default void switchProtocol(String protocolId, FutureCallback<ProtocolIOSession> callback) throws UnsupportedOperationException {
+        throw new UnsupportedOperationException("Protocol switch not supported");
+    }
+
 }