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 2017/11/13 21:45:56 UTC

[1/6] httpcomponents-client git commit: Treat System#getProperty as a privileged action

Repository: httpcomponents-client
Updated Branches:
  refs/heads/master 3db122824 -> 6228a7361


Treat System#getProperty as a privileged action


Project: http://git-wip-us.apache.org/repos/asf/httpcomponents-client/repo
Commit: http://git-wip-us.apache.org/repos/asf/httpcomponents-client/commit/703b7968
Tree: http://git-wip-us.apache.org/repos/asf/httpcomponents-client/tree/703b7968
Diff: http://git-wip-us.apache.org/repos/asf/httpcomponents-client/diff/703b7968

Branch: refs/heads/master
Commit: 703b7968c511905ca631a7d8e01a0798a18217e6
Parents: 3db1228
Author: Oleg Kalnichevski <ol...@apache.org>
Authored: Mon Nov 13 10:46:09 2017 +0100
Committer: Oleg Kalnichevski <ol...@apache.org>
Committed: Mon Nov 13 10:46:09 2017 +0100

----------------------------------------------------------------------
 .../org/apache/hc/client5/http/ssl/H2TlsStrategy.java | 14 ++++++++++++--
 1 file changed, 12 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/703b7968/httpclient5/src/main/java/org/apache/hc/client5/http/ssl/H2TlsStrategy.java
----------------------------------------------------------------------
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/ssl/H2TlsStrategy.java b/httpclient5/src/main/java/org/apache/hc/client5/http/ssl/H2TlsStrategy.java
index 79150c4..df82ba4 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/ssl/H2TlsStrategy.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/ssl/H2TlsStrategy.java
@@ -28,6 +28,8 @@
 package org.apache.hc.client5.http.ssl;
 
 import java.net.SocketAddress;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
 import java.security.cert.Certificate;
 import java.security.cert.X509Certificate;
 import java.util.ArrayList;
@@ -73,6 +75,14 @@ public class H2TlsStrategy implements TlsStrategy {
         return s.split(" *, *");
     }
 
+    private static String getProperty(final String key) {
+        return AccessController.doPrivileged(new PrivilegedAction<String>() {
+            @Override
+            public String run() {
+                return System.getProperty(key);
+            }
+        });
+    }
     public static HostnameVerifier getDefaultHostnameVerifier() {
         return new DefaultHostnameVerifier(PublicSuffixMatcherLoader.getDefault());
     }
@@ -86,8 +96,8 @@ public class H2TlsStrategy implements TlsStrategy {
     public static TlsStrategy getSystemDefault() {
         return new H2TlsStrategy(
                 SSLContexts.createSystemDefault(),
-                split(System.getProperty("https.protocols")),
-                split(System.getProperty("https.cipherSuites")),
+                split(getProperty("https.protocols")),
+                split(getProperty("https.cipherSuites")),
                 SSLBufferManagement.STATIC,
                 getDefaultHostnameVerifier());
     }


[2/6] httpcomponents-client git commit: * HTTP/2 multiplexing HttpAsyncClient implementation * Restructured integration tests to reduce test duplication

Posted by ol...@apache.org.
http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/6228a736/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalAbstractHttpAsyncClient.java
----------------------------------------------------------------------
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalAbstractHttpAsyncClient.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalAbstractHttpAsyncClient.java
new file mode 100644
index 0000000..aab6f78
--- /dev/null
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalAbstractHttpAsyncClient.java
@@ -0,0 +1,308 @@
+/*
+ * ====================================================================
+ * 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.async;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Future;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.hc.client5.http.HttpRoute;
+import org.apache.hc.client5.http.async.AsyncExecCallback;
+import org.apache.hc.client5.http.async.AsyncExecChain;
+import org.apache.hc.client5.http.async.AsyncExecRuntime;
+import org.apache.hc.client5.http.async.methods.SimpleRequestProducer;
+import org.apache.hc.client5.http.auth.AuthSchemeProvider;
+import org.apache.hc.client5.http.auth.CredentialsProvider;
+import org.apache.hc.client5.http.config.Configurable;
+import org.apache.hc.client5.http.config.RequestConfig;
+import org.apache.hc.client5.http.cookie.CookieSpecProvider;
+import org.apache.hc.client5.http.cookie.CookieStore;
+import org.apache.hc.client5.http.impl.ExecSupport;
+import org.apache.hc.client5.http.impl.RequestCopier;
+import org.apache.hc.client5.http.protocol.HttpClientContext;
+import org.apache.hc.core5.concurrent.BasicFuture;
+import org.apache.hc.core5.concurrent.FutureCallback;
+import org.apache.hc.core5.http.EntityDetails;
+import org.apache.hc.core5.http.HttpException;
+import org.apache.hc.core5.http.HttpRequest;
+import org.apache.hc.core5.http.HttpResponse;
+import org.apache.hc.core5.http.HttpStatus;
+import org.apache.hc.core5.http.config.Lookup;
+import org.apache.hc.core5.http.nio.AsyncDataConsumer;
+import org.apache.hc.core5.http.nio.AsyncEntityProducer;
+import org.apache.hc.core5.http.nio.AsyncRequestProducer;
+import org.apache.hc.core5.http.nio.AsyncResponseConsumer;
+import org.apache.hc.core5.http.nio.DataStreamChannel;
+import org.apache.hc.core5.http.nio.RequestChannel;
+import org.apache.hc.core5.http.protocol.HttpContext;
+import org.apache.hc.core5.reactor.DefaultConnectingIOReactor;
+
+abstract class InternalAbstractHttpAsyncClient extends AbstractHttpAsyncClientBase {
+
+    private final AsyncExecChainElement execChain;
+    private final Lookup<CookieSpecProvider> cookieSpecRegistry;
+    private final Lookup<AuthSchemeProvider> authSchemeRegistry;
+    private final CookieStore cookieStore;
+    private final CredentialsProvider credentialsProvider;
+    private final RequestConfig defaultConfig;
+    private final List<Closeable> closeables;
+
+    InternalAbstractHttpAsyncClient(
+            final DefaultConnectingIOReactor ioReactor,
+            final AsyncPushConsumerRegistry pushConsumerRegistry,
+            final ThreadFactory threadFactory,
+            final AsyncExecChainElement execChain,
+            final Lookup<CookieSpecProvider> cookieSpecRegistry,
+            final Lookup<AuthSchemeProvider> authSchemeRegistry,
+            final CookieStore cookieStore,
+            final CredentialsProvider credentialsProvider,
+            final RequestConfig defaultConfig,
+            final List<Closeable> closeables) {
+        super(ioReactor, pushConsumerRegistry, threadFactory);
+        this.execChain = execChain;
+        this.cookieSpecRegistry = cookieSpecRegistry;
+        this.authSchemeRegistry = authSchemeRegistry;
+        this.cookieStore = cookieStore;
+        this.credentialsProvider = credentialsProvider;
+        this.defaultConfig = defaultConfig;
+        this.closeables = closeables;
+    }
+
+    @Override
+    public void close() {
+        super.close();
+        if (closeables != null) {
+            for (final Closeable closeable: closeables) {
+                try {
+                    closeable.close();
+                } catch (final IOException ex) {
+                    log.error(ex.getMessage(), ex);
+                }
+            }
+        }
+    }
+
+    private void setupContext(final HttpClientContext context) {
+        if (context.getAttribute(HttpClientContext.AUTHSCHEME_REGISTRY) == null) {
+            context.setAttribute(HttpClientContext.AUTHSCHEME_REGISTRY, authSchemeRegistry);
+        }
+        if (context.getAttribute(HttpClientContext.COOKIESPEC_REGISTRY) == null) {
+            context.setAttribute(HttpClientContext.COOKIESPEC_REGISTRY, cookieSpecRegistry);
+        }
+        if (context.getAttribute(HttpClientContext.COOKIE_STORE) == null) {
+            context.setAttribute(HttpClientContext.COOKIE_STORE, cookieStore);
+        }
+        if (context.getAttribute(HttpClientContext.CREDS_PROVIDER) == null) {
+            context.setAttribute(HttpClientContext.CREDS_PROVIDER, credentialsProvider);
+        }
+        if (context.getAttribute(HttpClientContext.REQUEST_CONFIG) == null) {
+            context.setAttribute(HttpClientContext.REQUEST_CONFIG, defaultConfig);
+        }
+    }
+
+    abstract AsyncExecRuntime crerateAsyncExecRuntime();
+
+    abstract HttpRoute determineRoute(HttpRequest request, HttpClientContext clientContext) throws HttpException;
+
+    @Override
+    public <T> Future<T> execute(
+            final AsyncRequestProducer requestProducer,
+            final AsyncResponseConsumer<T> responseConsumer,
+            final HttpContext context,
+            final FutureCallback<T> callback) {
+        ensureRunning();
+        final BasicFuture<T> future = new BasicFuture<>(callback);
+        try {
+            final HttpClientContext clientContext = HttpClientContext.adapt(context);
+            requestProducer.sendRequest(new RequestChannel() {
+
+                @Override
+                public void sendRequest(
+                        final HttpRequest request,
+                        final EntityDetails entityDetails) throws HttpException, IOException {
+
+                    RequestConfig requestConfig = null;
+                    if (request instanceof Configurable) {
+                        requestConfig = ((Configurable) request).getConfig();
+                    }
+                    if (requestConfig != null) {
+                        clientContext.setRequestConfig(requestConfig);
+                    }
+                    final HttpRoute route = determineRoute(request, clientContext);
+                    final String exchangeId = String.format("ex-%08X", ExecSupport.getNextExecNumber());
+                    final AsyncExecRuntime execRuntime = crerateAsyncExecRuntime();
+                    if (log.isDebugEnabled()) {
+                        log.debug(exchangeId + ": preparing request execution");
+                    }
+
+                    setupContext(clientContext);
+
+                    final AsyncExecChain.Scope scope = new AsyncExecChain.Scope(exchangeId, route, request, clientContext, execRuntime);
+                    final AtomicReference<T> resultRef = new AtomicReference<>(null);
+                    final AtomicBoolean outputTerminated = new AtomicBoolean(false);
+                    execChain.execute(
+                            RequestCopier.INSTANCE.copy(request),
+                            entityDetails != null ? new AsyncEntityProducer() {
+
+                                @Override
+                                public void releaseResources() {
+                                    requestProducer.releaseResources();
+                                }
+
+                                @Override
+                                public void failed(final Exception cause) {
+                                    requestProducer.failed(cause);
+                                }
+
+                                @Override
+                                public boolean isRepeatable() {
+                                    //TODO: use AsyncRequestProducer#isRepeatable once available
+                                    return requestProducer instanceof SimpleRequestProducer;
+                                }
+
+                                @Override
+                                public long getContentLength() {
+                                    return entityDetails.getContentLength();
+                                }
+
+                                @Override
+                                public String getContentType() {
+                                    return entityDetails.getContentType();
+                                }
+
+                                @Override
+                                public String getContentEncoding() {
+                                    return entityDetails.getContentEncoding();
+                                }
+
+                                @Override
+                                public boolean isChunked() {
+                                    return entityDetails.isChunked();
+                                }
+
+                                @Override
+                                public Set<String> getTrailerNames() {
+                                    return entityDetails.getTrailerNames();
+                                }
+
+                                @Override
+                                public int available() {
+                                    return requestProducer.available();
+                                }
+
+                                @Override
+                                public void produce(final DataStreamChannel channel) throws IOException {
+                                    if (outputTerminated.get()) {
+                                        channel.endStream();
+                                        return;
+                                    }
+                                    requestProducer.produce(channel);
+                                }
+
+                            } : null,
+                            scope,
+                            new AsyncExecCallback() {
+
+                                @Override
+                                public AsyncDataConsumer handleResponse(
+                                        final HttpResponse response,
+                                        final EntityDetails entityDetails) throws HttpException, IOException {
+                                    if (response.getCode() >= HttpStatus.SC_CLIENT_ERROR) {
+                                        outputTerminated.set(true);
+                                        requestProducer.releaseResources();
+                                    }
+                                    responseConsumer.consumeResponse(response, entityDetails,
+                                            //TODO: eliminate this callback after upgrade to HttpCore 5.0b2
+                                            new FutureCallback<T>() {
+
+                                                @Override
+                                                public void completed(final T result) {
+                                                    resultRef.set(result);
+                                                }
+
+                                                @Override
+                                                public void failed(final Exception ex) {
+                                                    future.failed(ex);
+                                                }
+
+                                                @Override
+                                                public void cancelled() {
+                                                    future.cancel();
+                                                }
+
+                                            });
+                                    return responseConsumer;
+                                }
+
+                                @Override
+                                public void completed() {
+                                    if (log.isDebugEnabled()) {
+                                        log.debug(exchangeId + ": message exchange successfully completed");
+                                    }
+                                    try {
+                                        execRuntime.releaseConnection();
+                                        future.completed(resultRef.getAndSet(null));
+                                    } finally {
+                                        responseConsumer.releaseResources();
+                                        requestProducer.releaseResources();
+                                    }
+                                }
+
+                                @Override
+                                public void failed(final Exception cause) {
+                                    if (log.isDebugEnabled()) {
+                                        log.debug(exchangeId + ": request failed: " + cause.getMessage());
+                                    }
+                                    try {
+                                        execRuntime.discardConnection();
+                                        responseConsumer.failed(cause);
+                                    } finally {
+                                        try {
+                                            future.failed(cause);
+                                        } finally {
+                                            responseConsumer.releaseResources();
+                                            requestProducer.releaseResources();
+                                        }
+                                    }
+                                }
+
+                            });
+                }
+
+            });
+        } catch (final HttpException | IOException ex) {
+            future.failed(ex);
+        }
+        return future;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/6228a736/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalHttp2AsyncClient.java
----------------------------------------------------------------------
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalHttp2AsyncClient.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalHttp2AsyncClient.java
new file mode 100644
index 0000000..f68c69a
--- /dev/null
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalHttp2AsyncClient.java
@@ -0,0 +1,88 @@
+/*
+ * ====================================================================
+ * 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.async;
+
+import java.io.Closeable;
+import java.util.List;
+import java.util.concurrent.ThreadFactory;
+
+import org.apache.hc.client5.http.HttpRoute;
+import org.apache.hc.client5.http.async.AsyncExecRuntime;
+import org.apache.hc.client5.http.auth.AuthSchemeProvider;
+import org.apache.hc.client5.http.auth.CredentialsProvider;
+import org.apache.hc.client5.http.config.RequestConfig;
+import org.apache.hc.client5.http.cookie.CookieSpecProvider;
+import org.apache.hc.client5.http.cookie.CookieStore;
+import org.apache.hc.client5.http.protocol.HttpClientContext;
+import org.apache.hc.client5.http.routing.HttpRoutePlanner;
+import org.apache.hc.core5.http.HttpException;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.HttpRequest;
+import org.apache.hc.core5.http.config.Lookup;
+import org.apache.hc.core5.http2.nio.pool.H2ConnPool;
+import org.apache.hc.core5.reactor.DefaultConnectingIOReactor;
+
+class InternalHttp2AsyncClient extends InternalAbstractHttpAsyncClient {
+
+    private final HttpRoutePlanner routePlanner;
+    private final H2ConnPool connPool;
+
+    InternalHttp2AsyncClient(
+            final DefaultConnectingIOReactor ioReactor,
+            final AsyncExecChainElement execChain,
+            final AsyncPushConsumerRegistry pushConsumerRegistry,
+            final ThreadFactory threadFactory,
+            final H2ConnPool connPool,
+            final HttpRoutePlanner routePlanner,
+            final Lookup<CookieSpecProvider> cookieSpecRegistry,
+            final Lookup<AuthSchemeProvider> authSchemeRegistry,
+            final CookieStore cookieStore,
+            final CredentialsProvider credentialsProvider,
+            final RequestConfig defaultConfig,
+            final List<Closeable> closeables) {
+        super(ioReactor, pushConsumerRegistry, threadFactory, execChain,
+                cookieSpecRegistry, authSchemeRegistry, cookieStore, credentialsProvider, defaultConfig, closeables);
+        this.connPool = connPool;
+        this.routePlanner = routePlanner;
+    }
+
+    @Override
+    AsyncExecRuntime crerateAsyncExecRuntime() {
+        return new InternalHttp2AsyncExecRuntime(log, connPool);
+    }
+
+    @Override
+    HttpRoute determineRoute(final HttpRequest request, final HttpClientContext clientContext) throws HttpException {
+        final HttpHost target = routePlanner.determineTargetHost(request, clientContext);
+        final HttpRoute route = routePlanner.determineRoute(target, clientContext);
+        if (route.isTunnelled()) {
+            throw new HttpException("HTTP/2 tunneling not supported");
+        }
+        return route;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/6228a736/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalHttp2AsyncExecRuntime.java
----------------------------------------------------------------------
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalHttp2AsyncExecRuntime.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalHttp2AsyncExecRuntime.java
new file mode 100644
index 0000000..624dafe
--- /dev/null
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalHttp2AsyncExecRuntime.java
@@ -0,0 +1,245 @@
+/*
+ * ====================================================================
+ * 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.async;
+
+import java.io.InterruptedIOException;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.hc.client5.http.HttpRoute;
+import org.apache.hc.client5.http.async.AsyncExecRuntime;
+import org.apache.hc.client5.http.config.RequestConfig;
+import org.apache.hc.client5.http.impl.ConnPoolSupport;
+import org.apache.hc.client5.http.protocol.HttpClientContext;
+import org.apache.hc.core5.concurrent.FutureCallback;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.nio.AsyncClientExchangeHandler;
+import org.apache.hc.core5.http.nio.command.ExecutionCommand;
+import org.apache.hc.core5.http2.nio.pool.H2ConnPool;
+import org.apache.hc.core5.io.ShutdownType;
+import org.apache.hc.core5.reactor.IOSession;
+import org.apache.hc.core5.util.TimeValue;
+import org.apache.logging.log4j.Logger;
+
+class InternalHttp2AsyncExecRuntime implements AsyncExecRuntime {
+
+    private final Logger log;
+    private final H2ConnPool connPool;
+    private final AtomicReference<Endpoint> sessionRef;
+    private volatile boolean reusable;
+
+    InternalHttp2AsyncExecRuntime(final Logger log, final H2ConnPool connPool) {
+        super();
+        this.log = log;
+        this.connPool = connPool;
+        this.sessionRef = new AtomicReference<>(null);
+    }
+
+    @Override
+    public boolean isConnectionAcquired() {
+        return sessionRef.get() != null;
+    }
+
+    @Override
+    public void acquireConnection(
+            final HttpRoute route,
+            final Object object,
+            final HttpClientContext context,
+            final FutureCallback<AsyncExecRuntime> callback) {
+        if (sessionRef.get() == null) {
+            final HttpHost target = route.getTargetHost();
+            final RequestConfig requestConfig = context.getRequestConfig();
+            connPool.getSession(target, requestConfig.getConnectTimeout(), new FutureCallback<IOSession>() {
+
+                @Override
+                public void completed(final IOSession ioSession) {
+                    sessionRef.set(new Endpoint(target, ioSession));
+                    reusable = true;
+                    callback.completed(InternalHttp2AsyncExecRuntime.this);
+                }
+
+                @Override
+                public void failed(final Exception ex) {
+                    callback.failed(ex);
+                }
+
+                @Override
+                public void cancelled() {
+                    callback.cancelled();
+                }
+
+            });
+        } else {
+            callback.completed(this);
+        }
+    }
+
+    @Override
+    public void releaseConnection() {
+        final Endpoint endpoint = sessionRef.getAndSet(null);
+        if (endpoint != null && !reusable) {
+            endpoint.session.shutdown(ShutdownType.GRACEFUL);
+        }
+    }
+
+    @Override
+    public void discardConnection() {
+        final Endpoint endpoint = sessionRef.getAndSet(null);
+        if (endpoint != null) {
+            endpoint.session.shutdown(ShutdownType.GRACEFUL);
+        }
+    }
+
+    @Override
+    public boolean validateConnection() {
+        if (reusable) {
+            final Endpoint endpoint = sessionRef.get();
+            return endpoint != null && !endpoint.session.isClosed();
+        } else {
+            final Endpoint endpoint = sessionRef.getAndSet(null);
+            if (endpoint != null) {
+                endpoint.session.shutdown(ShutdownType.GRACEFUL);
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public boolean isConnected() {
+        final Endpoint endpoint = sessionRef.get();
+        return endpoint != null && !endpoint.session.isClosed();
+    }
+
+
+    Endpoint ensureValid() {
+        final Endpoint endpoint = sessionRef.get();
+        if (endpoint == null) {
+            throw new IllegalStateException("I/O session not acquired / already released");
+        }
+        return endpoint;
+    }
+
+    @Override
+    public void connect(
+            final HttpClientContext context,
+            final FutureCallback<AsyncExecRuntime> callback) {
+        final Endpoint endpoint = ensureValid();
+        if (!endpoint.session.isClosed()) {
+            callback.completed(this);
+        } else {
+            final HttpHost target = endpoint.target;
+            final RequestConfig requestConfig = context.getRequestConfig();
+            connPool.getSession(target, requestConfig.getConnectTimeout(), new FutureCallback<IOSession>() {
+
+                @Override
+                public void completed(final IOSession ioSession) {
+                    sessionRef.set(new Endpoint(target, ioSession));
+                    reusable = true;
+                    callback.completed(InternalHttp2AsyncExecRuntime.this);
+                }
+
+                @Override
+                public void failed(final Exception ex) {
+                    callback.failed(ex);
+                }
+
+                @Override
+                public void cancelled() {
+                    callback.cancelled();
+                }
+
+            });
+        }
+
+    }
+
+    @Override
+    public void upgradeTls(final HttpClientContext context) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void execute(final AsyncClientExchangeHandler exchangeHandler, final HttpClientContext context) {
+        final Endpoint endpoint = ensureValid();
+        final IOSession session = endpoint.session;
+        if (!session.isClosed()) {
+            if (log.isDebugEnabled()) {
+                log.debug(ConnPoolSupport.getId(endpoint) + ": executing " + ConnPoolSupport.getId(exchangeHandler));
+            }
+            session.addLast(new ExecutionCommand(exchangeHandler, context));
+        } else {
+            final HttpHost target = endpoint.target;
+            final RequestConfig requestConfig = context.getRequestConfig();
+            connPool.getSession(target, requestConfig.getConnectTimeout(), new FutureCallback<IOSession>() {
+
+                @Override
+                public void completed(final IOSession ioSession) {
+                    sessionRef.set(new Endpoint(target, ioSession));
+                    reusable = true;
+                    if (log.isDebugEnabled()) {
+                        log.debug(ConnPoolSupport.getId(endpoint) + ": executing " + ConnPoolSupport.getId(exchangeHandler));
+                    }
+                    session.addLast(new ExecutionCommand(exchangeHandler, context));
+                }
+
+                @Override
+                public void failed(final Exception ex) {
+                    exchangeHandler.failed(ex);
+                }
+
+                @Override
+                public void cancelled() {
+                    exchangeHandler.failed(new InterruptedIOException());
+                }
+
+            });
+        }
+
+    }
+
+    @Override
+    public void markConnectionReusable(final Object newState, final TimeValue newValidDuration) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void markConnectionNonReusable() {
+        reusable = false;
+    }
+
+    static class Endpoint {
+
+        final HttpHost target;
+        final IOSession session;
+
+        Endpoint(final HttpHost target, final IOSession session) {
+            this.target = target;
+            this.session = session;
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/6228a736/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalHttpAsyncClient.java
----------------------------------------------------------------------
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalHttpAsyncClient.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalHttpAsyncClient.java
index c8f7e8e..8d23479 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalHttpAsyncClient.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalHttpAsyncClient.java
@@ -27,63 +27,33 @@
 package org.apache.hc.client5.http.impl.async;
 
 import java.io.Closeable;
-import java.io.IOException;
 import java.util.List;
-import java.util.Set;
-import java.util.concurrent.Future;
 import java.util.concurrent.ThreadFactory;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicReference;
 
 import org.apache.hc.client5.http.HttpRoute;
-import org.apache.hc.client5.http.async.AsyncExecCallback;
-import org.apache.hc.client5.http.async.AsyncExecChain;
 import org.apache.hc.client5.http.async.AsyncExecRuntime;
-import org.apache.hc.client5.http.async.methods.SimpleRequestProducer;
 import org.apache.hc.client5.http.auth.AuthSchemeProvider;
 import org.apache.hc.client5.http.auth.CredentialsProvider;
-import org.apache.hc.client5.http.config.Configurable;
 import org.apache.hc.client5.http.config.RequestConfig;
 import org.apache.hc.client5.http.cookie.CookieSpecProvider;
 import org.apache.hc.client5.http.cookie.CookieStore;
-import org.apache.hc.client5.http.impl.ExecSupport;
-import org.apache.hc.client5.http.impl.RequestCopier;
 import org.apache.hc.client5.http.nio.AsyncClientConnectionManager;
 import org.apache.hc.client5.http.protocol.HttpClientContext;
 import org.apache.hc.client5.http.routing.HttpRoutePlanner;
-import org.apache.hc.core5.concurrent.BasicFuture;
-import org.apache.hc.core5.concurrent.FutureCallback;
-import org.apache.hc.core5.http.EntityDetails;
 import org.apache.hc.core5.http.HttpException;
 import org.apache.hc.core5.http.HttpHost;
 import org.apache.hc.core5.http.HttpRequest;
-import org.apache.hc.core5.http.HttpResponse;
-import org.apache.hc.core5.http.HttpStatus;
 import org.apache.hc.core5.http.HttpVersion;
 import org.apache.hc.core5.http.ProtocolVersion;
 import org.apache.hc.core5.http.config.Lookup;
-import org.apache.hc.core5.http.nio.AsyncDataConsumer;
-import org.apache.hc.core5.http.nio.AsyncEntityProducer;
-import org.apache.hc.core5.http.nio.AsyncRequestProducer;
-import org.apache.hc.core5.http.nio.AsyncResponseConsumer;
-import org.apache.hc.core5.http.nio.DataStreamChannel;
-import org.apache.hc.core5.http.nio.RequestChannel;
-import org.apache.hc.core5.http.protocol.HttpContext;
 import org.apache.hc.core5.http2.HttpVersionPolicy;
 import org.apache.hc.core5.reactor.DefaultConnectingIOReactor;
 
-class InternalHttpAsyncClient extends AbstractHttpAsyncClientBase {
+class InternalHttpAsyncClient extends InternalAbstractHttpAsyncClient {
 
     private final AsyncClientConnectionManager connmgr;
-    private final AsyncExecChainElement execChain;
     private final HttpRoutePlanner routePlanner;
     private final HttpVersionPolicy versionPolicy;
-    private final Lookup<CookieSpecProvider> cookieSpecRegistry;
-    private final Lookup<AuthSchemeProvider> authSchemeRegistry;
-    private final CookieStore cookieStore;
-    private final CredentialsProvider credentialsProvider;
-    private final RequestConfig defaultConfig;
-    private final List<Closeable> closeables;
 
     InternalHttpAsyncClient(
             final DefaultConnectingIOReactor ioReactor,
@@ -99,227 +69,27 @@ class InternalHttpAsyncClient extends AbstractHttpAsyncClientBase {
             final CredentialsProvider credentialsProvider,
             final RequestConfig defaultConfig,
             final List<Closeable> closeables) {
-        super(ioReactor, pushConsumerRegistry, threadFactory);
+        super(ioReactor, pushConsumerRegistry, threadFactory, execChain,
+                cookieSpecRegistry, authSchemeRegistry, cookieStore, credentialsProvider, defaultConfig, closeables);
         this.connmgr = connmgr;
-        this.execChain = execChain;
         this.routePlanner = routePlanner;
         this.versionPolicy = versionPolicy;
-        this.cookieSpecRegistry = cookieSpecRegistry;
-        this.authSchemeRegistry = authSchemeRegistry;
-        this.cookieStore = cookieStore;
-        this.credentialsProvider = credentialsProvider;
-        this.defaultConfig = defaultConfig;
-        this.closeables = closeables;
     }
 
     @Override
-    public void close() {
-        super.close();
-        if (closeables != null) {
-            for (final Closeable closeable: closeables) {
-                try {
-                    closeable.close();
-                } catch (final IOException ex) {
-                    log.error(ex.getMessage(), ex);
-                }
-            }
-        }
-    }
-
-    private void setupContext(final HttpClientContext context) {
-        if (context.getAttribute(HttpClientContext.AUTHSCHEME_REGISTRY) == null) {
-            context.setAttribute(HttpClientContext.AUTHSCHEME_REGISTRY, authSchemeRegistry);
-        }
-        if (context.getAttribute(HttpClientContext.COOKIESPEC_REGISTRY) == null) {
-            context.setAttribute(HttpClientContext.COOKIESPEC_REGISTRY, cookieSpecRegistry);
-        }
-        if (context.getAttribute(HttpClientContext.COOKIE_STORE) == null) {
-            context.setAttribute(HttpClientContext.COOKIE_STORE, cookieStore);
-        }
-        if (context.getAttribute(HttpClientContext.CREDS_PROVIDER) == null) {
-            context.setAttribute(HttpClientContext.CREDS_PROVIDER, credentialsProvider);
-        }
-        if (context.getAttribute(HttpClientContext.REQUEST_CONFIG) == null) {
-            context.setAttribute(HttpClientContext.REQUEST_CONFIG, defaultConfig);
-        }
+    AsyncExecRuntime crerateAsyncExecRuntime() {
+        return new InternalHttpAsyncExecRuntime(log, connmgr, getConnectionInitiator(), versionPolicy);
     }
 
     @Override
-    public <T> Future<T> execute(
-            final AsyncRequestProducer requestProducer,
-            final AsyncResponseConsumer<T> responseConsumer,
-            final HttpContext context,
-            final FutureCallback<T> callback) {
-        ensureRunning();
-        final BasicFuture<T> future = new BasicFuture<>(callback);
-        try {
-            final HttpClientContext clientContext = HttpClientContext.adapt(context);
-            requestProducer.sendRequest(new RequestChannel() {
-
-                @Override
-                public void sendRequest(
-                        final HttpRequest request,
-                        final EntityDetails entityDetails) throws HttpException, IOException {
-
-                    RequestConfig requestConfig = null;
-                    if (request instanceof Configurable) {
-                        requestConfig = ((Configurable) request).getConfig();
-                    }
-                    if (requestConfig != null) {
-                        clientContext.setRequestConfig(requestConfig);
-                    }
-                    final HttpHost target = routePlanner.determineTargetHost(request, clientContext);
-                    final HttpRoute route = routePlanner.determineRoute(target, clientContext);
-                    final String exchangeId = String.format("ex-%08X", ExecSupport.getNextExecNumber());
-                    final AsyncExecRuntime execRuntime = new AsyncExecRuntimeImpl(log, connmgr, getConnectionInitiator(), versionPolicy);
-                    if (log.isDebugEnabled()) {
-                        log.debug(exchangeId + ": preparing request execution");
-                    }
-
-                    final ProtocolVersion protocolVersion = clientContext.getProtocolVersion();
-                    if (route.isTunnelled() && protocolVersion.greaterEquals(HttpVersion.HTTP_2_0)) {
-                        throw new HttpException("HTTP/2 tunneling not supported");
-                    }
-
-                    setupContext(clientContext);
-
-                    final AsyncExecChain.Scope scope = new AsyncExecChain.Scope(exchangeId, route, request, clientContext, execRuntime);
-                    final AtomicReference<T> resultRef = new AtomicReference<>(null);
-                    final AtomicBoolean outputTerminated = new AtomicBoolean(false);
-                    execChain.execute(
-                            RequestCopier.INSTANCE.copy(request),
-                            entityDetails != null ? new AsyncEntityProducer() {
-
-                                @Override
-                                public void releaseResources() {
-                                    requestProducer.releaseResources();
-                                }
-
-                                @Override
-                                public void failed(final Exception cause) {
-                                    requestProducer.failed(cause);
-                                }
-
-                                @Override
-                                public boolean isRepeatable() {
-                                    //TODO: use AsyncRequestProducer#isRepeatable once available
-                                    return requestProducer instanceof SimpleRequestProducer;
-                                }
-
-                                @Override
-                                public long getContentLength() {
-                                    return entityDetails.getContentLength();
-                                }
-
-                                @Override
-                                public String getContentType() {
-                                    return entityDetails.getContentType();
-                                }
-
-                                @Override
-                                public String getContentEncoding() {
-                                    return entityDetails.getContentEncoding();
-                                }
-
-                                @Override
-                                public boolean isChunked() {
-                                    return entityDetails.isChunked();
-                                }
-
-                                @Override
-                                public Set<String> getTrailerNames() {
-                                    return entityDetails.getTrailerNames();
-                                }
-
-                                @Override
-                                public int available() {
-                                    return requestProducer.available();
-                                }
-
-                                @Override
-                                public void produce(final DataStreamChannel channel) throws IOException {
-                                    if (outputTerminated.get()) {
-                                        channel.endStream();
-                                        return;
-                                    }
-                                    requestProducer.produce(channel);
-                                }
-
-                            } : null,
-                            scope,
-                            new AsyncExecCallback() {
-
-                                @Override
-                                public AsyncDataConsumer handleResponse(
-                                        final HttpResponse response,
-                                        final EntityDetails entityDetails) throws HttpException, IOException {
-                                    if (response.getCode() >= HttpStatus.SC_CLIENT_ERROR) {
-                                        outputTerminated.set(true);
-                                        requestProducer.releaseResources();
-                                    }
-                                    responseConsumer.consumeResponse(response, entityDetails,
-                                            //TODO: eliminate this callback after upgrade to HttpCore 5.0b2
-                                            new FutureCallback<T>() {
-
-                                                @Override
-                                                public void completed(final T result) {
-                                                    resultRef.set(result);
-                                                }
-
-                                                @Override
-                                                public void failed(final Exception ex) {
-                                                    future.failed(ex);
-                                                }
-
-                                                @Override
-                                                public void cancelled() {
-                                                    future.cancel();
-                                                }
-
-                                            });
-                                    return responseConsumer;
-                                }
-
-                                @Override
-                                public void completed() {
-                                    if (log.isDebugEnabled()) {
-                                        log.debug(exchangeId + ": message exchange successfully completed");
-                                    }
-                                    try {
-                                        execRuntime.releaseConnection();
-                                        future.completed(resultRef.getAndSet(null));
-                                    } finally {
-                                        responseConsumer.releaseResources();
-                                        requestProducer.releaseResources();
-                                    }
-                                }
-
-                                @Override
-                                public void failed(final Exception cause) {
-                                    if (log.isDebugEnabled()) {
-                                        log.debug(exchangeId + ": request failed: " + cause.getMessage());
-                                    }
-                                    try {
-                                        execRuntime.discardConnection();
-                                        responseConsumer.failed(cause);
-                                    } finally {
-                                        try {
-                                            future.failed(cause);
-                                        } finally {
-                                            responseConsumer.releaseResources();
-                                            requestProducer.releaseResources();
-                                        }
-                                    }
-                                }
-
-                            });
-                }
-
-            });
-        } catch (final HttpException | IOException ex) {
-            future.failed(ex);
+    HttpRoute determineRoute(final HttpRequest request, final HttpClientContext clientContext) throws HttpException {
+        final HttpHost target = routePlanner.determineTargetHost(request, clientContext);
+        final HttpRoute route = routePlanner.determineRoute(target, clientContext);
+        final ProtocolVersion protocolVersion = clientContext.getProtocolVersion();
+        if (route.isTunnelled() && protocolVersion.greaterEquals(HttpVersion.HTTP_2_0)) {
+            throw new HttpException("HTTP/2 tunneling not supported");
         }
-        return future;
+        return route;
     }
 
 }

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/6228a736/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalHttpAsyncExecRuntime.java
----------------------------------------------------------------------
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
new file mode 100644
index 0000000..23ed986
--- /dev/null
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalHttpAsyncExecRuntime.java
@@ -0,0 +1,276 @@
+/*
+ * ====================================================================
+ * 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.async;
+
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.hc.client5.http.HttpRoute;
+import org.apache.hc.client5.http.async.AsyncExecRuntime;
+import org.apache.hc.client5.http.config.RequestConfig;
+import org.apache.hc.client5.http.impl.ConnPoolSupport;
+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.FutureCallback;
+import org.apache.hc.core5.http.nio.AsyncClientExchangeHandler;
+import org.apache.hc.core5.http2.HttpVersionPolicy;
+import org.apache.hc.core5.reactor.ConnectionInitiator;
+import org.apache.hc.core5.util.TimeValue;
+import org.apache.logging.log4j.Logger;
+
+class InternalHttpAsyncExecRuntime implements AsyncExecRuntime {
+
+    private final Logger log;
+    private final AsyncClientConnectionManager manager;
+    private final ConnectionInitiator connectionInitiator;
+    private final HttpVersionPolicy versionPolicy;
+    private final AtomicReference<AsyncConnectionEndpoint> endpointRef;
+    private volatile boolean reusable;
+    private volatile Object state;
+    private volatile TimeValue validDuration;
+
+    InternalHttpAsyncExecRuntime(
+            final Logger log,
+            final AsyncClientConnectionManager manager,
+            final ConnectionInitiator connectionInitiator,
+            final HttpVersionPolicy versionPolicy) {
+        super();
+        this.log = log;
+        this.manager = manager;
+        this.connectionInitiator = connectionInitiator;
+        this.versionPolicy = versionPolicy;
+        this.endpointRef = new AtomicReference<>(null);
+        this.validDuration = TimeValue.NEG_ONE_MILLISECONDS;
+    }
+
+    @Override
+    public boolean isConnectionAcquired() {
+        return endpointRef.get() != null;
+    }
+
+    @Override
+    public void acquireConnection(
+            final HttpRoute route,
+            final Object object,
+            final HttpClientContext context,
+            final FutureCallback<AsyncExecRuntime> callback) {
+        if (endpointRef.get() == null) {
+            state = object;
+            final RequestConfig requestConfig = context.getRequestConfig();
+            manager.lease(route, object, requestConfig.getConnectionRequestTimeout(), new FutureCallback<AsyncConnectionEndpoint>() {
+
+                @Override
+                public void completed(final AsyncConnectionEndpoint connectionEndpoint) {
+                    endpointRef.set(connectionEndpoint);
+                    reusable = connectionEndpoint.isConnected();
+                    callback.completed(InternalHttpAsyncExecRuntime.this);
+                }
+
+                @Override
+                public void failed(final Exception ex) {
+                    callback.failed(ex);
+                }
+
+                @Override
+                public void cancelled() {
+                    callback.cancelled();
+                }
+            });
+        } else {
+            callback.completed(this);
+        }
+    }
+
+    private void discardEndpoint(final AsyncConnectionEndpoint endpoint) {
+        try {
+            endpoint.shutdown();
+            if (log.isDebugEnabled()) {
+                log.debug(ConnPoolSupport.getId(endpoint) + ": discarding endpoint");
+            }
+        } catch (final IOException ex) {
+            if (log.isDebugEnabled()) {
+                log.debug(ConnPoolSupport.getId(endpoint) + ": " + ex.getMessage(), ex);
+            }
+        } finally {
+            manager.release(endpoint, null, TimeValue.ZERO_MILLISECONDS);
+        }
+    }
+
+    @Override
+    public void releaseConnection() {
+        final AsyncConnectionEndpoint endpoint = endpointRef.getAndSet(null);
+        if (endpoint != null) {
+            if (reusable) {
+                if (log.isDebugEnabled()) {
+                    log.debug(ConnPoolSupport.getId(endpoint) + ": releasing valid endpoint");
+                }
+                manager.release(endpoint, state, validDuration);
+            } else {
+                discardEndpoint(endpoint);
+            }
+        }
+    }
+
+    @Override
+    public void discardConnection() {
+        final AsyncConnectionEndpoint endpoint = endpointRef.getAndSet(null);
+        if (endpoint != null) {
+            discardEndpoint(endpoint);
+        }
+    }
+
+    @Override
+    public boolean validateConnection() {
+        if (reusable) {
+            final AsyncConnectionEndpoint endpoint = endpointRef.get();
+            return endpoint != null && endpoint.isConnected();
+        } else {
+            final AsyncConnectionEndpoint endpoint = endpointRef.getAndSet(null);
+            if (endpoint != null) {
+                discardEndpoint(endpoint);
+            }
+        }
+        return false;
+    }
+
+    AsyncConnectionEndpoint ensureValid() {
+        final AsyncConnectionEndpoint endpoint = endpointRef.get();
+        if (endpoint == null) {
+            throw new IllegalStateException("Endpoint not acquired / already released");
+        }
+        return endpoint;
+    }
+
+    @Override
+    public boolean isConnected() {
+        final AsyncConnectionEndpoint endpoint = endpointRef.get();
+        return endpoint != null && endpoint.isConnected();
+    }
+
+    @Override
+    public void connect(
+            final HttpClientContext context,
+            final FutureCallback<AsyncExecRuntime> callback) {
+        final AsyncConnectionEndpoint endpoint = ensureValid();
+        if (endpoint.isConnected()) {
+            callback.completed(this);
+        } else {
+            final RequestConfig requestConfig = context.getRequestConfig();
+            manager.connect(
+                    endpoint,
+                    connectionInitiator,
+                    requestConfig.getConnectTimeout(),
+                    versionPolicy,
+                    context,
+                    new FutureCallback<AsyncConnectionEndpoint>() {
+
+                        @Override
+                        public void completed(final AsyncConnectionEndpoint endpoint) {
+                            final TimeValue socketTimeout = requestConfig.getSocketTimeout();
+                            if (TimeValue.isPositive(socketTimeout)) {
+                                endpoint.setSocketTimeout(socketTimeout.toMillisIntBound());
+                            }
+                            callback.completed(InternalHttpAsyncExecRuntime.this);
+                        }
+
+                        @Override
+                        public void failed(final Exception ex) {
+                            callback.failed(ex);
+                        }
+
+                        @Override
+                        public void cancelled() {
+                            callback.cancelled();
+                        }
+
+            });
+        }
+
+    }
+
+    @Override
+    public void upgradeTls(final HttpClientContext context) {
+        final AsyncConnectionEndpoint endpoint = ensureValid();
+        manager.upgrade(endpoint, versionPolicy, context);
+    }
+
+    @Override
+    public void execute(final AsyncClientExchangeHandler exchangeHandler, final HttpClientContext context) {
+        final AsyncConnectionEndpoint endpoint = ensureValid();
+        if (endpoint.isConnected()) {
+            if (log.isDebugEnabled()) {
+                log.debug(ConnPoolSupport.getId(endpoint) + ": executing " + ConnPoolSupport.getId(exchangeHandler));
+            }
+            endpoint.execute(exchangeHandler, context);
+        } else {
+            connect(context, new FutureCallback<AsyncExecRuntime>() {
+
+                @Override
+                public void completed(final AsyncExecRuntime runtime) {
+                    if (log.isDebugEnabled()) {
+                        log.debug(ConnPoolSupport.getId(endpoint) + ": executing " + ConnPoolSupport.getId(exchangeHandler));
+                    }
+                    try {
+                        endpoint.execute(exchangeHandler, context);
+                    } catch (final RuntimeException ex) {
+                        failed(ex);
+                    }
+                }
+
+                @Override
+                public void failed(final Exception ex) {
+                    exchangeHandler.failed(ex);
+                }
+
+                @Override
+                public void cancelled() {
+                    exchangeHandler.failed(new InterruptedIOException());
+                }
+
+            });
+        }
+
+    }
+
+    @Override
+    public void markConnectionReusable(final Object newState, final TimeValue newValidDuration) {
+        reusable = true;
+        state = newState;
+        validDuration = newValidDuration;
+    }
+
+    @Override
+    public void markConnectionNonReusable() {
+        reusable = false;
+        state = null;
+        validDuration = null;
+    }
+
+}


[5/6] httpcomponents-client git commit: * HTTP/2 multiplexing HttpAsyncClient implementation * Restructured integration tests to reduce test duplication

Posted by ol...@apache.org.
http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/6228a736/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/LocalAsyncServerTestBase.java
----------------------------------------------------------------------
diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/LocalAsyncServerTestBase.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/LocalAsyncServerTestBase.java
deleted file mode 100644
index 5ae533c..0000000
--- a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/LocalAsyncServerTestBase.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * ====================================================================
- * 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.testing.async;
-
-import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManager;
-import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBuilder;
-import org.apache.hc.client5.http.ssl.H2TlsStrategy;
-import org.apache.hc.client5.testing.SSLTestContexts;
-import org.apache.hc.core5.function.Supplier;
-import org.apache.hc.core5.http.URIScheme;
-import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler;
-import org.apache.hc.core5.reactor.IOReactorConfig;
-import org.apache.hc.core5.testing.nio.Http2TestServer;
-import org.apache.hc.core5.util.TimeValue;
-import org.apache.hc.core5.util.Timeout;
-import org.junit.Rule;
-import org.junit.rules.ExternalResource;
-
-public abstract class LocalAsyncServerTestBase {
-
-    public static final Timeout TIMEOUT = Timeout.ofSeconds(30);
-    public static final Timeout LONG_TIMEOUT = Timeout.ofSeconds(60);
-
-    protected final URIScheme scheme;
-
-    public LocalAsyncServerTestBase(final URIScheme scheme) {
-        this.scheme = scheme;
-    }
-
-    public LocalAsyncServerTestBase() {
-        this(URIScheme.HTTP);
-    }
-
-    protected Http2TestServer server;
-    protected PoolingAsyncClientConnectionManager connManager;
-
-    @Rule
-    public ExternalResource serverResource = new ExternalResource() {
-
-        @Override
-        protected void before() throws Throwable {
-            server = new Http2TestServer(
-                    IOReactorConfig.custom()
-                        .setSoTimeout(TIMEOUT)
-                        .build(),
-                    scheme == URIScheme.HTTPS ? SSLTestContexts.createServerSSLContext() : null);
-            server.register("/echo/*", new Supplier<AsyncServerExchangeHandler>() {
-
-                @Override
-                public AsyncServerExchangeHandler get() {
-                    return new AsyncEchoHandler();
-                }
-
-            });
-            server.register("/random/*", new Supplier<AsyncServerExchangeHandler>() {
-
-                @Override
-                public AsyncServerExchangeHandler get() {
-                    return new AsyncRandomHandler();
-                }
-
-            });
-        }
-
-        @Override
-        protected void after() {
-            if (server != null) {
-                server.shutdown(TimeValue.ofSeconds(5));
-                server = null;
-            }
-        }
-
-    };
-
-    @Rule
-    public ExternalResource connManagerResource = new ExternalResource() {
-
-        @Override
-        protected void before() throws Throwable {
-            connManager = PoolingAsyncClientConnectionManagerBuilder.create()
-                    .setTlsStrategy(new H2TlsStrategy(SSLTestContexts.createClientSSLContext()))
-                    .build();
-        }
-
-        @Override
-        protected void after() {
-            if (connManager != null) {
-                connManager.close();
-                connManager = null;
-            }
-        }
-
-    };
-
-}

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/6228a736/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestAsyncRedirects.java
----------------------------------------------------------------------
diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestAsyncRedirects.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestAsyncRedirects.java
deleted file mode 100644
index 83c429e..0000000
--- a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestAsyncRedirects.java
+++ /dev/null
@@ -1,910 +0,0 @@
-/*
- * ====================================================================
- * 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.testing.async;
-
-import java.net.InetSocketAddress;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-import java.util.Queue;
-import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Future;
-
-import org.apache.hc.client5.http.CircularRedirectException;
-import org.apache.hc.client5.http.RedirectException;
-import org.apache.hc.client5.http.async.methods.SimpleHttpRequest;
-import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
-import org.apache.hc.client5.http.config.RequestConfig;
-import org.apache.hc.client5.http.cookie.BasicCookieStore;
-import org.apache.hc.client5.http.cookie.CookieStore;
-import org.apache.hc.client5.http.impl.cookie.BasicClientCookie;
-import org.apache.hc.client5.http.protocol.HttpClientContext;
-import org.apache.hc.client5.testing.SSLTestContexts;
-import org.apache.hc.core5.function.Supplier;
-import org.apache.hc.core5.http.ContentType;
-import org.apache.hc.core5.http.Header;
-import org.apache.hc.core5.http.HttpException;
-import org.apache.hc.core5.http.HttpHeaders;
-import org.apache.hc.core5.http.HttpHost;
-import org.apache.hc.core5.http.HttpRequest;
-import org.apache.hc.core5.http.HttpResponse;
-import org.apache.hc.core5.http.HttpStatus;
-import org.apache.hc.core5.http.ProtocolException;
-import org.apache.hc.core5.http.URIScheme;
-import org.apache.hc.core5.http.message.BasicHeader;
-import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler;
-import org.apache.hc.core5.http.protocol.HttpCoreContext;
-import org.apache.hc.core5.net.URIBuilder;
-import org.apache.hc.core5.reactor.IOReactorConfig;
-import org.apache.hc.core5.reactor.ListenerEndpoint;
-import org.apache.hc.core5.testing.nio.Http2TestServer;
-import org.apache.hc.core5.util.TimeValue;
-import org.junit.Assert;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-
-/**
- * Redirection test cases.
- */
-@RunWith(Parameterized.class)
-public class TestAsyncRedirects extends IntegrationTestBase {
-
-    @Parameterized.Parameters(name = "{0}")
-    public static Collection<Object[]> protocols() {
-        return Arrays.asList(new Object[][]{
-                {URIScheme.HTTP},
-                {URIScheme.HTTPS},
-        });
-    }
-
-    public TestAsyncRedirects(final URIScheme scheme) {
-        super(scheme);
-    }
-
-    static class BasicRedirectService extends AbstractSimpleServerExchangeHandler {
-
-        private final int statuscode;
-        private final boolean keepAlive;
-
-        public BasicRedirectService(final int statuscode, final boolean keepAlive) {
-            super();
-            this.statuscode = statuscode;
-            this.keepAlive = keepAlive;
-        }
-
-        public BasicRedirectService(final int statuscode) {
-            this(statuscode, true);
-        }
-
-        @Override
-        protected SimpleHttpResponse handle(
-                final SimpleHttpRequest request, final HttpCoreContext context) throws HttpException {
-            try {
-                final URI requestURI = request.getUri();
-                final String path = requestURI.getPath();
-                if (path.equals("/oldlocation/")) {
-                    final SimpleHttpResponse response = new SimpleHttpResponse(statuscode);
-                    response.addHeader(new BasicHeader("Location",
-                            new URIBuilder(requestURI).setPath("/newlocation/").build()));
-                    if (!keepAlive) {
-                        response.addHeader(new BasicHeader("Connection", "close"));
-                    }
-                    return response;
-                } else if (path.equals("/newlocation/")) {
-                    final SimpleHttpResponse response = new SimpleHttpResponse(HttpStatus.SC_OK);
-                    response.setBodyText("Successful redirect", ContentType.TEXT_PLAIN);
-                    return response;
-                } else {
-                    return new SimpleHttpResponse(HttpStatus.SC_NOT_FOUND);
-                }
-            } catch (final URISyntaxException ex) {
-                throw new ProtocolException(ex.getMessage(), ex);
-            }
-        }
-
-    }
-
-    static class CircularRedirectService extends AbstractSimpleServerExchangeHandler {
-
-        public CircularRedirectService() {
-            super();
-        }
-
-        @Override
-        protected SimpleHttpResponse handle(
-                final SimpleHttpRequest request, final HttpCoreContext context) throws HttpException {
-            try {
-                final URI requestURI = request.getUri();
-                final String path = requestURI.getPath();
-                if (path.startsWith("/circular-oldlocation")) {
-                    final SimpleHttpResponse response = new SimpleHttpResponse(HttpStatus.SC_MOVED_TEMPORARILY);
-                    response.addHeader(new BasicHeader("Location", "/circular-location2"));
-                    return response;
-                } else if (path.startsWith("/circular-location2")) {
-                    final SimpleHttpResponse response = new SimpleHttpResponse(HttpStatus.SC_MOVED_TEMPORARILY);
-                    response.addHeader(new BasicHeader("Location", "/circular-oldlocation"));
-                    return response;
-                } else {
-                    return new SimpleHttpResponse(HttpStatus.SC_NOT_FOUND);
-                }
-            } catch (final URISyntaxException ex) {
-                throw new ProtocolException(ex.getMessage(), ex);
-            }
-        }
-
-    }
-
-    static class RelativeRedirectService extends AbstractSimpleServerExchangeHandler {
-
-        @Override
-        protected SimpleHttpResponse handle(
-                final SimpleHttpRequest request, final HttpCoreContext context) throws HttpException {
-            try {
-                final URI requestURI = request.getUri();
-                final String path = requestURI.getPath();
-                if (path.equals("/oldlocation/")) {
-                    final SimpleHttpResponse response = new SimpleHttpResponse(HttpStatus.SC_MOVED_TEMPORARILY);
-                    response.addHeader(new BasicHeader("Location", "/relativelocation/"));
-                    return response;
-                } else if (path.equals("/relativelocation/")) {
-                    final SimpleHttpResponse response = new SimpleHttpResponse(HttpStatus.SC_OK);
-                    response.setBodyText("Successful redirect", ContentType.TEXT_PLAIN);
-                    return response;
-                } else {
-                    return new SimpleHttpResponse(HttpStatus.SC_NOT_FOUND);
-                }
-            } catch (final URISyntaxException ex) {
-                throw new ProtocolException(ex.getMessage(), ex);
-            }
-        }
-    }
-
-    static class RelativeRedirectService2 extends AbstractSimpleServerExchangeHandler {
-
-        @Override
-        protected SimpleHttpResponse handle(
-                final SimpleHttpRequest request, final HttpCoreContext context) throws HttpException {
-            try {
-                final URI requestURI = request.getUri();
-                final String path = requestURI.getPath();
-                if (path.equals("/test/oldlocation")) {
-                    final SimpleHttpResponse response = new SimpleHttpResponse(HttpStatus.SC_MOVED_TEMPORARILY);
-                    response.addHeader(new BasicHeader("Location", "relativelocation"));
-                    return response;
-                } else if (path.equals("/test/relativelocation")) {
-                    final SimpleHttpResponse response = new SimpleHttpResponse(HttpStatus.SC_OK);
-                    response.setBodyText("Successful redirect", ContentType.TEXT_PLAIN);
-                    return response;
-                } else {
-                    return new SimpleHttpResponse(HttpStatus.SC_NOT_FOUND);
-                }
-            } catch (final URISyntaxException ex) {
-                throw new ProtocolException(ex.getMessage(), ex);
-            }
-        }
-
-    }
-
-    @Test
-    public void testBasicRedirect300() throws Exception {
-        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
-
-            @Override
-            public AsyncServerExchangeHandler get() {
-                return new BasicRedirectService(HttpStatus.SC_MULTIPLE_CHOICES, false);
-            }
-
-        });
-        final HttpHost target = start();
-
-        final HttpClientContext context = HttpClientContext.create();
-        final Future<SimpleHttpResponse> future = httpclient.execute(
-                SimpleHttpRequest.get(target, "/oldlocation/"), context, null);
-        final HttpResponse response = future.get();
-        Assert.assertNotNull(response);
-
-        final HttpRequest request = context.getRequest();
-
-        Assert.assertEquals(HttpStatus.SC_MULTIPLE_CHOICES, response.getCode());
-        Assert.assertEquals("/oldlocation/", request.getRequestUri());
-    }
-
-    @Test
-    public void testBasicRedirect301KeepAlive() throws Exception {
-        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
-
-            @Override
-            public AsyncServerExchangeHandler get() {
-                return new BasicRedirectService(HttpStatus.SC_MOVED_PERMANENTLY, true);
-            }
-
-        });
-
-        final HttpHost target = start();
-        final HttpClientContext context = HttpClientContext.create();
-        final Future<SimpleHttpResponse> future = httpclient.execute(
-                SimpleHttpRequest.get(target, "/oldlocation/"), context, null);
-        final HttpResponse response = future.get();
-        Assert.assertNotNull(response);
-
-        final HttpRequest request = context.getRequest();
-
-        Assert.assertEquals(HttpStatus.SC_OK, response.getCode());
-        Assert.assertEquals("/newlocation/", request.getRequestUri());
-        Assert.assertEquals(target, new HttpHost(request.getAuthority(), request.getScheme()));
-    }
-
-    @Test
-    public void testBasicRedirect301NoKeepAlive() throws Exception {
-        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
-
-            @Override
-            public AsyncServerExchangeHandler get() {
-                return new BasicRedirectService(HttpStatus.SC_MOVED_PERMANENTLY, false);
-            }
-
-        });
-
-        final HttpHost target = start();
-        final HttpClientContext context = HttpClientContext.create();
-        final Future<SimpleHttpResponse> future = httpclient.execute(
-                SimpleHttpRequest.get(target, "/oldlocation/"), context, null);
-        final HttpResponse response = future.get();
-        Assert.assertNotNull(response);
-
-        final HttpRequest request = context.getRequest();
-
-        Assert.assertEquals(HttpStatus.SC_OK, response.getCode());
-        Assert.assertEquals("/newlocation/", request.getRequestUri());
-        Assert.assertEquals(target, new HttpHost(request.getAuthority(), request.getScheme()));
-    }
-
-    @Test
-    public void testBasicRedirect302() throws Exception {
-        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
-
-            @Override
-            public AsyncServerExchangeHandler get() {
-                return new BasicRedirectService(HttpStatus.SC_MOVED_TEMPORARILY);
-            }
-
-        });
-        final HttpHost target = start();
-        final HttpClientContext context = HttpClientContext.create();
-        final Future<SimpleHttpResponse> future = httpclient.execute(
-                SimpleHttpRequest.get(target, "/oldlocation/"), context, null);
-        final HttpResponse response = future.get();
-        Assert.assertNotNull(response);
-
-        final HttpRequest request = context.getRequest();
-
-        Assert.assertEquals(HttpStatus.SC_OK, response.getCode());
-        Assert.assertEquals("/newlocation/", request.getRequestUri());
-        Assert.assertEquals(target, new HttpHost(request.getAuthority(), request.getScheme()));
-    }
-
-    @Test
-    public void testBasicRedirect302NoLocation() throws Exception {
-        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
-
-            @Override
-            public AsyncServerExchangeHandler get() {
-                return new AbstractSimpleServerExchangeHandler() {
-
-                    @Override
-                    protected SimpleHttpResponse handle(
-                            final SimpleHttpRequest request, final HttpCoreContext context) throws HttpException {
-                        return new SimpleHttpResponse(HttpStatus.SC_MOVED_TEMPORARILY);
-                    }
-
-                };
-            }
-
-        });
-        final HttpHost target = start();
-        final HttpClientContext context = HttpClientContext.create();
-        final Future<SimpleHttpResponse> future = httpclient.execute(
-                SimpleHttpRequest.get(target, "/oldlocation/"), context, null);
-        final HttpResponse response = future.get();
-        Assert.assertNotNull(response);
-
-        final HttpRequest request = context.getRequest();
-        Assert.assertEquals(HttpStatus.SC_MOVED_TEMPORARILY, response.getCode());
-        Assert.assertEquals("/oldlocation/", request.getRequestUri());
-        Assert.assertEquals(target, new HttpHost(request.getAuthority(), request.getScheme()));
-    }
-
-    @Test
-    public void testBasicRedirect303() throws Exception {
-        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
-
-            @Override
-            public AsyncServerExchangeHandler get() {
-                return new BasicRedirectService(HttpStatus.SC_SEE_OTHER);
-            }
-
-        });
-        final HttpHost target = start();
-        final HttpClientContext context = HttpClientContext.create();
-        final Future<SimpleHttpResponse> future = httpclient.execute(
-                SimpleHttpRequest.get(target, "/oldlocation/"), context, null);
-        final HttpResponse response = future.get();
-        Assert.assertNotNull(response);
-
-        final HttpRequest request = context.getRequest();
-
-        Assert.assertEquals(HttpStatus.SC_OK, response.getCode());
-        Assert.assertEquals("/newlocation/", request.getRequestUri());
-        Assert.assertEquals(target, new HttpHost(request.getAuthority(), request.getScheme()));
-    }
-
-    @Test
-    public void testBasicRedirect304() throws Exception {
-        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
-
-            @Override
-            public AsyncServerExchangeHandler get() {
-                return new BasicRedirectService(HttpStatus.SC_NOT_MODIFIED);
-            }
-
-        });
-        final HttpHost target = start();
-        final HttpClientContext context = HttpClientContext.create();
-        final Future<SimpleHttpResponse> future = httpclient.execute(
-                SimpleHttpRequest.get(target, "/oldlocation/"), context, null);
-        final HttpResponse response = future.get();
-        Assert.assertNotNull(response);
-
-        final HttpRequest request = context.getRequest();
-
-        Assert.assertEquals(HttpStatus.SC_NOT_MODIFIED, response.getCode());
-        Assert.assertEquals("/oldlocation/", request.getRequestUri());
-    }
-
-    @Test
-    public void testBasicRedirect305() throws Exception {
-        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
-
-            @Override
-            public AsyncServerExchangeHandler get() {
-                return new BasicRedirectService(HttpStatus.SC_USE_PROXY);
-            }
-
-        });
-        final HttpHost target = start();
-        final HttpClientContext context = HttpClientContext.create();
-        final Future<SimpleHttpResponse> future = httpclient.execute(
-                SimpleHttpRequest.get(target, "/oldlocation/"), context, null);
-        final HttpResponse response = future.get();
-        Assert.assertNotNull(response);
-
-        final HttpRequest request = context.getRequest();
-
-        Assert.assertEquals(HttpStatus.SC_USE_PROXY, response.getCode());
-        Assert.assertEquals("/oldlocation/", request.getRequestUri());
-    }
-
-    @Test
-    public void testBasicRedirect307() throws Exception {
-        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
-
-            @Override
-            public AsyncServerExchangeHandler get() {
-                return new BasicRedirectService(HttpStatus.SC_TEMPORARY_REDIRECT);
-            }
-
-        });
-        final HttpHost target = start();
-        final HttpClientContext context = HttpClientContext.create();
-        final Future<SimpleHttpResponse> future = httpclient.execute(
-                SimpleHttpRequest.get(target, "/oldlocation/"), context, null);
-        final HttpResponse response = future.get();
-        Assert.assertNotNull(response);
-
-        final HttpRequest request = context.getRequest();
-
-        Assert.assertEquals(HttpStatus.SC_OK, response.getCode());
-        Assert.assertEquals("/newlocation/", request.getRequestUri());
-        Assert.assertEquals(target, new HttpHost(request.getAuthority(), request.getScheme()));
-    }
-
-    @Test(expected=ExecutionException.class)
-    public void testMaxRedirectCheck() throws Exception {
-        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
-
-            @Override
-            public AsyncServerExchangeHandler get() {
-                return new CircularRedirectService();
-            }
-
-        });
-        final HttpHost target = start();
-
-        final RequestConfig config = RequestConfig.custom()
-                .setCircularRedirectsAllowed(true)
-                .setMaxRedirects(5).build();
-        try {
-            final SimpleHttpRequest request = SimpleHttpRequest.get(target, "/circular-oldlocation/");
-            request.setConfig(config);
-            final Future<SimpleHttpResponse> future = httpclient.execute(request, null);
-            future.get();
-        } catch (final ExecutionException e) {
-            Assert.assertTrue(e.getCause() instanceof RedirectException);
-            throw e;
-        }
-    }
-
-    @Test(expected=ExecutionException.class)
-    public void testCircularRedirect() throws Exception {
-        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
-
-            @Override
-            public AsyncServerExchangeHandler get() {
-                return new CircularRedirectService();
-            }
-
-        });
-        final HttpHost target = start();
-
-        final RequestConfig config = RequestConfig.custom()
-                .setCircularRedirectsAllowed(false)
-                .build();
-        try {
-            final SimpleHttpRequest request = SimpleHttpRequest.get(target, "/circular-oldlocation/");
-            request.setConfig(config);
-            final Future<SimpleHttpResponse> future = httpclient.execute(request, null);
-            future.get();
-        } catch (final ExecutionException e) {
-            Assert.assertTrue(e.getCause() instanceof CircularRedirectException);
-            throw e;
-        }
-    }
-
-    @Test
-    public void testPostRedirectSeeOther() throws Exception {
-        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
-
-            @Override
-            public AsyncServerExchangeHandler get() {
-                return new BasicRedirectService(HttpStatus.SC_SEE_OTHER);
-            }
-
-        });
-        final HttpHost target = start();
-
-        final HttpClientContext context = HttpClientContext.create();
-
-        final SimpleHttpRequest post = SimpleHttpRequest.post(target, "/oldlocation/");
-        post.setBodyText("stuff", ContentType.TEXT_PLAIN);
-        final Future<SimpleHttpResponse> future = httpclient.execute(post, context, null);
-        final HttpResponse response = future.get();
-        Assert.assertNotNull(response);
-
-        final HttpRequest request = context.getRequest();
-
-        Assert.assertEquals(HttpStatus.SC_OK, response.getCode());
-        Assert.assertEquals("/newlocation/", request.getRequestUri());
-        Assert.assertEquals("GET", request.getMethod());
-    }
-
-    @Test
-    public void testRelativeRedirect() throws Exception {
-        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
-
-            @Override
-            public AsyncServerExchangeHandler get() {
-                return new RelativeRedirectService();
-            }
-
-        });
-        final HttpHost target = start();
-
-        final HttpClientContext context = HttpClientContext.create();
-
-        final Future<SimpleHttpResponse> future = httpclient.execute(
-                SimpleHttpRequest.get(target, "/oldlocation/"), context, null);
-        final HttpResponse response = future.get();
-        Assert.assertNotNull(response);
-
-        final HttpRequest request = context.getRequest();
-
-        Assert.assertEquals(HttpStatus.SC_OK, response.getCode());
-        Assert.assertEquals("/relativelocation/", request.getRequestUri());
-        Assert.assertEquals(target, new HttpHost(request.getAuthority(), request.getScheme()));
-    }
-
-    @Test
-    public void testRelativeRedirect2() throws Exception {
-        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
-
-            @Override
-            public AsyncServerExchangeHandler get() {
-                return new RelativeRedirectService2();
-            }
-
-        });
-        final HttpHost target = start();
-
-        final HttpClientContext context = HttpClientContext.create();
-
-        final Future<SimpleHttpResponse> future = httpclient.execute(
-                SimpleHttpRequest.get(target, "/test/oldlocation"), context, null);
-        final HttpResponse response = future.get();
-        Assert.assertNotNull(response);
-
-        final HttpRequest request = context.getRequest();
-
-        Assert.assertEquals(HttpStatus.SC_OK, response.getCode());
-        Assert.assertEquals("/test/relativelocation", request.getRequestUri());
-        Assert.assertEquals(target, new HttpHost(request.getAuthority(), request.getScheme()));
-    }
-
-    static class BogusRedirectService extends AbstractSimpleServerExchangeHandler {
-
-        private final String url;
-
-        public BogusRedirectService(final String url) {
-            super();
-            this.url = url;
-        }
-
-        @Override
-        protected SimpleHttpResponse handle(
-                final SimpleHttpRequest request, final HttpCoreContext context) throws HttpException {
-            try {
-                final URI requestURI = request.getUri();
-                final String path = requestURI.getPath();
-                if (path.equals("/oldlocation/")) {
-                    final SimpleHttpResponse response = new SimpleHttpResponse(HttpStatus.SC_MOVED_TEMPORARILY);
-                    response.addHeader(new BasicHeader("Location", url));
-                    return response;
-                } else if (path.equals("/relativelocation/")) {
-                    final SimpleHttpResponse response = new SimpleHttpResponse(HttpStatus.SC_OK);
-                    response.setBodyText("Successful redirect", ContentType.TEXT_PLAIN);
-                    return response;
-                } else {
-                    return new SimpleHttpResponse(HttpStatus.SC_NOT_FOUND);
-                }
-            } catch (final URISyntaxException ex) {
-                throw new ProtocolException(ex.getMessage(), ex);
-            }
-        }
-
-    }
-
-    @Test(expected=ExecutionException.class)
-    public void testRejectBogusRedirectLocation() throws Exception {
-        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
-
-            @Override
-            public AsyncServerExchangeHandler get() {
-                return new BogusRedirectService("xxx://bogus");
-            }
-
-        });
-        final HttpHost target = start();
-
-        try {
-            final Future<SimpleHttpResponse> future = httpclient.execute(
-                    SimpleHttpRequest.get(target, "/oldlocation/"), null);
-            future.get();
-        } catch (final ExecutionException ex) {
-            Assert.assertTrue(ex.getCause() instanceof HttpException);
-            throw ex;
-        }
-    }
-
-    @Test(expected=ExecutionException.class)
-    public void testRejectInvalidRedirectLocation() throws Exception {
-        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
-
-            @Override
-            public AsyncServerExchangeHandler get() {
-                return new BogusRedirectService("/newlocation/?p=I have spaces");
-            }
-
-        });
-        final HttpHost target = start();
-
-        try {
-            final Future<SimpleHttpResponse> future = httpclient.execute(
-                    SimpleHttpRequest.get(target, "/oldlocation/"), null);
-            future.get();
-        } catch (final ExecutionException e) {
-            Assert.assertTrue(e.getCause() instanceof ProtocolException);
-            throw e;
-        }
-    }
-
-    @Test
-    public void testRedirectWithCookie() throws Exception {
-        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
-
-            @Override
-            public AsyncServerExchangeHandler get() {
-                return new BasicRedirectService(HttpStatus.SC_MOVED_TEMPORARILY);
-            }
-
-        });
-        final HttpHost target = start();
-
-        final CookieStore cookieStore = new BasicCookieStore();
-        final HttpClientContext context = HttpClientContext.create();
-        context.setCookieStore(cookieStore);
-
-        final BasicClientCookie cookie = new BasicClientCookie("name", "value");
-        cookie.setDomain(target.getHostName());
-        cookie.setPath("/");
-
-        cookieStore.addCookie(cookie);
-
-        final Future<SimpleHttpResponse> future = httpclient.execute(
-                SimpleHttpRequest.get(target, "/oldlocation/"), context, null);
-        final HttpResponse response = future.get();
-        Assert.assertNotNull(response);
-
-        final HttpRequest request = context.getRequest();
-
-        Assert.assertEquals(HttpStatus.SC_OK, response.getCode());
-        Assert.assertEquals("/newlocation/", request.getRequestUri());
-
-        final Header[] headers = request.getHeaders("Cookie");
-        Assert.assertEquals("There can only be one (cookie)", 1, headers.length);
-    }
-
-    @Test
-    public void testDefaultHeadersRedirect() throws Exception {
-        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
-
-            @Override
-            public AsyncServerExchangeHandler get() {
-                return new BasicRedirectService(HttpStatus.SC_MOVED_TEMPORARILY);
-            }
-
-        });
-
-        final List<Header> defaultHeaders = new ArrayList<>(1);
-        defaultHeaders.add(new BasicHeader(HttpHeaders.USER_AGENT, "my-test-client"));
-        clientBuilder.setDefaultHeaders(defaultHeaders);
-
-        final HttpHost target = start();
-
-        final HttpClientContext context = HttpClientContext.create();
-
-        final Future<SimpleHttpResponse> future = httpclient.execute(
-                SimpleHttpRequest.get(target, "/oldlocation/"), context, null);
-        final HttpResponse response = future.get();
-        Assert.assertNotNull(response);
-
-        final HttpRequest request = context.getRequest();
-
-        Assert.assertEquals(HttpStatus.SC_OK, response.getCode());
-        Assert.assertEquals("/newlocation/", request.getRequestUri());
-
-        final Header header = request.getFirstHeader(HttpHeaders.USER_AGENT);
-        Assert.assertEquals("my-test-client", header.getValue());
-    }
-
-    static class CrossSiteRedirectService extends AbstractSimpleServerExchangeHandler {
-
-        private final HttpHost host;
-
-        public CrossSiteRedirectService(final HttpHost host) {
-            super();
-            this.host = host;
-        }
-
-        @Override
-        protected SimpleHttpResponse handle(
-                final SimpleHttpRequest request, final HttpCoreContext context) throws HttpException {
-            final String location;
-            try {
-                final URIBuilder uribuilder = new URIBuilder(request.getUri());
-                uribuilder.setScheme(host.getSchemeName());
-                uribuilder.setHost(host.getHostName());
-                uribuilder.setPort(host.getPort());
-                uribuilder.setPath("/random/1024");
-                location = uribuilder.build().toASCIIString();
-            } catch (final URISyntaxException ex) {
-                throw new ProtocolException("Invalid request URI", ex);
-            }
-            final SimpleHttpResponse response = new SimpleHttpResponse(HttpStatus.SC_TEMPORARY_REDIRECT);
-            response.addHeader(new BasicHeader("Location", location));
-            return response;
-        }
-    }
-
-    @Test
-    public void testCrossSiteRedirect() throws Exception {
-        server.register("/random/*", new Supplier<AsyncServerExchangeHandler>() {
-
-            @Override
-            public AsyncServerExchangeHandler get() {
-                return new AsyncRandomHandler();
-            }
-
-        });
-        final HttpHost redirectTarget = start();
-
-        final Http2TestServer secondServer = new Http2TestServer(IOReactorConfig.DEFAULT,
-                scheme == URIScheme.HTTPS ? SSLTestContexts.createServerSSLContext() : null);
-        try {
-            secondServer.register("/redirect/*", new Supplier<AsyncServerExchangeHandler>() {
-
-                @Override
-                public AsyncServerExchangeHandler get() {
-                    return new CrossSiteRedirectService(redirectTarget);
-                }
-
-            });
-
-            secondServer.start();
-            final Future<ListenerEndpoint> endpointFuture = secondServer.listen(new InetSocketAddress(0));
-            final ListenerEndpoint endpoint2 = endpointFuture.get();
-
-            final InetSocketAddress address2 = (InetSocketAddress) endpoint2.getAddress();
-            final HttpHost initialTarget = new HttpHost("localhost", address2.getPort(), scheme.name());
-
-            final Queue<Future<SimpleHttpResponse>> queue = new ConcurrentLinkedQueue<>();
-            for (int i = 0; i < 1; i++) {
-                queue.add(httpclient.execute(SimpleHttpRequest.get(initialTarget, "/redirect/anywhere"), null));
-            }
-            while (!queue.isEmpty()) {
-                final Future<SimpleHttpResponse> future = queue.remove();
-                final HttpResponse response = future.get();
-                Assert.assertNotNull(response);
-                Assert.assertEquals(200, response.getCode());
-            }
-        } finally {
-            server.shutdown(TimeValue.ofSeconds(5));
-        }
-    }
-
-    private static class RomeRedirectService extends AbstractSimpleServerExchangeHandler {
-
-        @Override
-        protected SimpleHttpResponse handle(
-                final SimpleHttpRequest request, final HttpCoreContext context) throws HttpException {
-            try {
-                final URI requestURI = request.getUri();
-                final String path = requestURI.getPath();
-                if (path.equals("/rome")) {
-                    final SimpleHttpResponse response = new SimpleHttpResponse(HttpStatus.SC_OK);
-                    response.setBodyText("Successful redirect", ContentType.TEXT_PLAIN);
-                    return response;
-                } else {
-                    final SimpleHttpResponse response = new SimpleHttpResponse(HttpStatus.SC_MOVED_TEMPORARILY);
-                    response.addHeader(new BasicHeader("Location", "/rome"));
-                    return response;
-                }
-            } catch (final URISyntaxException ex) {
-                throw new ProtocolException(ex.getMessage(), ex);
-            }
-        }
-
-    }
-
-    @Test
-    public void testRepeatRequest() throws Exception {
-        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
-
-            @Override
-            public AsyncServerExchangeHandler get() {
-                return new RomeRedirectService();
-            }
-
-        });
-        final HttpHost target = start();
-
-        final HttpClientContext context = HttpClientContext.create();
-
-        final Future<SimpleHttpResponse> future1 = httpclient.execute(
-                SimpleHttpRequest.get(target, "/rome"), context, null);
-        final HttpResponse response1 = future1.get();
-        Assert.assertNotNull(response1);
-
-        final Future<SimpleHttpResponse> future2 = httpclient.execute(
-                SimpleHttpRequest.get(target, "/rome"), context, null);
-        final HttpResponse response2 = future2.get();
-        Assert.assertNotNull(response2);
-
-        final HttpRequest request = context.getRequest();
-
-        Assert.assertEquals(HttpStatus.SC_OK, response2.getCode());
-        Assert.assertEquals("/rome", request.getRequestUri());
-        Assert.assertEquals(target, new HttpHost(request.getAuthority(), request.getScheme()));
-    }
-
-    @Test
-    public void testRepeatRequestRedirect() throws Exception {
-        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
-
-            @Override
-            public AsyncServerExchangeHandler get() {
-                return new RomeRedirectService();
-            }
-
-        });
-        final HttpHost target = start();
-
-        final HttpClientContext context = HttpClientContext.create();
-
-        final Future<SimpleHttpResponse> future1 = httpclient.execute(
-                SimpleHttpRequest.get(target, "/lille"), context, null);
-        final HttpResponse response1 = future1.get();
-        Assert.assertNotNull(response1);
-
-        final Future<SimpleHttpResponse> future2 = httpclient.execute(
-                SimpleHttpRequest.get(target, "/lille"), context, null);
-        final HttpResponse response2 = future2.get();
-        Assert.assertNotNull(response2);
-
-        final HttpRequest request = context.getRequest();
-
-        Assert.assertEquals(HttpStatus.SC_OK, response2.getCode());
-        Assert.assertEquals("/rome", request.getRequestUri());
-        Assert.assertEquals(target, new HttpHost(request.getAuthority(), request.getScheme()));
-    }
-
-    @Test
-    public void testDifferentRequestSameRedirect() throws Exception {
-        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
-
-            @Override
-            public AsyncServerExchangeHandler get() {
-                return new RomeRedirectService();
-            }
-
-        });
-        final HttpHost target = start();
-
-        final HttpClientContext context = HttpClientContext.create();
-
-        final Future<SimpleHttpResponse> future1 = httpclient.execute(
-                SimpleHttpRequest.get(target, "/alian"), context, null);
-        final HttpResponse response1 = future1.get();
-        Assert.assertNotNull(response1);
-
-        final Future<SimpleHttpResponse> future2 = httpclient.execute(
-                SimpleHttpRequest.get(target, "/lille"), context, null);
-        final HttpResponse response2 = future2.get();
-        Assert.assertNotNull(response2);
-
-
-        final HttpRequest request = context.getRequest();
-
-        Assert.assertEquals(HttpStatus.SC_OK, response2.getCode());
-        Assert.assertEquals("/rome", request.getRequestUri());
-        Assert.assertEquals(target, new HttpHost(request.getAuthority(), request.getScheme()));
-    }
-
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/6228a736/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestAsyncStatefulConnManagement.java
----------------------------------------------------------------------
diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestAsyncStatefulConnManagement.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestAsyncStatefulConnManagement.java
deleted file mode 100644
index bbede25..0000000
--- a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestAsyncStatefulConnManagement.java
+++ /dev/null
@@ -1,261 +0,0 @@
-/*
- * ====================================================================
- * 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.testing.async;
-
-import java.util.concurrent.Future;
-
-import org.apache.hc.client5.http.HttpRoute;
-import org.apache.hc.client5.http.UserTokenHandler;
-import org.apache.hc.client5.http.async.methods.SimpleHttpRequest;
-import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
-import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
-import org.apache.hc.client5.http.protocol.HttpClientContext;
-import org.apache.hc.core5.function.Supplier;
-import org.apache.hc.core5.http.ContentType;
-import org.apache.hc.core5.http.EndpointDetails;
-import org.apache.hc.core5.http.HttpException;
-import org.apache.hc.core5.http.HttpHost;
-import org.apache.hc.core5.http.HttpResponse;
-import org.apache.hc.core5.http.HttpStatus;
-import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler;
-import org.apache.hc.core5.http.protocol.BasicHttpContext;
-import org.apache.hc.core5.http.protocol.HttpContext;
-import org.apache.hc.core5.http.protocol.HttpCoreContext;
-import org.junit.Assert;
-import org.junit.Test;
-
-public class TestAsyncStatefulConnManagement extends IntegrationTestBase {
-
-    @Test
-    public void testStatefulConnections() throws Exception {
-        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
-
-            @Override
-            public AsyncServerExchangeHandler get() {
-                return new AbstractSimpleServerExchangeHandler() {
-
-                    @Override
-                    protected SimpleHttpResponse handle(
-                            final SimpleHttpRequest request,
-                            final HttpCoreContext context) throws HttpException {
-                        final SimpleHttpResponse response = new SimpleHttpResponse(HttpStatus.SC_OK);
-                        response.setBodyText("Whatever", ContentType.TEXT_PLAIN);
-                        return response;
-                    }
-                };
-            }
-
-        });
-
-        final UserTokenHandler userTokenHandler = new UserTokenHandler() {
-
-            @Override
-            public Object getUserToken(final HttpRoute route, final HttpContext context) {
-                return context.getAttribute("user");
-            }
-
-        };
-        clientBuilder.setUserTokenHandler(userTokenHandler);
-        final HttpHost target = start();
-
-        final int workerCount = 2;
-        final int requestCount = 5;
-
-        final HttpContext[] contexts = new HttpContext[workerCount];
-        final HttpWorker[] workers = new HttpWorker[workerCount];
-        for (int i = 0; i < contexts.length; i++) {
-            final HttpClientContext context = HttpClientContext.create();
-            contexts[i] = context;
-            workers[i] = new HttpWorker(
-                    "user" + i,
-                    context, requestCount, target, httpclient);
-        }
-
-        for (final HttpWorker worker : workers) {
-            worker.start();
-        }
-        for (final HttpWorker worker : workers) {
-            worker.join(LONG_TIMEOUT.toMillis());
-        }
-        for (final HttpWorker worker : workers) {
-            final Exception ex = worker.getException();
-            if (ex != null) {
-                throw ex;
-            }
-            Assert.assertEquals(requestCount, worker.getCount());
-        }
-
-        for (final HttpContext context : contexts) {
-            final String state0 = (String) context.getAttribute("r0");
-            Assert.assertNotNull(state0);
-            for (int r = 1; r < requestCount; r++) {
-                Assert.assertEquals(state0, context.getAttribute("r" + r));
-            }
-        }
-
-    }
-
-    static class HttpWorker extends Thread {
-
-        private final String uid;
-        private final HttpClientContext context;
-        private final int requestCount;
-        private final HttpHost target;
-        private final CloseableHttpAsyncClient httpclient;
-
-        private volatile Exception exception;
-        private volatile int count;
-
-        public HttpWorker(
-                final String uid,
-                final HttpClientContext context,
-                final int requestCount,
-                final HttpHost target,
-                final CloseableHttpAsyncClient httpclient) {
-            super();
-            this.uid = uid;
-            this.context = context;
-            this.requestCount = requestCount;
-            this.target = target;
-            this.httpclient = httpclient;
-            this.count = 0;
-        }
-
-        public int getCount() {
-            return count;
-        }
-
-        public Exception getException() {
-            return exception;
-        }
-
-        @Override
-        public void run() {
-            try {
-                context.setAttribute("user", uid);
-                for (int r = 0; r < requestCount; r++) {
-                    final SimpleHttpRequest httpget = SimpleHttpRequest.get(target, "/");
-                    final Future<SimpleHttpResponse> future = httpclient.execute(httpget, null);
-                    future.get();
-
-                    count++;
-                    final EndpointDetails endpointDetails = context.getEndpointDetails();
-                    final String connuid = Integer.toHexString(System.identityHashCode(endpointDetails));
-                    context.setAttribute("r" + r, connuid);
-                }
-
-            } catch (final Exception ex) {
-                exception = ex;
-            }
-        }
-
-    }
-
-    @Test
-    public void testRouteSpecificPoolRecylcing() throws Exception {
-        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
-
-            @Override
-            public AsyncServerExchangeHandler get() {
-                return new AbstractSimpleServerExchangeHandler() {
-
-                    @Override
-                    protected SimpleHttpResponse handle(
-                            final SimpleHttpRequest request,
-                            final HttpCoreContext context) throws HttpException {
-                        final SimpleHttpResponse response = new SimpleHttpResponse(HttpStatus.SC_OK);
-                        response.setBodyText("Whatever", ContentType.TEXT_PLAIN);
-                        return response;
-                    }
-                };
-            }
-
-        });
-
-        // This tests what happens when a maxed connection pool needs
-        // to kill the last idle connection to a route to build a new
-        // one to the same route.
-        final UserTokenHandler userTokenHandler = new UserTokenHandler() {
-
-            @Override
-            public Object getUserToken(final HttpRoute route, final HttpContext context) {
-                return context.getAttribute("user");
-            }
-
-        };
-        clientBuilder.setUserTokenHandler(userTokenHandler);
-
-        final HttpHost target = start();
-        final int maxConn = 2;
-        // We build a client with 2 max active // connections, and 2 max per route.
-        connManager.setMaxTotal(maxConn);
-        connManager.setDefaultMaxPerRoute(maxConn);
-
-        // Bottom of the pool : a *keep alive* connection to Route 1.
-        final HttpContext context1 = new BasicHttpContext();
-        context1.setAttribute("user", "stuff");
-
-        final Future<SimpleHttpResponse> future1 = httpclient.execute(SimpleHttpRequest.get(target, "/"), context1, null);
-        final HttpResponse response1 = future1.get();
-        Assert.assertNotNull(response1);
-        Assert.assertEquals(200, response1.getCode());
-
-        // The ConnPoolByRoute now has 1 free connection, out of 2 max
-        // The ConnPoolByRoute has one RouteSpcfcPool, that has one free connection
-        // for [localhost][stuff]
-
-        Thread.sleep(100);
-
-        // Send a very simple HTTP get (it MUST be simple, no auth, no proxy, no 302, no 401, ...)
-        // Send it to another route. Must be a keepalive.
-        final HttpContext context2 = new BasicHttpContext();
-
-        final Future<SimpleHttpResponse> future2 = httpclient.execute(SimpleHttpRequest.get(
-                new HttpHost("127.0.0.1", target.getPort(), target.getSchemeName()),"/"), context2, null);
-        final HttpResponse response2 = future2.get();
-        Assert.assertNotNull(response2);
-        Assert.assertEquals(200, response2.getCode());
-
-        // ConnPoolByRoute now has 2 free connexions, out of its 2 max.
-        // The [localhost][stuff] RouteSpcfcPool is the same as earlier
-        // And there is a [127.0.0.1][null] pool with 1 free connection
-
-        Thread.sleep(100);
-
-        // This will put the ConnPoolByRoute to the targeted state :
-        // [localhost][stuff] will not get reused because this call is [localhost][null]
-        // So the ConnPoolByRoute will need to kill one connection (it is maxed out globally).
-        // The killed conn is the oldest, which means the first HTTPGet ([localhost][stuff]).
-        // When this happens, the RouteSpecificPool becomes empty.
-        final HttpContext context3 = new BasicHttpContext();
-        final Future<SimpleHttpResponse> future3 = httpclient.execute(SimpleHttpRequest.get(target, "/"), context3, null);
-        final HttpResponse response3 = future3.get();
-        Assert.assertNotNull(response3);
-        Assert.assertEquals(200, response3.getCode());
-    }
-
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/6228a736/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestClientAuthentication.java
----------------------------------------------------------------------
diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestClientAuthentication.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestClientAuthentication.java
deleted file mode 100644
index 7841420..0000000
--- a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestClientAuthentication.java
+++ /dev/null
@@ -1,598 +0,0 @@
-/*
- * ====================================================================
- * 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.testing.async;
-
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.Future;
-import java.util.concurrent.atomic.AtomicLong;
-
-import org.apache.hc.client5.http.async.methods.SimpleHttpRequest;
-import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
-import org.apache.hc.client5.http.auth.AuthChallenge;
-import org.apache.hc.client5.http.auth.AuthScheme;
-import org.apache.hc.client5.http.auth.AuthSchemeProvider;
-import org.apache.hc.client5.http.auth.AuthScope;
-import org.apache.hc.client5.http.auth.ChallengeType;
-import org.apache.hc.client5.http.auth.Credentials;
-import org.apache.hc.client5.http.auth.CredentialsStore;
-import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;
-import org.apache.hc.client5.http.config.RequestConfig;
-import org.apache.hc.client5.http.impl.DefaultAuthenticationStrategy;
-import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider;
-import org.apache.hc.client5.http.impl.auth.BasicScheme;
-import org.apache.hc.client5.http.protocol.HttpClientContext;
-import org.apache.hc.client5.testing.BasicTestAuthenticator;
-import org.apache.hc.client5.testing.auth.Authenticator;
-import org.apache.hc.core5.function.Decorator;
-import org.apache.hc.core5.function.Supplier;
-import org.apache.hc.core5.http.ContentType;
-import org.apache.hc.core5.http.HeaderElements;
-import org.apache.hc.core5.http.HttpException;
-import org.apache.hc.core5.http.HttpHeaders;
-import org.apache.hc.core5.http.HttpHost;
-import org.apache.hc.core5.http.HttpResponse;
-import org.apache.hc.core5.http.HttpStatus;
-import org.apache.hc.core5.http.URIScheme;
-import org.apache.hc.core5.http.config.H1Config;
-import org.apache.hc.core5.http.config.Registry;
-import org.apache.hc.core5.http.config.RegistryBuilder;
-import org.apache.hc.core5.http.impl.HttpProcessors;
-import org.apache.hc.core5.http.message.BasicHeader;
-import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler;
-import org.apache.hc.core5.http.protocol.HttpContext;
-import org.apache.hc.core5.http.protocol.HttpCoreContext;
-import org.apache.hc.core5.net.URIAuthority;
-import org.junit.Assert;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-
-@RunWith(Parameterized.class)
-public class TestClientAuthentication extends IntegrationTestBase {
-
-    @Parameterized.Parameters(name = "{0}")
-    public static Collection<Object[]> protocols() {
-        return Arrays.asList(new Object[][]{
-                {URIScheme.HTTP},
-                {URIScheme.HTTPS},
-        });
-    }
-
-    public TestClientAuthentication(final URIScheme scheme) {
-        super(scheme);
-    }
-
-    @Override
-    public HttpHost start() throws Exception {
-        return super.start(
-                HttpProcessors.server(),
-                new Decorator<AsyncServerExchangeHandler>() {
-
-                    @Override
-                    public AsyncServerExchangeHandler decorate(final AsyncServerExchangeHandler requestHandler) {
-                        return new AuthenticatingAsyncDecorator(requestHandler, new BasicTestAuthenticator("test:test", "test realm"));
-                    }
-
-                },
-                H1Config.DEFAULT);
-    }
-
-    static class TestCredentialsProvider implements CredentialsStore {
-
-        private final Credentials creds;
-        private AuthScope authscope;
-
-        TestCredentialsProvider(final Credentials creds) {
-            super();
-            this.creds = creds;
-        }
-
-        @Override
-        public void clear() {
-        }
-
-        @Override
-        public Credentials getCredentials(final AuthScope authscope, final HttpContext context) {
-            this.authscope = authscope;
-            return this.creds;
-        }
-
-        @Override
-        public void setCredentials(final AuthScope authscope, final Credentials credentials) {
-        }
-
-        public AuthScope getAuthScope() {
-            return this.authscope;
-        }
-
-    }
-
-    @Test
-    public void testBasicAuthenticationNoCreds() throws Exception {
-        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
-
-            @Override
-            public AsyncServerExchangeHandler get() {
-                return new AsyncEchoHandler();
-            }
-
-        });
-        final HttpHost target = start();
-
-        final TestCredentialsProvider credsProvider = new TestCredentialsProvider(null);
-        final HttpClientContext context = HttpClientContext.create();
-        context.setCredentialsProvider(credsProvider);
-
-        final Future<SimpleHttpResponse> future = httpclient.execute(SimpleHttpRequest.get(target, "/"), context, null);
-        final HttpResponse response = future.get();
-
-        Assert.assertNotNull(response);
-        Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, response.getCode());
-        final AuthScope authscope = credsProvider.getAuthScope();
-        Assert.assertNotNull(authscope);
-        Assert.assertEquals("test realm", authscope.getRealm());
-    }
-
-    @Test
-    public void testBasicAuthenticationFailure() throws Exception {
-        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
-
-            @Override
-            public AsyncServerExchangeHandler get() {
-                return new AsyncEchoHandler();
-            }
-
-        });
-        final HttpHost target = start();
-
-        final TestCredentialsProvider credsProvider = new TestCredentialsProvider(
-                new UsernamePasswordCredentials("test", "all-wrong".toCharArray()));
-        final HttpClientContext context = HttpClientContext.create();
-        context.setCredentialsProvider(credsProvider);
-
-        final Future<SimpleHttpResponse> future = httpclient.execute(SimpleHttpRequest.get(target, "/"), context, null);
-        final HttpResponse response = future.get();
-
-        Assert.assertNotNull(response);
-        Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, response.getCode());
-        final AuthScope authscope = credsProvider.getAuthScope();
-        Assert.assertNotNull(authscope);
-        Assert.assertEquals("test realm", authscope.getRealm());
-    }
-
-    @Test
-    public void testBasicAuthenticationSuccess() throws Exception {
-        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
-
-            @Override
-            public AsyncServerExchangeHandler get() {
-                return new AsyncEchoHandler();
-            }
-
-        });
-        final HttpHost target = start();
-
-        final TestCredentialsProvider credsProvider = new TestCredentialsProvider(
-                new UsernamePasswordCredentials("test", "test".toCharArray()));
-        final HttpClientContext context = HttpClientContext.create();
-        context.setCredentialsProvider(credsProvider);
-
-        final Future<SimpleHttpResponse> future = httpclient.execute(SimpleHttpRequest.get(target, "/"), context, null);
-        final HttpResponse response = future.get();
-
-        Assert.assertNotNull(response);
-        Assert.assertEquals(HttpStatus.SC_OK, response.getCode());
-        final AuthScope authscope = credsProvider.getAuthScope();
-        Assert.assertNotNull(authscope);
-        Assert.assertEquals("test realm", authscope.getRealm());
-    }
-
-    @Test
-    public void testBasicAuthenticationWithEntitySuccess() throws Exception {
-        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
-
-            @Override
-            public AsyncServerExchangeHandler get() {
-                return new AsyncEchoHandler();
-            }
-
-        });
-        final HttpHost target = start();
-
-        final TestCredentialsProvider credsProvider = new TestCredentialsProvider(
-                new UsernamePasswordCredentials("test", "test".toCharArray()));
-        final HttpClientContext context = HttpClientContext.create();
-        context.setCredentialsProvider(credsProvider);
-
-        final SimpleHttpRequest put = SimpleHttpRequest.put(target, "/");
-        put.setBodyText("Some important stuff", ContentType.TEXT_PLAIN);
-        final Future<SimpleHttpResponse> future = httpclient.execute(put, context, null);
-        final HttpResponse response = future.get();
-
-        Assert.assertNotNull(response);
-        Assert.assertEquals(HttpStatus.SC_OK, response.getCode());
-        final AuthScope authscope = credsProvider.getAuthScope();
-        Assert.assertNotNull(authscope);
-        Assert.assertEquals("test realm", authscope.getRealm());
-    }
-
-    @Test
-    public void testBasicAuthenticationSuccessNonPersistentConnection() throws Exception {
-        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
-
-            @Override
-            public AsyncServerExchangeHandler get() {
-                return new AsyncEchoHandler();
-            }
-
-        });
-        final HttpHost target = start(
-                HttpProcessors.server(),
-                new Decorator<AsyncServerExchangeHandler>() {
-
-                    @Override
-                    public AsyncServerExchangeHandler decorate(final AsyncServerExchangeHandler exchangeHandler) {
-                        return new AuthenticatingAsyncDecorator(exchangeHandler, new BasicTestAuthenticator("test:test", "test realm")) {
-
-                            @Override
-                            protected void customizeUnauthorizedResponse(final HttpResponse unauthorized) {
-                                unauthorized.addHeader(HttpHeaders.CONNECTION, HeaderElements.CLOSE);
-                            }
-                        };
-                    }
-
-                },
-                H1Config.DEFAULT);
-
-        final TestCredentialsProvider credsProvider = new TestCredentialsProvider(
-                new UsernamePasswordCredentials("test", "test".toCharArray()));
-        final HttpClientContext context = HttpClientContext.create();
-        context.setCredentialsProvider(credsProvider);
-
-        final Future<SimpleHttpResponse> future = httpclient.execute(SimpleHttpRequest.get(target, "/"), context, null);
-        final HttpResponse response = future.get();
-
-        Assert.assertNotNull(response);
-        Assert.assertEquals(HttpStatus.SC_OK, response.getCode());
-        final AuthScope authscope = credsProvider.getAuthScope();
-        Assert.assertNotNull(authscope);
-        Assert.assertEquals("test realm", authscope.getRealm());
-    }
-
-    @Test
-    public void testBasicAuthenticationExpectationFailure() throws Exception {
-        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
-
-            @Override
-            public AsyncServerExchangeHandler get() {
-                return new AsyncEchoHandler();
-            }
-
-        });
-        final HttpHost target = start();
-
-        final TestCredentialsProvider credsProvider = new TestCredentialsProvider(
-                new UsernamePasswordCredentials("test", "all-wrong".toCharArray()));
-        final HttpClientContext context = HttpClientContext.create();
-        context.setCredentialsProvider(credsProvider);
-        context.setRequestConfig(RequestConfig.custom().setExpectContinueEnabled(true).build());
-
-        final SimpleHttpRequest put = SimpleHttpRequest.put(target, "/");
-        put.setBodyText("Some important stuff", ContentType.TEXT_PLAIN);
-        final Future<SimpleHttpResponse> future = httpclient.execute(put, context, null);
-        final HttpResponse response = future.get();
-
-        Assert.assertNotNull(response);
-        Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, response.getCode());
-    }
-
-    @Test
-    public void testBasicAuthenticationExpectationSuccess() throws Exception {
-        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
-
-            @Override
-            public AsyncServerExchangeHandler get() {
-                return new AsyncEchoHandler();
-            }
-
-        });
-        final HttpHost target = start();
-
-        final TestCredentialsProvider credsProvider = new TestCredentialsProvider(
-                new UsernamePasswordCredentials("test", "test".toCharArray()));
-        final HttpClientContext context = HttpClientContext.create();
-        context.setCredentialsProvider(credsProvider);
-        context.setRequestConfig(RequestConfig.custom().setExpectContinueEnabled(true).build());
-
-        final SimpleHttpRequest put = SimpleHttpRequest.put(target, "/");
-        put.setBodyText("Some important stuff", ContentType.TEXT_PLAIN);
-        final Future<SimpleHttpResponse> future = httpclient.execute(put, context, null);
-        final HttpResponse response = future.get();
-
-        Assert.assertNotNull(response);
-        Assert.assertEquals(HttpStatus.SC_OK, response.getCode());
-        final AuthScope authscope = credsProvider.getAuthScope();
-        Assert.assertNotNull(authscope);
-        Assert.assertEquals("test realm", authscope.getRealm());
-    }
-
-    @Test
-    public void testBasicAuthenticationCredentialsCaching() throws Exception {
-        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
-
-            @Override
-            public AsyncServerExchangeHandler get() {
-                return new AsyncEchoHandler();
-            }
-
-        });
-
-        final AtomicLong count = new AtomicLong(0);
-        this.clientBuilder.setTargetAuthenticationStrategy(new DefaultAuthenticationStrategy() {
-
-            @Override
-            public List<AuthScheme> select(
-                    final ChallengeType challengeType,
-                    final Map<String, AuthChallenge> challenges,
-                    final HttpContext context) {
-                count.incrementAndGet();
-                return super.select(challengeType, challenges, context);
-            }
-        });
-        final HttpHost target = start();
-
-        final BasicCredentialsProvider credsProvider = new BasicCredentialsProvider();
-        credsProvider.setCredentials(AuthScope.ANY,
-                new UsernamePasswordCredentials("test", "test".toCharArray()));
-        final HttpClientContext context = HttpClientContext.create();
-        context.setCredentialsProvider(credsProvider);
-
-        final Future<SimpleHttpResponse> future1 = httpclient.execute(SimpleHttpRequest.get(target, "/"), context, null);
-        final HttpResponse response1 = future1.get();
-        Assert.assertNotNull(response1);
-        Assert.assertEquals(HttpStatus.SC_OK, response1.getCode());
-
-        final Future<SimpleHttpResponse> future2 = httpclient.execute(SimpleHttpRequest.get(target, "/"), context, null);
-        final HttpResponse response2 = future2.get();
-        Assert.assertNotNull(response2);
-        Assert.assertEquals(HttpStatus.SC_OK, response2.getCode());
-
-        Assert.assertEquals(1, count.get());
-    }
-
-    @Test
-    public void testAuthenticationUserinfoInRequestSuccess() throws Exception {
-        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
-
-            @Override
-            public AsyncServerExchangeHandler get() {
-                return new AsyncEchoHandler();
-            }
-
-        });
-        final HttpHost target = start();
-
-        final HttpClientContext context = HttpClientContext.create();
-        final Future<SimpleHttpResponse> future = httpclient.execute(
-                SimpleHttpRequest.get(target.getSchemeName() + "://test:test@" +  target.toHostString() + "/"), context, null);
-        final SimpleHttpResponse response = future.get();
-
-        Assert.assertNotNull(response);
-        Assert.assertEquals(HttpStatus.SC_OK, response.getCode());
-    }
-
-    @Test
-    public void testAuthenticationUserinfoInRequestFailure() throws Exception {
-        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
-
-            @Override
-            public AsyncServerExchangeHandler get() {
-                return new AsyncEchoHandler();
-            }
-
-        });
-        final HttpHost target = start();
-
-        final HttpClientContext context = HttpClientContext.create();
-        final Future<SimpleHttpResponse> future = httpclient.execute(
-                SimpleHttpRequest.get(target.getSchemeName() + "://test:all-worng@" +  target.toHostString() + "/"), context, null);
-        final SimpleHttpResponse response = future.get();
-
-        Assert.assertNotNull(response);
-        Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, response.getCode());
-    }
-
-    @Test
-    public void testAuthenticationUserinfoInRedirectSuccess() throws Exception {
-        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
-
-            @Override
-            public AsyncServerExchangeHandler get() {
-                return new AsyncEchoHandler();
-            }
-
-        });
-        final HttpHost target = start();
-        server.register("/thatway", new Supplier<AsyncServerExchangeHandler>() {
-
-            @Override
-            public AsyncServerExchangeHandler get() {
-                return new AbstractSimpleServerExchangeHandler() {
-
-                    @Override
-                    protected SimpleHttpResponse handle(
-                            final SimpleHttpRequest request, final HttpCoreContext context) throws HttpException {
-                        final SimpleHttpResponse response = new SimpleHttpResponse(HttpStatus.SC_MOVED_PERMANENTLY);
-                        response.addHeader(new BasicHeader("Location", target.getSchemeName() + "://test:test@" + target.toHostString() + "/"));
-                        return response;
-                    }
-                };
-            }
-
-        });
-
-        final HttpClientContext context = HttpClientContext.create();
-        final Future<SimpleHttpResponse> future = httpclient.execute(
-                SimpleHttpRequest.get(target.getSchemeName() + "://test:test@" +  target.toHostString() + "/thatway"), context, null);
-        final SimpleHttpResponse response = future.get();
-
-        Assert.assertNotNull(response);
-        Assert.assertEquals(HttpStatus.SC_OK, response.getCode());
-    }
-
-    @Test
-    public void testReauthentication() throws Exception {
-        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
-
-            @Override
-            public AsyncServerExchangeHandler get() {
-                return new AsyncEchoHandler();
-            }
-
-        });
-        final TestCredentialsProvider credsProvider = new TestCredentialsProvider(
-                new UsernamePasswordCredentials("test", "test".toCharArray()));
-
-        final Registry<AuthSchemeProvider> authSchemeRegistry = RegistryBuilder.<AuthSchemeProvider>create()
-                .register("MyBasic", new AuthSchemeProvider() {
-
-                    @Override
-                    public AuthScheme create(final HttpContext context) {
-                        return new BasicScheme() {
-
-                            @Override
-                            public String getName() {
-                                return "MyBasic";
-                            }
-
-                        };
-                    }
-
-                })
-                .build();
-        this.clientBuilder.setDefaultAuthSchemeRegistry(authSchemeRegistry);
-
-        final Authenticator authenticator = new BasicTestAuthenticator("test:test", "test realm") {
-
-            private final AtomicLong count = new AtomicLong(0);
-
-            @Override
-            public boolean authenticate(final URIAuthority authority, final String requestUri, final String credentials) {
-                final boolean authenticated = super.authenticate(authority, requestUri, credentials);
-                if (authenticated) {
-                    if (this.count.incrementAndGet() % 4 != 0) {
-                        return true;
-                    } else {
-                        return false;
-                    }
-                } else {
-                    return false;
-                }
-            }
-        };
-
-        final HttpHost target = start(
-                HttpProcessors.server(),
-                new Decorator<AsyncServerExchangeHandler>() {
-
-                    @Override
-                    public AsyncServerExchangeHandler decorate(final AsyncServerExchangeHandler exchangeHandler) {
-                        return new AuthenticatingAsyncDecorator(exchangeHandler, authenticator) {
-
-                            @Override
-                            protected void customizeUnauthorizedResponse(final HttpResponse unauthorized) {
-                                unauthorized.removeHeaders(HttpHeaders.WWW_AUTHENTICATE);
-                                unauthorized.addHeader(HttpHeaders.WWW_AUTHENTICATE, "MyBasic realm=\"test realm\"");
-                            }
-
-                        };
-                    }
-
-                }, H1Config.DEFAULT);
-
-        final RequestConfig config = RequestConfig.custom()
-                .setTargetPreferredAuthSchemes(Arrays.asList("MyBasic"))
-                .build();
-        final HttpClientContext context = HttpClientContext.create();
-        context.setCredentialsProvider(credsProvider);
-
-        for (int i = 0; i < 10; i++) {
-            final SimpleHttpRequest request = SimpleHttpRequest.get(target, "/");
-            request.setConfig(config);
-            final Future<SimpleHttpResponse> future = httpclient.execute(request, context, null);
-            final SimpleHttpResponse response = future.get();
-            Assert.assertNotNull(response);
-            Assert.assertEquals(HttpStatus.SC_OK, response.getCode());
-        }
-    }
-
-    @Test
-    public void testAuthenticationFallback() throws Exception {
-        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
-
-            @Override
-            public AsyncServerExchangeHandler get() {
-                return new AsyncEchoHandler();
-            }
-
-        });
-        final HttpHost target = start(
-                HttpProcessors.server(),
-                new Decorator<AsyncServerExchangeHandler>() {
-
-                    @Override
-                    public AsyncServerExchangeHandler decorate(final AsyncServerExchangeHandler exchangeHandler) {
-                        return new AuthenticatingAsyncDecorator(exchangeHandler, new BasicTestAuthenticator("test:test", "test realm")) {
-
-                            @Override
-                            protected void customizeUnauthorizedResponse(final HttpResponse unauthorized) {
-                                unauthorized.addHeader(HttpHeaders.WWW_AUTHENTICATE, "Digest realm=\"test realm\" invalid");
-                            }
-
-                        };
-                    }
-
-                }, H1Config.DEFAULT);
-
-        final TestCredentialsProvider credsProvider = new TestCredentialsProvider(
-                new UsernamePasswordCredentials("test", "test".toCharArray()));
-        final HttpClientContext context = HttpClientContext.create();
-        context.setCredentialsProvider(credsProvider);
-
-        final Future<SimpleHttpResponse> future = httpclient.execute(SimpleHttpRequest.get(target, "/"), context, null);
-        final SimpleHttpResponse response = future.get();
-        Assert.assertNotNull(response);
-        Assert.assertEquals(HttpStatus.SC_OK, response.getCode());
-        final AuthScope authscope = credsProvider.getAuthScope();
-        Assert.assertNotNull(authscope);
-        Assert.assertEquals("test realm", authscope.getRealm());
-    }
-
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/6228a736/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp1Async.java
----------------------------------------------------------------------
diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp1Async.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp1Async.java
new file mode 100644
index 0000000..506001f
--- /dev/null
+++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp1Async.java
@@ -0,0 +1,198 @@
+/*
+ * ====================================================================
+ * 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.testing.async;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.concurrent.Future;
+
+import org.apache.hc.client5.http.async.methods.SimpleHttpRequest;
+import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
+import org.apache.hc.client5.http.config.RequestConfig;
+import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
+import org.apache.hc.client5.http.impl.async.HttpAsyncClientBuilder;
+import org.apache.hc.client5.http.impl.async.HttpAsyncClients;
+import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManager;
+import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBuilder;
+import org.apache.hc.client5.http.ssl.H2TlsStrategy;
+import org.apache.hc.client5.testing.SSLTestContexts;
+import org.apache.hc.core5.http.HeaderElements;
+import org.apache.hc.core5.http.HttpHeaders;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.URIScheme;
+import org.apache.hc.core5.http.config.H1Config;
+import org.hamcrest.CoreMatchers;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExternalResource;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class TestHttp1Async extends AbstractHttpAsyncFundamentalsTest<CloseableHttpAsyncClient> {
+
+    @Parameterized.Parameters(name = "HTTP/1.1 {0}")
+    public static Collection<Object[]> protocols() {
+        return Arrays.asList(new Object[][]{
+                { URIScheme.HTTP },
+                { URIScheme.HTTPS },
+        });
+    }
+
+    protected HttpAsyncClientBuilder clientBuilder;
+    protected PoolingAsyncClientConnectionManager connManager;
+
+    @Rule
+    public ExternalResource connManagerResource = new ExternalResource() {
+
+        @Override
+        protected void before() throws Throwable {
+            connManager = PoolingAsyncClientConnectionManagerBuilder.create()
+                    .setTlsStrategy(new H2TlsStrategy(SSLTestContexts.createClientSSLContext()))
+                    .build();
+        }
+
+        @Override
+        protected void after() {
+            if (connManager != null) {
+                connManager.close();
+                connManager = null;
+            }
+        }
+
+    };
+
+    @Rule
+    public ExternalResource clientResource = new ExternalResource() {
+
+        @Override
+        protected void before() throws Throwable {
+            clientBuilder = HttpAsyncClientBuilder.create()
+                    .setDefaultRequestConfig(RequestConfig.custom()
+                            .setSocketTimeout(TIMEOUT)
+                            .setConnectTimeout(TIMEOUT)
+                            .setConnectionRequestTimeout(TIMEOUT)
+                            .build())
+                    .setConnectionManager(connManager);
+        }
+
+    };
+
+    public TestHttp1Async(final URIScheme scheme) {
+        super(scheme);
+    }
+
+    @Override
+    protected CloseableHttpAsyncClient createClient() {
+        return clientBuilder.build();
+    }
+
+    @Override
+    public HttpHost start() throws Exception {
+        return super.start(null, H1Config.DEFAULT);
+    }
+
+    @Test
+    public void testSequenctialGetRequestsCloseConnection() throws Exception {
+        final HttpHost target = start();
+        for (int i = 0; i < 3; i++) {
+            final SimpleHttpRequest get = SimpleHttpRequest.get(target, "/random/2048");
+            get.setHeader(HttpHeaders.CONNECTION, HeaderElements.CLOSE);
+            final Future<SimpleHttpResponse> future = httpclient.execute(get, null);
+            final SimpleHttpResponse response = future.get();
+            Assert.assertThat(response, CoreMatchers.notNullValue());
+            Assert.assertThat(response.getCode(), CoreMatchers.equalTo(200));
+            final String body = response.getBodyText();
+            Assert.assertThat(body, CoreMatchers.notNullValue());
+            Assert.assertThat(body.length(), CoreMatchers.equalTo(2048));
+        }
+    }
+
+    @Test
+    public void testConcurrentPostsOverMultipleConnections() throws Exception {
+        connManager.setDefaultMaxPerRoute(20);
+        connManager.setMaxTotal(100);
+        super.testConcurrentPostRequests();
+    }
+
+    @Test
+    public void testConcurrentPostsOverSingleConnection() throws Exception {
+        connManager.setDefaultMaxPerRoute(1);
+        connManager.setMaxTotal(100);
+        super.testConcurrentPostRequests();
+    }
+
+    @Test
+    public void testSharedPool() throws Exception {
+        final HttpHost target = start();
+        final Future<SimpleHttpResponse> future1 = httpclient.execute(
+                SimpleHttpRequest.get(target, "/random/2048"), null);
+        final SimpleHttpResponse response1 = future1.get();
+        Assert.assertThat(response1, CoreMatchers.notNullValue());
+        Assert.assertThat(response1.getCode(), CoreMatchers.equalTo(200));
+        final String body1 = response1.getBodyText();
+        Assert.assertThat(body1, CoreMatchers.notNullValue());
+        Assert.assertThat(body1.length(), CoreMatchers.equalTo(2048));
+
+
+        try (final CloseableHttpAsyncClient httpclient2 = HttpAsyncClients.custom()
+                .setConnectionManager(connManager)
+                .setConnectionManagerShared(true)
+                .build()) {
+            httpclient2.start();
+            final Future<SimpleHttpResponse> future2 = httpclient2.execute(
+                    SimpleHttpRequest.get(target, "/random/2048"), null);
+            final SimpleHttpResponse response2 = future2.get();
+            Assert.assertThat(response2, CoreMatchers.notNullValue());
+            Assert.assertThat(response2.getCode(), CoreMatchers.equalTo(200));
+            final String body2 = response2.getBodyText();
+            Assert.assertThat(body2, CoreMatchers.notNullValue());
+            Assert.assertThat(body2.length(), CoreMatchers.equalTo(2048));
+        }
+
+        final Future<SimpleHttpResponse> future3 = httpclient.execute(
+                SimpleHttpRequest.get(target, "/random/2048"), null);
+        final SimpleHttpResponse response3 = future3.get();
+        Assert.assertThat(response3, CoreMatchers.notNullValue());
+        Assert.assertThat(response3.getCode(), CoreMatchers.equalTo(200));
+        final String body3 = response3.getBodyText();
+        Assert.assertThat(body3, CoreMatchers.notNullValue());
+        Assert.assertThat(body3.length(), CoreMatchers.equalTo(2048));
+    }
+
+    @Test
+    public void testBadRequest() throws Exception {
+        final HttpHost target = start();
+        final Future<SimpleHttpResponse> future = httpclient.execute(
+                SimpleHttpRequest.get(target, "/random/boom"), null);
+        final SimpleHttpResponse response = future.get();
+        Assert.assertThat(response, CoreMatchers.notNullValue());
+        Assert.assertThat(response.getCode(), CoreMatchers.equalTo(400));
+    }
+
+}
\ No newline at end of file


[3/6] httpcomponents-client git commit: * HTTP/2 multiplexing HttpAsyncClient implementation * Restructured integration tests to reduce test duplication

Posted by ol...@apache.org.
http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/6228a736/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncMainClientExec.java
----------------------------------------------------------------------
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncMainClientExec.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncMainClientExec.java
deleted file mode 100644
index d23f20f..0000000
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncMainClientExec.java
+++ /dev/null
@@ -1,229 +0,0 @@
-/*
- * ====================================================================
- * 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.async;
-
-import java.io.IOException;
-import java.io.InterruptedIOException;
-import java.nio.ByteBuffer;
-import java.util.List;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicReference;
-
-import org.apache.hc.client5.http.ConnectionKeepAliveStrategy;
-import org.apache.hc.client5.http.HttpRoute;
-import org.apache.hc.client5.http.UserTokenHandler;
-import org.apache.hc.client5.http.async.AsyncExecCallback;
-import org.apache.hc.client5.http.async.AsyncExecChain;
-import org.apache.hc.client5.http.async.AsyncExecChainHandler;
-import org.apache.hc.client5.http.async.AsyncExecRuntime;
-import org.apache.hc.client5.http.protocol.HttpClientContext;
-import org.apache.hc.core5.http.EntityDetails;
-import org.apache.hc.core5.http.Header;
-import org.apache.hc.core5.http.HttpException;
-import org.apache.hc.core5.http.HttpRequest;
-import org.apache.hc.core5.http.HttpResponse;
-import org.apache.hc.core5.http.HttpStatus;
-import org.apache.hc.core5.http.message.RequestLine;
-import org.apache.hc.core5.http.nio.AsyncClientExchangeHandler;
-import org.apache.hc.core5.http.nio.AsyncDataConsumer;
-import org.apache.hc.core5.http.nio.AsyncEntityProducer;
-import org.apache.hc.core5.http.nio.CapacityChannel;
-import org.apache.hc.core5.http.nio.DataStreamChannel;
-import org.apache.hc.core5.http.nio.RequestChannel;
-import org.apache.hc.core5.util.TimeValue;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-
-class AsyncMainClientExec implements AsyncExecChainHandler {
-
-    private final Logger log = LogManager.getLogger(getClass());
-
-    private final ConnectionKeepAliveStrategy keepAliveStrategy;
-    private final UserTokenHandler userTokenHandler;
-
-    AsyncMainClientExec(final ConnectionKeepAliveStrategy keepAliveStrategy, final UserTokenHandler userTokenHandler) {
-        this.keepAliveStrategy = keepAliveStrategy;
-        this.userTokenHandler = userTokenHandler;
-    }
-
-    @Override
-    public void execute(
-            final HttpRequest request,
-            final AsyncEntityProducer entityProducer,
-            final AsyncExecChain.Scope scope,
-            final AsyncExecChain chain,
-            final AsyncExecCallback asyncExecCallback) throws HttpException, IOException {
-        final String exchangeId = scope.exchangeId;
-        final HttpRoute route = scope.route;
-        final HttpClientContext clientContext = scope.clientContext;
-        final AsyncExecRuntime execRuntime = scope.execRuntime;
-
-        if (log.isDebugEnabled()) {
-            log.debug(exchangeId + ": executing " + new RequestLine(request));
-        }
-
-        final AtomicInteger messageCountDown = new AtomicInteger(2);
-        final AsyncClientExchangeHandler internalExchangeHandler = new AsyncClientExchangeHandler() {
-
-            private final AtomicReference<AsyncDataConsumer> entityConsumerRef = new AtomicReference<>(null);
-
-            @Override
-            public void releaseResources() {
-                final AsyncDataConsumer entityConsumer = entityConsumerRef.getAndSet(null);
-                if (entityConsumer != null) {
-                    entityConsumer.releaseResources();
-                }
-            }
-
-            @Override
-            public void failed(final Exception cause) {
-                execRuntime.markConnectionNonReusable();
-                asyncExecCallback.failed(cause);
-            }
-
-            @Override
-            public void cancel() {
-                failed(new InterruptedIOException());
-            }
-
-            @Override
-            public void produceRequest(final RequestChannel channel) throws HttpException, IOException {
-                channel.sendRequest(request, entityProducer);
-                if (entityProducer == null) {
-                    messageCountDown.decrementAndGet();
-                }
-            }
-
-            @Override
-            public int available() {
-                return entityProducer.available();
-            }
-
-            @Override
-            public void produce(final DataStreamChannel channel) throws IOException {
-                entityProducer.produce(new DataStreamChannel() {
-
-                    @Override
-                    public void requestOutput() {
-                        channel.requestOutput();
-                    }
-
-                    @Override
-                    public int write(final ByteBuffer src) throws IOException {
-                        return channel.write(src);
-                    }
-
-                    @Override
-                    public void endStream(final List<? extends Header> trailers) throws IOException {
-                        channel.endStream(trailers);
-                        if (messageCountDown.decrementAndGet() <= 0) {
-                            asyncExecCallback.completed();
-                        }
-                    }
-
-                    @Override
-                    public void endStream() throws IOException {
-                        channel.endStream();
-                        if (messageCountDown.decrementAndGet() <= 0) {
-                            asyncExecCallback.completed();
-                        }
-                    }
-
-                });
-            }
-
-            @Override
-            public void consumeInformation(final HttpResponse response) throws HttpException, IOException {
-            }
-
-            @Override
-            public void consumeResponse(final HttpResponse response, final EntityDetails entityDetails) throws HttpException, IOException {
-                entityConsumerRef.set(asyncExecCallback.handleResponse(response, entityDetails));
-                if (response.getCode() >= HttpStatus.SC_CLIENT_ERROR) {
-                    messageCountDown.decrementAndGet();
-                }
-                final TimeValue keepAliveDuration = keepAliveStrategy.getKeepAliveDuration(response, clientContext);
-                Object userToken = clientContext.getUserToken();
-                if (userToken == null) {
-                    userToken = userTokenHandler.getUserToken(route, clientContext);
-                    clientContext.setAttribute(HttpClientContext.USER_TOKEN, userToken);
-                }
-                execRuntime.markConnectionReusable(userToken, keepAliveDuration);
-                if (entityDetails == null) {
-                    execRuntime.validateConnection();
-                    if (messageCountDown.decrementAndGet() <= 0) {
-                        asyncExecCallback.completed();
-                    }
-                }
-            }
-
-            @Override
-            public void updateCapacity(final CapacityChannel capacityChannel) throws IOException {
-                final AsyncDataConsumer entityConsumer = entityConsumerRef.get();
-                if (entityConsumer != null) {
-                    entityConsumer.updateCapacity(capacityChannel);
-                } else {
-                    capacityChannel.update(Integer.MAX_VALUE);
-                }
-            }
-
-            @Override
-            public int consume(final ByteBuffer src) throws IOException {
-                final AsyncDataConsumer entityConsumer = entityConsumerRef.get();
-                if (entityConsumer != null) {
-                    return entityConsumer.consume(src);
-                } else {
-                    return Integer.MAX_VALUE;
-                }
-            }
-
-            @Override
-            public void streamEnd(final List<? extends Header> trailers) throws HttpException, IOException {
-                final AsyncDataConsumer entityConsumer = entityConsumerRef.getAndSet(null);
-                if (entityConsumer != null) {
-                    entityConsumer.streamEnd(trailers);
-                } else {
-                    execRuntime.validateConnection();
-                }
-                if (messageCountDown.decrementAndGet() <= 0) {
-                    asyncExecCallback.completed();
-                }
-            }
-
-        };
-
-        if (log.isDebugEnabled()) {
-            execRuntime.execute(
-                    new LoggingAsyncClientExchangeHandler(log, exchangeId, internalExchangeHandler),
-                    clientContext);
-        } else {
-            execRuntime.execute(
-                    internalExchangeHandler, clientContext);
-        }
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/6228a736/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/Http2AsyncClientBuilder.java
----------------------------------------------------------------------
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/Http2AsyncClientBuilder.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/Http2AsyncClientBuilder.java
new file mode 100644
index 0000000..1728709
--- /dev/null
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/Http2AsyncClientBuilder.java
@@ -0,0 +1,885 @@
+/*
+ * ====================================================================
+ * 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.async;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.ThreadFactory;
+
+import org.apache.hc.client5.http.AuthenticationStrategy;
+import org.apache.hc.client5.http.DnsResolver;
+import org.apache.hc.client5.http.HttpRequestRetryHandler;
+import org.apache.hc.client5.http.SchemePortResolver;
+import org.apache.hc.client5.http.SystemDefaultDnsResolver;
+import org.apache.hc.client5.http.async.AsyncExecChainHandler;
+import org.apache.hc.client5.http.auth.AuthSchemeProvider;
+import org.apache.hc.client5.http.auth.CredentialsProvider;
+import org.apache.hc.client5.http.auth.KerberosConfig;
+import org.apache.hc.client5.http.config.AuthSchemes;
+import org.apache.hc.client5.http.config.RequestConfig;
+import org.apache.hc.client5.http.cookie.BasicCookieStore;
+import org.apache.hc.client5.http.cookie.CookieSpecProvider;
+import org.apache.hc.client5.http.cookie.CookieStore;
+import org.apache.hc.client5.http.impl.ChainElements;
+import org.apache.hc.client5.http.impl.CookieSpecRegistries;
+import org.apache.hc.client5.http.impl.DefaultAuthenticationStrategy;
+import org.apache.hc.client5.http.impl.DefaultHttpRequestRetryHandler;
+import org.apache.hc.client5.http.impl.DefaultRedirectStrategy;
+import org.apache.hc.client5.http.impl.DefaultSchemePortResolver;
+import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider;
+import org.apache.hc.client5.http.impl.auth.BasicSchemeFactory;
+import org.apache.hc.client5.http.impl.auth.CredSspSchemeFactory;
+import org.apache.hc.client5.http.impl.auth.DigestSchemeFactory;
+import org.apache.hc.client5.http.impl.auth.KerberosSchemeFactory;
+import org.apache.hc.client5.http.impl.auth.NTLMSchemeFactory;
+import org.apache.hc.client5.http.impl.auth.SPNegoSchemeFactory;
+import org.apache.hc.client5.http.impl.auth.SystemDefaultCredentialsProvider;
+import org.apache.hc.client5.http.impl.nio.MultuhomeConnectionInitiator;
+import org.apache.hc.client5.http.impl.routing.DefaultRoutePlanner;
+import org.apache.hc.client5.http.protocol.RedirectStrategy;
+import org.apache.hc.client5.http.protocol.RequestAddCookies;
+import org.apache.hc.client5.http.protocol.RequestAuthCache;
+import org.apache.hc.client5.http.protocol.RequestDefaultHeaders;
+import org.apache.hc.client5.http.protocol.RequestExpectContinue;
+import org.apache.hc.client5.http.protocol.ResponseProcessCookies;
+import org.apache.hc.client5.http.routing.HttpRoutePlanner;
+import org.apache.hc.client5.http.ssl.H2TlsStrategy;
+import org.apache.hc.core5.annotation.Internal;
+import org.apache.hc.core5.concurrent.DefaultThreadFactory;
+import org.apache.hc.core5.function.Callback;
+import org.apache.hc.core5.function.Resolver;
+import org.apache.hc.core5.http.Header;
+import org.apache.hc.core5.http.HttpException;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.HttpRequest;
+import org.apache.hc.core5.http.HttpRequestInterceptor;
+import org.apache.hc.core5.http.HttpResponseInterceptor;
+import org.apache.hc.core5.http.config.CharCodingConfig;
+import org.apache.hc.core5.http.config.Lookup;
+import org.apache.hc.core5.http.config.NamedElementChain;
+import org.apache.hc.core5.http.config.RegistryBuilder;
+import org.apache.hc.core5.http.nio.AsyncPushConsumer;
+import org.apache.hc.core5.http.nio.HandlerFactory;
+import org.apache.hc.core5.http.nio.command.ShutdownCommand;
+import org.apache.hc.core5.http.nio.ssl.TlsStrategy;
+import org.apache.hc.core5.http.protocol.DefaultHttpProcessor;
+import org.apache.hc.core5.http.protocol.HttpContext;
+import org.apache.hc.core5.http.protocol.HttpProcessor;
+import org.apache.hc.core5.http.protocol.HttpProcessorBuilder;
+import org.apache.hc.core5.http.protocol.RequestTargetHost;
+import org.apache.hc.core5.http.protocol.RequestUserAgent;
+import org.apache.hc.core5.http2.config.H2Config;
+import org.apache.hc.core5.http2.nio.pool.H2ConnPool;
+import org.apache.hc.core5.http2.protocol.H2RequestConnControl;
+import org.apache.hc.core5.http2.protocol.H2RequestContent;
+import org.apache.hc.core5.http2.protocol.H2RequestTargetHost;
+import org.apache.hc.core5.io.ShutdownType;
+import org.apache.hc.core5.reactor.DefaultConnectingIOReactor;
+import org.apache.hc.core5.reactor.IOEventHandlerFactory;
+import org.apache.hc.core5.reactor.IOReactorConfig;
+import org.apache.hc.core5.reactor.IOSession;
+import org.apache.hc.core5.util.Args;
+import org.apache.hc.core5.util.TimeValue;
+import org.apache.hc.core5.util.VersionInfo;
+
+/**
+ * Builder for HTTP/2 {@link CloseableHttpAsyncClient} instances.
+ * <p>
+ * When a particular component is not explicitly set this class will
+ * use its default implementation.
+ *
+ * @since 5.0
+ */
+public class Http2AsyncClientBuilder {
+
+    private static class RequestInterceptorEntry {
+
+        enum Postion { FIRST, LAST }
+
+        final RequestInterceptorEntry.Postion postion;
+        final HttpRequestInterceptor interceptor;
+
+        private RequestInterceptorEntry(final RequestInterceptorEntry.Postion postion, final HttpRequestInterceptor interceptor) {
+            this.postion = postion;
+            this.interceptor = interceptor;
+        }
+    }
+
+    private static class ResponseInterceptorEntry {
+
+        enum Postion { FIRST, LAST }
+
+        final ResponseInterceptorEntry.Postion postion;
+        final HttpResponseInterceptor interceptor;
+
+        private ResponseInterceptorEntry(final ResponseInterceptorEntry.Postion postion, final HttpResponseInterceptor interceptor) {
+            this.postion = postion;
+            this.interceptor = interceptor;
+        }
+    }
+
+    private static class ExecInterceptorEntry {
+
+        enum Postion { BEFORE, AFTER, REPLACE, FIRST, LAST }
+
+        final ExecInterceptorEntry.Postion postion;
+        final String name;
+        final AsyncExecChainHandler interceptor;
+        final String existing;
+
+        private ExecInterceptorEntry(
+                final ExecInterceptorEntry.Postion postion,
+                final String name,
+                final AsyncExecChainHandler interceptor,
+                final String existing) {
+            this.postion = postion;
+            this.name = name;
+            this.interceptor = interceptor;
+            this.existing = existing;
+        }
+
+    }
+
+    private IOReactorConfig ioReactorConfig;
+    private H2Config h2Config;
+    private CharCodingConfig charCodingConfig;
+    private SchemePortResolver schemePortResolver;
+    private AuthenticationStrategy targetAuthStrategy;
+    private AuthenticationStrategy proxyAuthStrategy;
+
+    private LinkedList<RequestInterceptorEntry> requestInterceptors;
+    private LinkedList<ResponseInterceptorEntry> responseInterceptors;
+    private LinkedList<ExecInterceptorEntry> execInterceptors;
+
+    private HttpRoutePlanner routePlanner;
+    private RedirectStrategy redirectStrategy;
+    private HttpRequestRetryHandler retryHandler;
+
+    private Lookup<AuthSchemeProvider> authSchemeRegistry;
+    private Lookup<CookieSpecProvider> cookieSpecRegistry;
+    private CookieStore cookieStore;
+    private CredentialsProvider credentialsProvider;
+
+    private String userAgent;
+    private Collection<? extends Header> defaultHeaders;
+    private RequestConfig defaultRequestConfig;
+    private boolean evictIdleConnections;
+    private TimeValue maxIdleTime;
+
+    private boolean systemProperties;
+    private boolean automaticRetriesDisabled;
+    private boolean redirectHandlingDisabled;
+    private boolean cookieManagementDisabled;
+    private boolean authCachingDisabled;
+
+    private DnsResolver dnsResolver;
+    private TlsStrategy tlsStrategy;
+
+    private ThreadFactory threadFactory;
+
+    private List<Closeable> closeables;
+
+    public static Http2AsyncClientBuilder create() {
+        return new Http2AsyncClientBuilder();
+    }
+
+    protected Http2AsyncClientBuilder() {
+        super();
+    }
+
+    /**
+     * Sets {@link H2Config} configuration.
+     */
+    public final Http2AsyncClientBuilder setH2Config(final H2Config h2Config) {
+        this.h2Config = h2Config;
+        return this;
+    }
+
+    /**
+     * Sets {@link IOReactorConfig} configuration.
+     */
+    public final Http2AsyncClientBuilder setIOReactorConfig(final IOReactorConfig ioReactorConfig) {
+        this.ioReactorConfig = ioReactorConfig;
+        return this;
+    }
+
+    /**
+     * Sets {@link CharCodingConfig} configuration.
+     */
+    public final Http2AsyncClientBuilder setCharCodingConfig(final CharCodingConfig charCodingConfig) {
+        this.charCodingConfig = charCodingConfig;
+        return this;
+    }
+
+    /**
+     * Assigns {@link AuthenticationStrategy} instance for target
+     * host authentication.
+     */
+    public final Http2AsyncClientBuilder setTargetAuthenticationStrategy(
+            final AuthenticationStrategy targetAuthStrategy) {
+        this.targetAuthStrategy = targetAuthStrategy;
+        return this;
+    }
+
+    /**
+     * Assigns {@link AuthenticationStrategy} instance for proxy
+     * authentication.
+     */
+    public final Http2AsyncClientBuilder setProxyAuthenticationStrategy(
+            final AuthenticationStrategy proxyAuthStrategy) {
+        this.proxyAuthStrategy = proxyAuthStrategy;
+        return this;
+    }
+
+    /**
+     * Adds this protocol interceptor to the head of the protocol processing list.
+     */
+    public final Http2AsyncClientBuilder addRequestInterceptorFirst(final HttpResponseInterceptor interceptor) {
+        Args.notNull(interceptor, "Interceptor");
+        if (responseInterceptors == null) {
+            responseInterceptors = new LinkedList<>();
+        }
+        responseInterceptors.add(new ResponseInterceptorEntry(ResponseInterceptorEntry.Postion.FIRST, interceptor));
+        return this;
+    }
+
+    /**
+     * Adds this protocol interceptor to the tail of the protocol processing list.
+     */
+    public final Http2AsyncClientBuilder addResponseInterceptorLast(final HttpResponseInterceptor interceptor) {
+        Args.notNull(interceptor, "Interceptor");
+        if (responseInterceptors == null) {
+            responseInterceptors = new LinkedList<>();
+        }
+        responseInterceptors.add(new ResponseInterceptorEntry(ResponseInterceptorEntry.Postion.LAST, interceptor));
+        return this;
+    }
+
+    /**
+     * Adds this execution interceptor before an existing interceptor.
+     */
+    public final Http2AsyncClientBuilder addExecInterceptorBefore(final String existing, final String name, final AsyncExecChainHandler interceptor) {
+        Args.notBlank(existing, "Existing");
+        Args.notBlank(name, "Name");
+        Args.notNull(interceptor, "Interceptor");
+        if (execInterceptors == null) {
+            execInterceptors = new LinkedList<>();
+        }
+        execInterceptors.add(new ExecInterceptorEntry(ExecInterceptorEntry.Postion.BEFORE, name, interceptor, existing));
+        return this;
+    }
+
+    /**
+     * Adds this execution interceptor after interceptor with the given name.
+     */
+    public final Http2AsyncClientBuilder addExecInterceptorAfter(final String existing, final String name, final AsyncExecChainHandler interceptor) {
+        Args.notBlank(existing, "Existing");
+        Args.notBlank(name, "Name");
+        Args.notNull(interceptor, "Interceptor");
+        if (execInterceptors == null) {
+            execInterceptors = new LinkedList<>();
+        }
+        execInterceptors.add(new ExecInterceptorEntry(ExecInterceptorEntry.Postion.AFTER, name, interceptor, existing));
+        return this;
+    }
+
+    /**
+     * Replace an existing interceptor with the given name with new interceptor.
+     */
+    public final Http2AsyncClientBuilder replaceExecInterceptor(final String existing, final AsyncExecChainHandler interceptor) {
+        Args.notBlank(existing, "Existing");
+        Args.notNull(interceptor, "Interceptor");
+        if (execInterceptors == null) {
+            execInterceptors = new LinkedList<>();
+        }
+        execInterceptors.add(new ExecInterceptorEntry(ExecInterceptorEntry.Postion.REPLACE, existing, interceptor, existing));
+        return this;
+    }
+
+    /**
+     * Add an interceptor to the head of the processing list.
+     */
+    public final Http2AsyncClientBuilder addExecInterceptorFirst(final String name, final AsyncExecChainHandler interceptor) {
+        Args.notNull(name, "Name");
+        Args.notNull(interceptor, "Interceptor");
+        execInterceptors.add(new ExecInterceptorEntry(ExecInterceptorEntry.Postion.FIRST, name, interceptor, null));
+        return this;
+    }
+
+    /**
+     * Add an interceptor to the tail of the processing list.
+     */
+    public final Http2AsyncClientBuilder addExecInterceptorLast(final String name, final AsyncExecChainHandler interceptor) {
+        Args.notNull(name, "Name");
+        Args.notNull(interceptor, "Interceptor");
+        execInterceptors.add(new ExecInterceptorEntry(ExecInterceptorEntry.Postion.LAST, name, interceptor, null));
+        return this;
+    }
+
+    /**
+     * Adds this protocol interceptor to the head of the protocol processing list.
+     */
+    public final Http2AsyncClientBuilder addRequestInterceptorFirst(final HttpRequestInterceptor interceptor) {
+        Args.notNull(interceptor, "Interceptor");
+        if (requestInterceptors == null) {
+            requestInterceptors = new LinkedList<>();
+        }
+        requestInterceptors.add(new RequestInterceptorEntry(RequestInterceptorEntry.Postion.FIRST, interceptor));
+        return this;
+    }
+
+    /**
+     * Adds this protocol interceptor to the tail of the protocol processing list.
+     */
+    public final Http2AsyncClientBuilder addResponseInterceptorLast(final HttpRequestInterceptor interceptor) {
+        Args.notNull(interceptor, "Interceptor");
+        if (requestInterceptors == null) {
+            requestInterceptors = new LinkedList<>();
+        }
+        requestInterceptors.add(new RequestInterceptorEntry(RequestInterceptorEntry.Postion.LAST, interceptor));
+        return this;
+    }
+
+    /**
+     * Assigns {@link HttpRequestRetryHandler} instance.
+     * <p>
+     * Please note this value can be overridden by the {@link #disableAutomaticRetries()}
+     * method.
+     */
+    public final Http2AsyncClientBuilder setRetryHandler(final HttpRequestRetryHandler retryHandler) {
+        this.retryHandler = retryHandler;
+        return this;
+    }
+
+    /**
+     * Assigns {@link RedirectStrategy} instance.
+     * <p>
+     * Please note this value can be overridden by the {@link #disableRedirectHandling()}
+     * method.
+     * </p>
+     */
+    public Http2AsyncClientBuilder setRedirectStrategy(final RedirectStrategy redirectStrategy) {
+        this.redirectStrategy = redirectStrategy;
+        return this;
+    }
+
+    /**
+     * Assigns {@link SchemePortResolver} instance.
+     */
+    public final Http2AsyncClientBuilder setSchemePortResolver(final SchemePortResolver schemePortResolver) {
+        this.schemePortResolver = schemePortResolver;
+        return this;
+    }
+
+    /**
+     * Assigns {@link DnsResolver} instance.
+     */
+    public final Http2AsyncClientBuilder setDnsResolver(final DnsResolver dnsResolver) {
+        this.dnsResolver = dnsResolver;
+        return this;
+    }
+
+    /**
+     * Assigns {@link TlsStrategy} instance.
+     */
+    public final Http2AsyncClientBuilder setTlsStrategy(final TlsStrategy tlsStrategy) {
+        this.tlsStrategy = tlsStrategy;
+        return this;
+    }
+
+    /**
+     * Assigns {@link ThreadFactory} instance.
+     */
+    public final Http2AsyncClientBuilder setThreadFactory(final ThreadFactory threadFactory) {
+        this.threadFactory = threadFactory;
+        return this;
+    }
+
+    /**
+     * Assigns {@code User-Agent} value.
+     */
+    public final Http2AsyncClientBuilder setUserAgent(final String userAgent) {
+        this.userAgent = userAgent;
+        return this;
+    }
+
+    /**
+     * Assigns default request header values.
+     */
+    public final Http2AsyncClientBuilder setDefaultHeaders(final Collection<? extends Header> defaultHeaders) {
+        this.defaultHeaders = defaultHeaders;
+        return this;
+    }
+
+    /**
+     * Assigns {@link HttpRoutePlanner} instance.
+     */
+    public final Http2AsyncClientBuilder setRoutePlanner(final HttpRoutePlanner routePlanner) {
+        this.routePlanner = routePlanner;
+        return this;
+    }
+
+    /**
+     * Assigns default {@link CredentialsProvider} instance which will be used
+     * for request execution if not explicitly set in the client execution
+     * context.
+     */
+    public final Http2AsyncClientBuilder setDefaultCredentialsProvider(final CredentialsProvider credentialsProvider) {
+        this.credentialsProvider = credentialsProvider;
+        return this;
+    }
+
+    /**
+     * Assigns default {@link org.apache.hc.client5.http.auth.AuthScheme} registry which will
+     * be used for request execution if not explicitly set in the client execution
+     * context.
+     */
+    public final Http2AsyncClientBuilder setDefaultAuthSchemeRegistry(final Lookup<AuthSchemeProvider> authSchemeRegistry) {
+        this.authSchemeRegistry = authSchemeRegistry;
+        return this;
+    }
+
+    /**
+     * Assigns default {@link org.apache.hc.client5.http.cookie.CookieSpec} registry
+     * which will be used for request execution if not explicitly set in the client
+     * execution context.
+     */
+    public final Http2AsyncClientBuilder setDefaultCookieSpecRegistry(final Lookup<CookieSpecProvider> cookieSpecRegistry) {
+        this.cookieSpecRegistry = cookieSpecRegistry;
+        return this;
+    }
+
+    /**
+     * Assigns default {@link CookieStore} instance which will be used for
+     * request execution if not explicitly set in the client execution context.
+     */
+    public final Http2AsyncClientBuilder setDefaultCookieStore(final CookieStore cookieStore) {
+        this.cookieStore = cookieStore;
+        return this;
+    }
+
+    /**
+     * Assigns default {@link RequestConfig} instance which will be used
+     * for request execution if not explicitly set in the client execution
+     * context.
+     */
+    public final Http2AsyncClientBuilder setDefaultRequestConfig(final RequestConfig config) {
+        this.defaultRequestConfig = config;
+        return this;
+    }
+
+    /**
+     * Use system properties when creating and configuring default
+     * implementations.
+     */
+    public final Http2AsyncClientBuilder useSystemProperties() {
+        this.systemProperties = true;
+        return this;
+    }
+
+    /**
+     * Disables automatic redirect handling.
+     */
+    public final Http2AsyncClientBuilder disableRedirectHandling() {
+        redirectHandlingDisabled = true;
+        return this;
+    }
+
+    /**
+     * Disables automatic request recovery and re-execution.
+     */
+    public final Http2AsyncClientBuilder disableAutomaticRetries() {
+        automaticRetriesDisabled = true;
+        return this;
+    }
+
+    /**
+     * Disables state (cookie) management.
+     */
+    public final Http2AsyncClientBuilder disableCookieManagement() {
+        this.cookieManagementDisabled = true;
+        return this;
+    }
+
+    /**
+     * Disables authentication scheme caching.
+     */
+    public final Http2AsyncClientBuilder disableAuthCaching() {
+        this.authCachingDisabled = true;
+        return this;
+    }
+
+    /**
+     * Makes this instance of HttpClient proactively evict idle connections from the
+     * connection pool using a background thread.
+     * <p>
+     * One MUST explicitly close HttpClient with {@link CloseableHttpAsyncClient#close()}
+     * in order to stop and release the background thread.
+     * <p>
+     * Please note this method has no effect if the instance of HttpClient is configuted to
+     * use a shared connection manager.
+     *
+     * @param maxIdleTime maximum time persistent connections can stay idle while kept alive
+     * in the connection pool. Connections whose inactivity period exceeds this value will
+     * get closed and evicted from the pool.
+     */
+    public final Http2AsyncClientBuilder evictIdleConnections(final TimeValue maxIdleTime) {
+        this.evictIdleConnections = true;
+        this.maxIdleTime = maxIdleTime;
+        return this;
+    }
+
+    /**
+     * Request exec chain customization and extension.
+     * <p>
+     * For internal use.
+     */
+    @Internal
+    protected void customizeExecChain(final NamedElementChain<AsyncExecChainHandler> execChainDefinition) {
+    }
+
+    /**
+     * Adds to the list of {@link Closeable} resources to be managed by the client.
+     * <p>
+     * For internal use.
+     */
+    @Internal
+    protected void addCloseable(final Closeable closeable) {
+        if (closeable == null) {
+            return;
+        }
+        if (closeables == null) {
+            closeables = new ArrayList<>();
+        }
+        closeables.add(closeable);
+    }
+
+    public CloseableHttpAsyncClient build() {
+        final NamedElementChain<AsyncExecChainHandler> execChainDefinition = new NamedElementChain<>();
+        execChainDefinition.addLast(
+                new Http2AsyncMainClientExec(),
+                ChainElements.MAIN_TRANSPORT.name());
+
+        AuthenticationStrategy targetAuthStrategyCopy = this.targetAuthStrategy;
+        if (targetAuthStrategyCopy == null) {
+            targetAuthStrategyCopy = DefaultAuthenticationStrategy.INSTANCE;
+        }
+        AuthenticationStrategy proxyAuthStrategyCopy = this.proxyAuthStrategy;
+        if (proxyAuthStrategyCopy == null) {
+            proxyAuthStrategyCopy = DefaultAuthenticationStrategy.INSTANCE;
+        }
+
+        String userAgentCopy = this.userAgent;
+        if (userAgentCopy == null) {
+            if (systemProperties) {
+                userAgentCopy = getProperty("http.agent", null);
+            }
+            if (userAgentCopy == null) {
+                userAgentCopy = VersionInfo.getSoftwareInfo("Apache-HttpAsyncClient",
+                        "org.apache.hc.client5", getClass());
+            }
+        }
+
+        execChainDefinition.addFirst(
+                new AsyncConnectExec(
+                        new DefaultHttpProcessor(new RequestTargetHost(), new RequestUserAgent(userAgentCopy)),
+                        proxyAuthStrategyCopy),
+                ChainElements.CONNECT.name());
+
+        final HttpProcessorBuilder b = HttpProcessorBuilder.create();
+        if (requestInterceptors != null) {
+            for (final RequestInterceptorEntry entry: requestInterceptors) {
+                if (entry.postion == RequestInterceptorEntry.Postion.FIRST) {
+                    b.addFirst(entry.interceptor);
+                }
+            }
+        }
+        if (responseInterceptors != null) {
+            for (final ResponseInterceptorEntry entry: responseInterceptors) {
+                if (entry.postion == ResponseInterceptorEntry.Postion.FIRST) {
+                    b.addFirst(entry.interceptor);
+                }
+            }
+        }
+        b.addAll(
+                new RequestDefaultHeaders(defaultHeaders),
+                new RequestUserAgent(userAgentCopy),
+                new RequestExpectContinue());
+        if (!cookieManagementDisabled) {
+            b.add(new RequestAddCookies());
+        }
+        if (!authCachingDisabled) {
+            b.add(new RequestAuthCache());
+        }
+        if (!cookieManagementDisabled) {
+            b.add(new ResponseProcessCookies());
+        }
+        if (requestInterceptors != null) {
+            for (final RequestInterceptorEntry entry: requestInterceptors) {
+                if (entry.postion == RequestInterceptorEntry.Postion.LAST) {
+                    b.addFirst(entry.interceptor);
+                }
+            }
+        }
+        if (responseInterceptors != null) {
+            for (final ResponseInterceptorEntry entry: responseInterceptors) {
+                if (entry.postion == ResponseInterceptorEntry.Postion.LAST) {
+                    b.addFirst(entry.interceptor);
+                }
+            }
+        }
+
+        final HttpProcessor httpProcessor = b.build();
+        execChainDefinition.addFirst(
+                new AsyncProtocolExec(httpProcessor, targetAuthStrategyCopy, proxyAuthStrategyCopy),
+                ChainElements.PROTOCOL.name());
+
+        // Add request retry executor, if not disabled
+        if (!automaticRetriesDisabled) {
+            HttpRequestRetryHandler retryHandlerCopy = this.retryHandler;
+            if (retryHandlerCopy == null) {
+                retryHandlerCopy = DefaultHttpRequestRetryHandler.INSTANCE;
+            }
+            execChainDefinition.addFirst(
+                    new AsyncRetryExec(retryHandlerCopy),
+                    ChainElements.RETRY_IO_ERROR.name());
+        }
+
+        HttpRoutePlanner routePlannerCopy = this.routePlanner;
+        if (routePlannerCopy == null) {
+            SchemePortResolver schemePortResolverCopy = this.schemePortResolver;
+            if (schemePortResolverCopy == null) {
+                schemePortResolverCopy = DefaultSchemePortResolver.INSTANCE;
+            }
+            routePlannerCopy = new DefaultRoutePlanner(schemePortResolverCopy);
+        }
+
+        // Add redirect executor, if not disabled
+        if (!redirectHandlingDisabled) {
+            RedirectStrategy redirectStrategyCopy = this.redirectStrategy;
+            if (redirectStrategyCopy == null) {
+                redirectStrategyCopy = DefaultRedirectStrategy.INSTANCE;
+            }
+            execChainDefinition.addFirst(
+                    new AsyncRedirectExec(routePlannerCopy, redirectStrategyCopy),
+                    ChainElements.REDIRECT.name());
+        }
+
+        final AsyncPushConsumerRegistry pushConsumerRegistry = new AsyncPushConsumerRegistry();
+        final IOEventHandlerFactory ioEventHandlerFactory = new Http2AsyncClientEventHandlerFactory(
+                new DefaultHttpProcessor(new H2RequestContent(), new H2RequestTargetHost(), new H2RequestConnControl()),
+                new HandlerFactory<AsyncPushConsumer>() {
+
+                    @Override
+                    public AsyncPushConsumer create(final HttpRequest request, final HttpContext context) throws HttpException {
+                        return pushConsumerRegistry.get(request);
+                    }
+
+                },
+                h2Config != null ? h2Config : H2Config.DEFAULT,
+                charCodingConfig != null ? charCodingConfig : CharCodingConfig.DEFAULT);
+        final DefaultConnectingIOReactor ioReactor = new DefaultConnectingIOReactor(
+                ioEventHandlerFactory,
+                ioReactorConfig != null ? ioReactorConfig : IOReactorConfig.DEFAULT,
+                threadFactory != null ? threadFactory : new DefaultThreadFactory("httpclient-dispatch", true),
+                null,
+                null,
+                new Callback<IOSession>() {
+
+                    @Override
+                    public void execute(final IOSession ioSession) {
+                        ioSession.addFirst(new ShutdownCommand(ShutdownType.GRACEFUL));
+                    }
+
+                });
+
+        if (execInterceptors != null) {
+            for (final ExecInterceptorEntry entry: execInterceptors) {
+                switch (entry.postion) {
+                    case AFTER:
+                        execChainDefinition.addAfter(entry.existing, entry.interceptor, entry.name);
+                        break;
+                    case BEFORE:
+                        execChainDefinition.addBefore(entry.existing, entry.interceptor, entry.name);
+                        break;
+                    case FIRST:
+                        execChainDefinition.addFirst(entry.interceptor, entry.name);
+                        break;
+                    case LAST:
+                        execChainDefinition.addLast(entry.interceptor, entry.name);
+                        break;
+                }
+            }
+        }
+
+        customizeExecChain(execChainDefinition);
+
+        NamedElementChain<AsyncExecChainHandler>.Node current = execChainDefinition.getLast();
+        AsyncExecChainElement execChain = null;
+        while (current != null) {
+            execChain = new AsyncExecChainElement(current.getValue(), execChain);
+            current = current.getPrevious();
+        }
+
+        Lookup<AuthSchemeProvider> authSchemeRegistryCopy = this.authSchemeRegistry;
+        if (authSchemeRegistryCopy == null) {
+            authSchemeRegistryCopy = RegistryBuilder.<AuthSchemeProvider>create()
+                    .register(AuthSchemes.BASIC, new BasicSchemeFactory())
+                    .register(AuthSchemes.DIGEST, new DigestSchemeFactory())
+                    .register(AuthSchemes.CREDSSP, new CredSspSchemeFactory())
+                    .register(AuthSchemes.NTLM, new NTLMSchemeFactory())
+                    .register(AuthSchemes.SPNEGO, new SPNegoSchemeFactory(KerberosConfig.DEFAULT, SystemDefaultDnsResolver.INSTANCE))
+                    .register(AuthSchemes.KERBEROS, new KerberosSchemeFactory(KerberosConfig.DEFAULT, SystemDefaultDnsResolver.INSTANCE))
+                    .build();
+        }
+        Lookup<CookieSpecProvider> cookieSpecRegistryCopy = this.cookieSpecRegistry;
+        if (cookieSpecRegistryCopy == null) {
+            cookieSpecRegistryCopy = CookieSpecRegistries.createDefault();
+        }
+
+        CookieStore cookieStoreCopy = this.cookieStore;
+        if (cookieStoreCopy == null) {
+            cookieStoreCopy = new BasicCookieStore();
+        }
+
+        CredentialsProvider credentialsProviderCopy = this.credentialsProvider;
+        if (credentialsProviderCopy == null) {
+            if (systemProperties) {
+                credentialsProviderCopy = new SystemDefaultCredentialsProvider();
+            } else {
+                credentialsProviderCopy = new BasicCredentialsProvider();
+            }
+        }
+
+        TlsStrategy tlsStrategyCopy = this.tlsStrategy;
+        if (tlsStrategyCopy == null) {
+            if (systemProperties) {
+                tlsStrategyCopy = H2TlsStrategy.getSystemDefault();
+            } else {
+                tlsStrategyCopy = H2TlsStrategy.getDefault();
+            }
+        }
+
+        final MultuhomeConnectionInitiator connectionInitiator = new MultuhomeConnectionInitiator(ioReactor, dnsResolver);
+        final H2ConnPool connPool = new H2ConnPool(connectionInitiator, new Resolver<HttpHost, InetSocketAddress>() {
+
+            @Override
+            public InetSocketAddress resolve(final HttpHost host) {
+                return null;
+            }
+
+        }, tlsStrategyCopy);
+
+        List<Closeable> closeablesCopy = closeables != null ? new ArrayList<>(closeables) : null;
+        if (closeablesCopy == null) {
+            closeablesCopy = new ArrayList<>(1);
+        }
+        if (evictIdleConnections) {
+            final IdleConnectionEvictor connectionEvictor = new IdleConnectionEvictor(connPool,
+                    maxIdleTime != null ? maxIdleTime : TimeValue.ofSeconds(30L));
+            closeablesCopy.add(new Closeable() {
+
+                @Override
+                public void close() throws IOException {
+                    connectionEvictor.shutdown();
+                }
+
+            });
+            connectionEvictor.start();
+        }
+        closeablesCopy.add(connPool);
+
+        return new InternalHttp2AsyncClient(
+                ioReactor,
+                execChain,
+                pushConsumerRegistry,
+                threadFactory != null ? threadFactory : new DefaultThreadFactory("httpclient-main", true),
+                connPool,
+                routePlannerCopy,
+                cookieSpecRegistryCopy,
+                authSchemeRegistryCopy,
+                cookieStoreCopy,
+                credentialsProviderCopy,
+                defaultRequestConfig,
+                closeablesCopy);
+    }
+
+    private static String getProperty(final String key, final String defaultValue) {
+        return AccessController.doPrivileged(new PrivilegedAction<String>() {
+            @Override
+            public String run() {
+                return System.getProperty(key, defaultValue);
+            }
+        });
+    }
+
+    static class IdleConnectionEvictor implements Closeable {
+
+        private final Thread thread;
+
+        public IdleConnectionEvictor(final H2ConnPool connPool, final TimeValue maxIdleTime) {
+            this.thread = new DefaultThreadFactory("idle-connection-evictor", true).newThread(new Runnable() {
+                @Override
+                public void run() {
+                    try {
+                        while (!Thread.currentThread().isInterrupted()) {
+                            Thread.sleep(maxIdleTime.toMillis());
+                            connPool.closeIdle(maxIdleTime);
+                        }
+                    } catch (final InterruptedException ex) {
+                        Thread.currentThread().interrupt();
+                    } catch (final Exception ex) {
+                    }
+
+                }
+            });
+        }
+
+        public void start() {
+            thread.start();
+        }
+
+        public void shutdown() {
+            thread.interrupt();
+        }
+
+        @Override
+        public void close() throws IOException {
+            shutdown();
+        }
+
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/6228a736/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/Http2AsyncClientEventHandlerFactory.java
----------------------------------------------------------------------
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/Http2AsyncClientEventHandlerFactory.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/Http2AsyncClientEventHandlerFactory.java
new file mode 100644
index 0000000..52f399c
--- /dev/null
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/Http2AsyncClientEventHandlerFactory.java
@@ -0,0 +1,194 @@
+/*
+ * ====================================================================
+ * 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.async;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.hc.client5.http.impl.ConnPoolSupport;
+import org.apache.hc.core5.annotation.Contract;
+import org.apache.hc.core5.annotation.ThreadingBehavior;
+import org.apache.hc.core5.http.Header;
+import org.apache.hc.core5.http.HttpConnection;
+import org.apache.hc.core5.http.config.CharCodingConfig;
+import org.apache.hc.core5.http.nio.AsyncPushConsumer;
+import org.apache.hc.core5.http.nio.HandlerFactory;
+import org.apache.hc.core5.http.protocol.HttpProcessor;
+import org.apache.hc.core5.http2.config.H2Config;
+import org.apache.hc.core5.http2.frame.FramePrinter;
+import org.apache.hc.core5.http2.frame.RawFrame;
+import org.apache.hc.core5.http2.impl.nio.ClientHttp2StreamMultiplexerFactory;
+import org.apache.hc.core5.http2.impl.nio.Http2OnlyClientProtocolNegotiator;
+import org.apache.hc.core5.http2.impl.nio.Http2StreamListener;
+import org.apache.hc.core5.reactor.IOEventHandler;
+import org.apache.hc.core5.reactor.IOEventHandlerFactory;
+import org.apache.hc.core5.reactor.TlsCapableIOSession;
+import org.apache.hc.core5.util.Args;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+/**
+ * @since 5.0
+ */
+@Contract(threading = ThreadingBehavior.IMMUTABLE)
+class Http2AsyncClientEventHandlerFactory implements IOEventHandlerFactory {
+
+    private final Logger wireLog = LogManager.getLogger("org.apache.hc.client5.http.wire");
+    private final Logger headerLog = LogManager.getLogger("org.apache.hc.client5.http.headers");
+    private final Logger frameLog = LogManager.getLogger("org.apache.hc.client5.http2.frame");
+    private final Logger framePayloadLog = LogManager.getLogger("org.apache.hc.client5.http2.frame.payload");
+    private final Logger flowCtrlLog = LogManager.getLogger("org.apache.hc.client5.http2.flow");
+
+    private final HttpProcessor httpProcessor;
+    private final HandlerFactory<AsyncPushConsumer> exchangeHandlerFactory;
+    private final H2Config h2Config;
+    private final CharCodingConfig charCodingConfig;
+
+    Http2AsyncClientEventHandlerFactory(
+            final HttpProcessor httpProcessor,
+            final HandlerFactory<AsyncPushConsumer> exchangeHandlerFactory,
+            final H2Config h2Config,
+            final CharCodingConfig charCodingConfig) {
+        this.httpProcessor = Args.notNull(httpProcessor, "HTTP processor");
+        this.exchangeHandlerFactory = exchangeHandlerFactory;
+        this.h2Config = h2Config != null ? h2Config : H2Config.DEFAULT;
+        this.charCodingConfig = charCodingConfig != null ? charCodingConfig : CharCodingConfig.DEFAULT;
+    }
+
+    @Override
+    public IOEventHandler createHandler(final TlsCapableIOSession ioSession, final Object attachment) {
+        final Logger sessionLog = LogManager.getLogger(ioSession.getClass());
+        if (sessionLog.isDebugEnabled()
+                || wireLog.isDebugEnabled()
+                || headerLog.isDebugEnabled()
+                || frameLog.isDebugEnabled()
+                || framePayloadLog.isDebugEnabled()
+                || flowCtrlLog.isDebugEnabled()) {
+            final String id = ConnPoolSupport.getId(ioSession);
+            final ClientHttp2StreamMultiplexerFactory http2StreamHandlerFactory = new ClientHttp2StreamMultiplexerFactory(
+                    httpProcessor,
+                    exchangeHandlerFactory,
+                    h2Config,
+                    charCodingConfig,
+                    new Http2StreamListener() {
+
+                        final FramePrinter framePrinter = new FramePrinter();
+
+                        private void logFrameInfo(final String prefix, final RawFrame frame) {
+                            try {
+                                final LogAppendable logAppendable = new LogAppendable(frameLog, prefix);
+                                framePrinter.printFrameInfo(frame, logAppendable);
+                                logAppendable.flush();
+                            } catch (final IOException ignore) {
+                            }
+                        }
+
+                        private void logFramePayload(final String prefix, final RawFrame frame) {
+                            try {
+                                final LogAppendable logAppendable = new LogAppendable(framePayloadLog, prefix);
+                                framePrinter.printPayload(frame, logAppendable);
+                                logAppendable.flush();
+                            } catch (final IOException ignore) {
+                            }
+                        }
+
+                        private void logFlowControl(final String prefix, final int streamId, final int delta, final int actualSize) {
+                            final StringBuilder buffer = new StringBuilder();
+                            buffer.append(prefix).append(" stream ").append(streamId).append(" flow control " )
+                                    .append(delta).append(" -> ")
+                                    .append(actualSize);
+                            flowCtrlLog.debug(buffer.toString());
+                        }
+
+                        @Override
+                        public void onHeaderInput(final HttpConnection connection, final int streamId, final List<? extends Header> headers) {
+                            if (headerLog.isDebugEnabled()) {
+                                for (int i = 0; i < headers.size(); i++) {
+                                    headerLog.debug(id + " << " + headers.get(i));
+                                }
+                            }
+                        }
+
+                        @Override
+                        public void onHeaderOutput(final HttpConnection connection, final int streamId, final List<? extends Header> headers) {
+                            if (headerLog.isDebugEnabled()) {
+                                for (int i = 0; i < headers.size(); i++) {
+                                    headerLog.debug(id + " >> " + headers.get(i));
+                                }
+                            }
+                        }
+
+                        @Override
+                        public void onFrameInput(final HttpConnection connection, final int streamId, final RawFrame frame) {
+                            if (frameLog.isDebugEnabled()) {
+                                logFrameInfo(id + " <<", frame);
+                            }
+                            if (framePayloadLog.isDebugEnabled()) {
+                                logFramePayload(id + " <<", frame);
+                            }
+                        }
+
+                        @Override
+                        public void onFrameOutput(final HttpConnection connection, final int streamId, final RawFrame frame) {
+                            if (frameLog.isDebugEnabled()) {
+                                logFrameInfo(id + " >>", frame);
+                            }
+                            if (framePayloadLog.isDebugEnabled()) {
+                                logFramePayload(id + " >>", frame);
+                            }
+                        }
+
+                        @Override
+                        public void onInputFlowControl(final HttpConnection connection, final int streamId, final int delta, final int actualSize) {
+                            if (flowCtrlLog.isDebugEnabled()) {
+                                logFlowControl(id + " <<", streamId, delta, actualSize);
+                            }
+                        }
+
+                        @Override
+                        public void onOutputFlowControl(final HttpConnection connection, final int streamId, final int delta, final int actualSize) {
+                            if (flowCtrlLog.isDebugEnabled()) {
+                                logFlowControl(id + " >>", streamId, delta, actualSize);
+                            }
+                        }
+
+                    });
+            final LoggingIOSession loggingIOSession = new LoggingIOSession(ioSession, id, sessionLog, wireLog);
+            return new Http2OnlyClientProtocolNegotiator(loggingIOSession, http2StreamHandlerFactory);
+        } else {
+            final ClientHttp2StreamMultiplexerFactory http2StreamHandlerFactory = new ClientHttp2StreamMultiplexerFactory(
+                    httpProcessor,
+                    exchangeHandlerFactory,
+                    h2Config,
+                    charCodingConfig,
+                    null);
+            return new Http2OnlyClientProtocolNegotiator(ioSession, http2StreamHandlerFactory);
+        }
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/6228a736/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/Http2AsyncMainClientExec.java
----------------------------------------------------------------------
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/Http2AsyncMainClientExec.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/Http2AsyncMainClientExec.java
new file mode 100644
index 0000000..a954a56
--- /dev/null
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/Http2AsyncMainClientExec.java
@@ -0,0 +1,170 @@
+/*
+ * ====================================================================
+ * 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.async;
+
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.nio.ByteBuffer;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.hc.client5.http.HttpRoute;
+import org.apache.hc.client5.http.async.AsyncExecCallback;
+import org.apache.hc.client5.http.async.AsyncExecChain;
+import org.apache.hc.client5.http.async.AsyncExecChainHandler;
+import org.apache.hc.client5.http.async.AsyncExecRuntime;
+import org.apache.hc.client5.http.protocol.HttpClientContext;
+import org.apache.hc.core5.http.EntityDetails;
+import org.apache.hc.core5.http.Header;
+import org.apache.hc.core5.http.HttpException;
+import org.apache.hc.core5.http.HttpRequest;
+import org.apache.hc.core5.http.HttpResponse;
+import org.apache.hc.core5.http.message.RequestLine;
+import org.apache.hc.core5.http.nio.AsyncClientExchangeHandler;
+import org.apache.hc.core5.http.nio.AsyncDataConsumer;
+import org.apache.hc.core5.http.nio.AsyncEntityProducer;
+import org.apache.hc.core5.http.nio.CapacityChannel;
+import org.apache.hc.core5.http.nio.DataStreamChannel;
+import org.apache.hc.core5.http.nio.RequestChannel;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+class Http2AsyncMainClientExec implements AsyncExecChainHandler {
+
+    private final Logger log = LogManager.getLogger(getClass());
+
+    @Override
+    public void execute(
+            final HttpRequest request,
+            final AsyncEntityProducer entityProducer,
+            final AsyncExecChain.Scope scope,
+            final AsyncExecChain chain,
+            final AsyncExecCallback asyncExecCallback) throws HttpException, IOException {
+        final String exchangeId = scope.exchangeId;
+        final HttpRoute route = scope.route;
+        final HttpClientContext clientContext = scope.clientContext;
+        final AsyncExecRuntime execRuntime = scope.execRuntime;
+
+        if (log.isDebugEnabled()) {
+            log.debug(exchangeId + ": executing " + new RequestLine(request));
+        }
+
+        final AsyncClientExchangeHandler internalExchangeHandler = new AsyncClientExchangeHandler() {
+
+            private final AtomicReference<AsyncDataConsumer> entityConsumerRef = new AtomicReference<>(null);
+
+            @Override
+            public void releaseResources() {
+                final AsyncDataConsumer entityConsumer = entityConsumerRef.getAndSet(null);
+                if (entityConsumer != null) {
+                    entityConsumer.releaseResources();
+                }
+            }
+
+            @Override
+            public void failed(final Exception cause) {
+                execRuntime.markConnectionNonReusable();
+                asyncExecCallback.failed(cause);
+            }
+
+            @Override
+            public void cancel() {
+                failed(new InterruptedIOException());
+            }
+
+            @Override
+            public void produceRequest(final RequestChannel channel) throws HttpException, IOException {
+                channel.sendRequest(request, entityProducer);
+            }
+
+            @Override
+            public int available() {
+                return entityProducer.available();
+            }
+
+            @Override
+            public void produce(final DataStreamChannel channel) throws IOException {
+                entityProducer.produce(channel);
+            }
+
+            @Override
+            public void consumeInformation(final HttpResponse response) throws HttpException, IOException {
+            }
+
+            @Override
+            public void consumeResponse(final HttpResponse response, final EntityDetails entityDetails) throws HttpException, IOException {
+                entityConsumerRef.set(asyncExecCallback.handleResponse(response, entityDetails));
+                if (entityDetails == null) {
+                    execRuntime.validateConnection();
+                    asyncExecCallback.completed();
+                }
+            }
+
+            @Override
+            public void updateCapacity(final CapacityChannel capacityChannel) throws IOException {
+                final AsyncDataConsumer entityConsumer = entityConsumerRef.get();
+                if (entityConsumer != null) {
+                    entityConsumer.updateCapacity(capacityChannel);
+                } else {
+                    capacityChannel.update(Integer.MAX_VALUE);
+                }
+            }
+
+            @Override
+            public int consume(final ByteBuffer src) throws IOException {
+                final AsyncDataConsumer entityConsumer = entityConsumerRef.get();
+                if (entityConsumer != null) {
+                    return entityConsumer.consume(src);
+                } else {
+                    return Integer.MAX_VALUE;
+                }
+            }
+
+            @Override
+            public void streamEnd(final List<? extends Header> trailers) throws HttpException, IOException {
+                final AsyncDataConsumer entityConsumer = entityConsumerRef.getAndSet(null);
+                if (entityConsumer != null) {
+                    entityConsumer.streamEnd(trailers);
+                } else {
+                    execRuntime.validateConnection();
+                }
+                asyncExecCallback.completed();
+            }
+
+        };
+
+        if (log.isDebugEnabled()) {
+            execRuntime.execute(
+                    new LoggingAsyncClientExchangeHandler(log, exchangeId, internalExchangeHandler),
+                    clientContext);
+        } else {
+            execRuntime.execute(
+                    internalExchangeHandler, clientContext);
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/6228a736/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClientBuilder.java
----------------------------------------------------------------------
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClientBuilder.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClientBuilder.java
index 87ce294..91c28ae 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClientBuilder.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClientBuilder.java
@@ -736,7 +736,7 @@ public class HttpAsyncClientBuilder {
 
         final NamedElementChain<AsyncExecChainHandler> execChainDefinition = new NamedElementChain<>();
         execChainDefinition.addLast(
-                new AsyncMainClientExec(keepAliveStrategyCopy, userTokenHandlerCopy),
+                new HttpAsyncMainClientExec(keepAliveStrategyCopy, userTokenHandlerCopy),
                 ChainElements.MAIN_TRANSPORT.name());
 
         AuthenticationStrategy targetAuthStrategyCopy = this.targetAuthStrategy;

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/6228a736/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClientEventHandlerFactory.java
----------------------------------------------------------------------
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClientEventHandlerFactory.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClientEventHandlerFactory.java
index 95d9267..4a97d67 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClientEventHandlerFactory.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClientEventHandlerFactory.java
@@ -71,7 +71,7 @@ import org.apache.logging.log4j.Logger;
  * @since 5.0
  */
 @Contract(threading = ThreadingBehavior.IMMUTABLE)
-public class HttpAsyncClientEventHandlerFactory implements IOEventHandlerFactory {
+class HttpAsyncClientEventHandlerFactory implements IOEventHandlerFactory {
 
     private final Logger streamLog = LogManager.getLogger(InternalHttpAsyncClient.class);
     private final Logger wireLog = LogManager.getLogger("org.apache.hc.client5.http.wire");

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/6228a736/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClients.java
----------------------------------------------------------------------
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClients.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClients.java
index 56a59c3..dab74fd 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClients.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClients.java
@@ -88,6 +88,31 @@ public class HttpAsyncClients {
         return HttpAsyncClientBuilder.create().useSystemProperties().build();
     }
 
+    /**
+     * Creates builder object for construction of custom HTTP/2
+     * {@link CloseableHttpAsyncClient} instances optimized for HTTP/2 protocol
+     * and message multiplexing
+     */
+    public static Http2AsyncClientBuilder customHttp2() {
+        return Http2AsyncClientBuilder.create();
+    }
+
+    /**
+     * Creates HTTP/2 {@link CloseableHttpAsyncClient} instance with default configuration
+     * optimized for HTTP/2 protocol and message multiplexing.
+     */
+    public static CloseableHttpAsyncClient createHttp2Default() {
+        return Http2AsyncClientBuilder.create().build();
+    }
+
+    /**
+     * Creates HTTP/2 {@link CloseableHttpAsyncClient} instance with default configuration and
+     * system properties optimized for HTTP/2 protocol and message multiplexing.
+     */
+    public static CloseableHttpAsyncClient createHttp2System() {
+        return Http2AsyncClientBuilder.create().useSystemProperties().build();
+    }
+
     private static HttpProcessor createMinimalProtocolProcessor() {
         return new DefaultHttpProcessor(
                 new H2RequestContent(),
@@ -220,7 +245,7 @@ public class HttpAsyncClients {
             final TlsStrategy tlsStrategy) {
         final AsyncPushConsumerRegistry pushConsumerRegistry = new AsyncPushConsumerRegistry();
         return createMinimalHttp2AsyncClientImpl(
-                new HttpAsyncClientEventHandlerFactory(
+                new Http2AsyncClientEventHandlerFactory(
                         createMinimalProtocolProcessor(),
                         new HandlerFactory<AsyncPushConsumer>() {
 
@@ -230,11 +255,8 @@ public class HttpAsyncClients {
                             }
 
                         },
-                        HttpVersionPolicy.FORCE_HTTP_2,
                         h2Config,
-                        null,
-                        CharCodingConfig.DEFAULT,
-                        DefaultConnectionReuseStrategy.INSTANCE),
+                        CharCodingConfig.DEFAULT),
                 pushConsumerRegistry,
                 ioReactorConfig,
                 dnsResolver,

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/6228a736/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncMainClientExec.java
----------------------------------------------------------------------
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncMainClientExec.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncMainClientExec.java
new file mode 100644
index 0000000..012803c
--- /dev/null
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncMainClientExec.java
@@ -0,0 +1,229 @@
+/*
+ * ====================================================================
+ * 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.async;
+
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.nio.ByteBuffer;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.hc.client5.http.ConnectionKeepAliveStrategy;
+import org.apache.hc.client5.http.HttpRoute;
+import org.apache.hc.client5.http.UserTokenHandler;
+import org.apache.hc.client5.http.async.AsyncExecCallback;
+import org.apache.hc.client5.http.async.AsyncExecChain;
+import org.apache.hc.client5.http.async.AsyncExecChainHandler;
+import org.apache.hc.client5.http.async.AsyncExecRuntime;
+import org.apache.hc.client5.http.protocol.HttpClientContext;
+import org.apache.hc.core5.http.EntityDetails;
+import org.apache.hc.core5.http.Header;
+import org.apache.hc.core5.http.HttpException;
+import org.apache.hc.core5.http.HttpRequest;
+import org.apache.hc.core5.http.HttpResponse;
+import org.apache.hc.core5.http.HttpStatus;
+import org.apache.hc.core5.http.message.RequestLine;
+import org.apache.hc.core5.http.nio.AsyncClientExchangeHandler;
+import org.apache.hc.core5.http.nio.AsyncDataConsumer;
+import org.apache.hc.core5.http.nio.AsyncEntityProducer;
+import org.apache.hc.core5.http.nio.CapacityChannel;
+import org.apache.hc.core5.http.nio.DataStreamChannel;
+import org.apache.hc.core5.http.nio.RequestChannel;
+import org.apache.hc.core5.util.TimeValue;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+class HttpAsyncMainClientExec implements AsyncExecChainHandler {
+
+    private final Logger log = LogManager.getLogger(getClass());
+
+    private final ConnectionKeepAliveStrategy keepAliveStrategy;
+    private final UserTokenHandler userTokenHandler;
+
+    HttpAsyncMainClientExec(final ConnectionKeepAliveStrategy keepAliveStrategy, final UserTokenHandler userTokenHandler) {
+        this.keepAliveStrategy = keepAliveStrategy;
+        this.userTokenHandler = userTokenHandler;
+    }
+
+    @Override
+    public void execute(
+            final HttpRequest request,
+            final AsyncEntityProducer entityProducer,
+            final AsyncExecChain.Scope scope,
+            final AsyncExecChain chain,
+            final AsyncExecCallback asyncExecCallback) throws HttpException, IOException {
+        final String exchangeId = scope.exchangeId;
+        final HttpRoute route = scope.route;
+        final HttpClientContext clientContext = scope.clientContext;
+        final AsyncExecRuntime execRuntime = scope.execRuntime;
+
+        if (log.isDebugEnabled()) {
+            log.debug(exchangeId + ": executing " + new RequestLine(request));
+        }
+
+        final AtomicInteger messageCountDown = new AtomicInteger(2);
+        final AsyncClientExchangeHandler internalExchangeHandler = new AsyncClientExchangeHandler() {
+
+            private final AtomicReference<AsyncDataConsumer> entityConsumerRef = new AtomicReference<>(null);
+
+            @Override
+            public void releaseResources() {
+                final AsyncDataConsumer entityConsumer = entityConsumerRef.getAndSet(null);
+                if (entityConsumer != null) {
+                    entityConsumer.releaseResources();
+                }
+            }
+
+            @Override
+            public void failed(final Exception cause) {
+                execRuntime.markConnectionNonReusable();
+                asyncExecCallback.failed(cause);
+            }
+
+            @Override
+            public void cancel() {
+                failed(new InterruptedIOException());
+            }
+
+            @Override
+            public void produceRequest(final RequestChannel channel) throws HttpException, IOException {
+                channel.sendRequest(request, entityProducer);
+                if (entityProducer == null) {
+                    messageCountDown.decrementAndGet();
+                }
+            }
+
+            @Override
+            public int available() {
+                return entityProducer.available();
+            }
+
+            @Override
+            public void produce(final DataStreamChannel channel) throws IOException {
+                entityProducer.produce(new DataStreamChannel() {
+
+                    @Override
+                    public void requestOutput() {
+                        channel.requestOutput();
+                    }
+
+                    @Override
+                    public int write(final ByteBuffer src) throws IOException {
+                        return channel.write(src);
+                    }
+
+                    @Override
+                    public void endStream(final List<? extends Header> trailers) throws IOException {
+                        channel.endStream(trailers);
+                        if (messageCountDown.decrementAndGet() <= 0) {
+                            asyncExecCallback.completed();
+                        }
+                    }
+
+                    @Override
+                    public void endStream() throws IOException {
+                        channel.endStream();
+                        if (messageCountDown.decrementAndGet() <= 0) {
+                            asyncExecCallback.completed();
+                        }
+                    }
+
+                });
+            }
+
+            @Override
+            public void consumeInformation(final HttpResponse response) throws HttpException, IOException {
+            }
+
+            @Override
+            public void consumeResponse(final HttpResponse response, final EntityDetails entityDetails) throws HttpException, IOException {
+                entityConsumerRef.set(asyncExecCallback.handleResponse(response, entityDetails));
+                if (response.getCode() >= HttpStatus.SC_CLIENT_ERROR) {
+                    messageCountDown.decrementAndGet();
+                }
+                final TimeValue keepAliveDuration = keepAliveStrategy.getKeepAliveDuration(response, clientContext);
+                Object userToken = clientContext.getUserToken();
+                if (userToken == null) {
+                    userToken = userTokenHandler.getUserToken(route, clientContext);
+                    clientContext.setAttribute(HttpClientContext.USER_TOKEN, userToken);
+                }
+                execRuntime.markConnectionReusable(userToken, keepAliveDuration);
+                if (entityDetails == null) {
+                    execRuntime.validateConnection();
+                    if (messageCountDown.decrementAndGet() <= 0) {
+                        asyncExecCallback.completed();
+                    }
+                }
+            }
+
+            @Override
+            public void updateCapacity(final CapacityChannel capacityChannel) throws IOException {
+                final AsyncDataConsumer entityConsumer = entityConsumerRef.get();
+                if (entityConsumer != null) {
+                    entityConsumer.updateCapacity(capacityChannel);
+                } else {
+                    capacityChannel.update(Integer.MAX_VALUE);
+                }
+            }
+
+            @Override
+            public int consume(final ByteBuffer src) throws IOException {
+                final AsyncDataConsumer entityConsumer = entityConsumerRef.get();
+                if (entityConsumer != null) {
+                    return entityConsumer.consume(src);
+                } else {
+                    return Integer.MAX_VALUE;
+                }
+            }
+
+            @Override
+            public void streamEnd(final List<? extends Header> trailers) throws HttpException, IOException {
+                final AsyncDataConsumer entityConsumer = entityConsumerRef.getAndSet(null);
+                if (entityConsumer != null) {
+                    entityConsumer.streamEnd(trailers);
+                } else {
+                    execRuntime.validateConnection();
+                }
+                if (messageCountDown.decrementAndGet() <= 0) {
+                    asyncExecCallback.completed();
+                }
+            }
+
+        };
+
+        if (log.isDebugEnabled()) {
+            execRuntime.execute(
+                    new LoggingAsyncClientExchangeHandler(log, exchangeId, internalExchangeHandler),
+                    clientContext);
+        } else {
+            execRuntime.execute(
+                    internalExchangeHandler, clientContext);
+        }
+    }
+
+}


[6/6] httpcomponents-client git commit: * HTTP/2 multiplexing HttpAsyncClient implementation * Restructured integration tests to reduce test duplication

Posted by ol...@apache.org.
* HTTP/2 multiplexing HttpAsyncClient implementation
* Restructured integration tests to reduce test duplication


Project: http://git-wip-us.apache.org/repos/asf/httpcomponents-client/repo
Commit: http://git-wip-us.apache.org/repos/asf/httpcomponents-client/commit/6228a736
Tree: http://git-wip-us.apache.org/repos/asf/httpcomponents-client/tree/6228a736
Diff: http://git-wip-us.apache.org/repos/asf/httpcomponents-client/diff/6228a736

Branch: refs/heads/master
Commit: 6228a7361350df0c1ec99ebfd36034438512f068
Parents: 703b796
Author: Oleg Kalnichevski <ol...@apache.org>
Authored: Mon Nov 13 10:50:27 2017 +0100
Committer: Oleg Kalnichevski <ol...@apache.org>
Committed: Mon Nov 13 22:30:12 2017 +0100

----------------------------------------------------------------------
 .../async/AbstractHttp1IntegrationTestBase.java | 133 +++
 .../AbstractHttpAsyncClientAuthentication.java  | 565 ++++++++++++
 .../AbstractHttpAsyncFundamentalsTest.java      | 136 +++
 .../async/AbstractHttpAsyncRedirectsTest.java   | 825 +++++++++++++++++
 .../async/AbstractIntegrationTestBase.java      | 114 +++
 .../testing/async/AbstractServerTestBase.java   |  96 ++
 .../testing/async/IntegrationTestBase.java      | 108 ---
 .../testing/async/LocalAsyncServerTestBase.java | 120 ---
 .../testing/async/TestAsyncRedirects.java       | 910 -------------------
 .../async/TestAsyncStatefulConnManagement.java  | 261 ------
 .../testing/async/TestClientAuthentication.java | 598 ------------
 .../client5/testing/async/TestHttp1Async.java   | 198 ++++
 .../testing/async/TestHttp1AsyncRedirects.java  | 254 ++++++
 .../TestHttp1AsyncStatefulConnManagement.java   | 319 +++++++
 .../async/TestHttp1ClientAuthentication.java    | 180 ++++
 .../client5/testing/async/TestHttp2Async.java   |  88 ++
 .../testing/async/TestHttp2AsyncMinimal.java    | 171 +---
 .../testing/async/TestHttp2AsyncRedirect.java   |  88 ++
 .../async/TestHttp2ClientAuthentication.java    |  97 ++
 .../hc/client5/testing/async/TestHttpAsync.java | 252 -----
 .../testing/async/TestHttpAsyncMinimal.java     | 176 +---
 .../http/impl/async/AsyncExecRuntimeImpl.java   | 276 ------
 .../http/impl/async/AsyncMainClientExec.java    | 229 -----
 .../impl/async/Http2AsyncClientBuilder.java     | 885 ++++++++++++++++++
 .../Http2AsyncClientEventHandlerFactory.java    | 194 ++++
 .../impl/async/Http2AsyncMainClientExec.java    | 170 ++++
 .../http/impl/async/HttpAsyncClientBuilder.java |   2 +-
 .../HttpAsyncClientEventHandlerFactory.java     |   2 +-
 .../http/impl/async/HttpAsyncClients.java       |  32 +-
 .../impl/async/HttpAsyncMainClientExec.java     | 229 +++++
 .../async/InternalAbstractHttpAsyncClient.java  | 308 +++++++
 .../impl/async/InternalHttp2AsyncClient.java    |  88 ++
 .../async/InternalHttp2AsyncExecRuntime.java    | 245 +++++
 .../impl/async/InternalHttpAsyncClient.java     | 254 +-----
 .../async/InternalHttpAsyncExecRuntime.java     | 276 ++++++
 35 files changed, 5563 insertions(+), 3316 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/6228a736/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/AbstractHttp1IntegrationTestBase.java
----------------------------------------------------------------------
diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/AbstractHttp1IntegrationTestBase.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/AbstractHttp1IntegrationTestBase.java
new file mode 100644
index 0000000..18dd89e
--- /dev/null
+++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/AbstractHttp1IntegrationTestBase.java
@@ -0,0 +1,133 @@
+/*
+ * ====================================================================
+ * 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.testing.async;
+
+import java.net.InetSocketAddress;
+import java.util.concurrent.Future;
+
+import org.apache.hc.client5.http.config.RequestConfig;
+import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
+import org.apache.hc.client5.http.impl.async.HttpAsyncClientBuilder;
+import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManager;
+import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBuilder;
+import org.apache.hc.client5.http.ssl.H2TlsStrategy;
+import org.apache.hc.client5.testing.SSLTestContexts;
+import org.apache.hc.core5.function.Decorator;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.URIScheme;
+import org.apache.hc.core5.http.config.H1Config;
+import org.apache.hc.core5.http.impl.HttpProcessors;
+import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler;
+import org.apache.hc.core5.http.protocol.HttpProcessor;
+import org.apache.hc.core5.io.ShutdownType;
+import org.apache.hc.core5.reactor.ListenerEndpoint;
+import org.junit.Rule;
+import org.junit.rules.ExternalResource;
+
+public abstract class AbstractHttp1IntegrationTestBase extends AbstractServerTestBase {
+
+    public AbstractHttp1IntegrationTestBase(final URIScheme scheme) {
+        super(scheme);
+    }
+
+    public AbstractHttp1IntegrationTestBase() {
+        super(URIScheme.HTTP);
+    }
+
+    protected HttpAsyncClientBuilder clientBuilder;
+    protected PoolingAsyncClientConnectionManager connManager;
+    protected CloseableHttpAsyncClient httpclient;
+
+    @Rule
+    public ExternalResource connManagerResource = new ExternalResource() {
+
+        @Override
+        protected void before() throws Throwable {
+            connManager = PoolingAsyncClientConnectionManagerBuilder.create()
+                    .setTlsStrategy(new H2TlsStrategy(SSLTestContexts.createClientSSLContext()))
+                    .build();
+        }
+
+        @Override
+        protected void after() {
+            if (connManager != null) {
+                connManager.close();
+                connManager = null;
+            }
+        }
+
+    };
+
+    @Rule
+    public ExternalResource clientResource = new ExternalResource() {
+
+        @Override
+        protected void before() throws Throwable {
+            clientBuilder = HttpAsyncClientBuilder.create()
+                    .setDefaultRequestConfig(RequestConfig.custom()
+                            .setSocketTimeout(TIMEOUT)
+                            .setConnectTimeout(TIMEOUT)
+                            .setConnectionRequestTimeout(TIMEOUT)
+                            .build())
+                    .setConnectionManager(connManager);
+        }
+
+        @Override
+        protected void after() {
+            if (httpclient != null) {
+                httpclient.shutdown(ShutdownType.GRACEFUL);
+                httpclient = null;
+            }
+        }
+
+    };
+
+    public HttpHost start(
+            final HttpProcessor httpProcessor,
+            final Decorator<AsyncServerExchangeHandler> exchangeHandlerDecorator,
+            final H1Config h1Config) throws Exception {
+        server.start(httpProcessor, exchangeHandlerDecorator, h1Config);
+        final Future<ListenerEndpoint> endpointFuture = server.listen(new InetSocketAddress(0));
+        httpclient = clientBuilder.build();
+        httpclient.start();
+        final ListenerEndpoint endpoint = endpointFuture.get();
+        final InetSocketAddress address = (InetSocketAddress) endpoint.getAddress();
+        return new HttpHost("localhost", address.getPort(), scheme.name());
+    }
+
+    public HttpHost start(
+            final HttpProcessor httpProcessor,
+            final H1Config h1Config) throws Exception {
+        return start(httpProcessor, null, h1Config);
+    }
+
+    public HttpHost start() throws Exception {
+        return start(HttpProcessors.server(), H1Config.DEFAULT);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/6228a736/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/AbstractHttpAsyncClientAuthentication.java
----------------------------------------------------------------------
diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/AbstractHttpAsyncClientAuthentication.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/AbstractHttpAsyncClientAuthentication.java
new file mode 100644
index 0000000..7dc8098
--- /dev/null
+++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/AbstractHttpAsyncClientAuthentication.java
@@ -0,0 +1,565 @@
+/*
+ * ====================================================================
+ * 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.testing.async;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Future;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.apache.hc.client5.http.AuthenticationStrategy;
+import org.apache.hc.client5.http.async.methods.SimpleHttpRequest;
+import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
+import org.apache.hc.client5.http.auth.AuthChallenge;
+import org.apache.hc.client5.http.auth.AuthScheme;
+import org.apache.hc.client5.http.auth.AuthSchemeProvider;
+import org.apache.hc.client5.http.auth.AuthScope;
+import org.apache.hc.client5.http.auth.ChallengeType;
+import org.apache.hc.client5.http.auth.Credentials;
+import org.apache.hc.client5.http.auth.CredentialsStore;
+import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;
+import org.apache.hc.client5.http.config.RequestConfig;
+import org.apache.hc.client5.http.impl.DefaultAuthenticationStrategy;
+import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
+import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider;
+import org.apache.hc.client5.http.impl.auth.BasicScheme;
+import org.apache.hc.client5.http.protocol.HttpClientContext;
+import org.apache.hc.client5.testing.BasicTestAuthenticator;
+import org.apache.hc.client5.testing.auth.Authenticator;
+import org.apache.hc.core5.function.Decorator;
+import org.apache.hc.core5.function.Supplier;
+import org.apache.hc.core5.http.ContentType;
+import org.apache.hc.core5.http.HttpException;
+import org.apache.hc.core5.http.HttpHeaders;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.HttpResponse;
+import org.apache.hc.core5.http.HttpStatus;
+import org.apache.hc.core5.http.HttpVersion;
+import org.apache.hc.core5.http.URIScheme;
+import org.apache.hc.core5.http.config.H1Config;
+import org.apache.hc.core5.http.config.Lookup;
+import org.apache.hc.core5.http.config.Registry;
+import org.apache.hc.core5.http.config.RegistryBuilder;
+import org.apache.hc.core5.http.impl.HttpProcessors;
+import org.apache.hc.core5.http.message.BasicHeader;
+import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler;
+import org.apache.hc.core5.http.protocol.HttpContext;
+import org.apache.hc.core5.http.protocol.HttpCoreContext;
+import org.apache.hc.core5.http2.config.H2Config;
+import org.apache.hc.core5.http2.impl.Http2Processors;
+import org.apache.hc.core5.net.URIAuthority;
+import org.junit.Assert;
+import org.junit.Test;
+
+public abstract class AbstractHttpAsyncClientAuthentication<T extends CloseableHttpAsyncClient> extends AbstractIntegrationTestBase<T> {
+
+    protected final HttpVersion protocolVersion;
+
+    public AbstractHttpAsyncClientAuthentication(final URIScheme scheme, final HttpVersion protocolVersion) {
+        super(scheme);
+        this.protocolVersion = protocolVersion;
+    }
+
+    @Override
+    public final HttpHost start() throws Exception {
+        return start(new Decorator<AsyncServerExchangeHandler>() {
+
+            @Override
+            public AsyncServerExchangeHandler decorate(final AsyncServerExchangeHandler requestHandler) {
+                return new AuthenticatingAsyncDecorator(requestHandler, new BasicTestAuthenticator("test:test", "test realm"));
+            }
+
+        });
+    }
+
+    public final HttpHost start(
+            final Decorator<AsyncServerExchangeHandler> exchangeHandlerDecorator) throws Exception {
+        if (protocolVersion.greaterEquals(HttpVersion.HTTP_2_0)) {
+            return super.start(
+                    Http2Processors.server(),
+                    exchangeHandlerDecorator,
+                    H2Config.DEFAULT);
+        } else {
+            return super.start(
+                    HttpProcessors.server(),
+                    exchangeHandlerDecorator,
+                    H1Config.DEFAULT);
+        }
+    }
+
+    abstract void setDefaultAuthSchemeRegistry(Lookup<AuthSchemeProvider> authSchemeRegistry);
+
+    abstract void setTargetAuthenticationStrategy(AuthenticationStrategy targetAuthStrategy);
+
+    static class TestCredentialsProvider implements CredentialsStore {
+
+        private final Credentials creds;
+        private AuthScope authscope;
+
+        TestCredentialsProvider(final Credentials creds) {
+            super();
+            this.creds = creds;
+        }
+
+        @Override
+        public void clear() {
+        }
+
+        @Override
+        public Credentials getCredentials(final AuthScope authscope, final HttpContext context) {
+            this.authscope = authscope;
+            return this.creds;
+        }
+
+        @Override
+        public void setCredentials(final AuthScope authscope, final Credentials credentials) {
+        }
+
+        public AuthScope getAuthScope() {
+            return this.authscope;
+        }
+
+    }
+
+    @Test
+    public void testBasicAuthenticationNoCreds() throws Exception {
+        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
+
+            @Override
+            public AsyncServerExchangeHandler get() {
+                return new AsyncEchoHandler();
+            }
+
+        });
+        final HttpHost target = start();
+
+        final TestCredentialsProvider credsProvider = new TestCredentialsProvider(null);
+        final HttpClientContext context = HttpClientContext.create();
+        context.setCredentialsProvider(credsProvider);
+
+        final Future<SimpleHttpResponse> future = httpclient.execute(SimpleHttpRequest.get(target, "/"), context, null);
+        final HttpResponse response = future.get();
+
+        Assert.assertNotNull(response);
+        Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, response.getCode());
+        final AuthScope authscope = credsProvider.getAuthScope();
+        Assert.assertNotNull(authscope);
+        Assert.assertEquals("test realm", authscope.getRealm());
+    }
+
+    @Test
+    public void testBasicAuthenticationFailure() throws Exception {
+        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
+
+            @Override
+            public AsyncServerExchangeHandler get() {
+                return new AsyncEchoHandler();
+            }
+
+        });
+        final HttpHost target = start();
+
+        final TestCredentialsProvider credsProvider = new TestCredentialsProvider(
+                new UsernamePasswordCredentials("test", "all-wrong".toCharArray()));
+        final HttpClientContext context = HttpClientContext.create();
+        context.setCredentialsProvider(credsProvider);
+
+        final Future<SimpleHttpResponse> future = httpclient.execute(SimpleHttpRequest.get(target, "/"), context, null);
+        final HttpResponse response = future.get();
+
+        Assert.assertNotNull(response);
+        Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, response.getCode());
+        final AuthScope authscope = credsProvider.getAuthScope();
+        Assert.assertNotNull(authscope);
+        Assert.assertEquals("test realm", authscope.getRealm());
+    }
+
+    @Test
+    public void testBasicAuthenticationSuccess() throws Exception {
+        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
+
+            @Override
+            public AsyncServerExchangeHandler get() {
+                return new AsyncEchoHandler();
+            }
+
+        });
+        final HttpHost target = start();
+
+        final TestCredentialsProvider credsProvider = new TestCredentialsProvider(
+                new UsernamePasswordCredentials("test", "test".toCharArray()));
+        final HttpClientContext context = HttpClientContext.create();
+        context.setCredentialsProvider(credsProvider);
+
+        final Future<SimpleHttpResponse> future = httpclient.execute(SimpleHttpRequest.get(target, "/"), context, null);
+        final HttpResponse response = future.get();
+
+        Assert.assertNotNull(response);
+        Assert.assertEquals(HttpStatus.SC_OK, response.getCode());
+        final AuthScope authscope = credsProvider.getAuthScope();
+        Assert.assertNotNull(authscope);
+        Assert.assertEquals("test realm", authscope.getRealm());
+    }
+
+    @Test
+    public void testBasicAuthenticationWithEntitySuccess() throws Exception {
+        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
+
+            @Override
+            public AsyncServerExchangeHandler get() {
+                return new AsyncEchoHandler();
+            }
+
+        });
+        final HttpHost target = start();
+
+        final TestCredentialsProvider credsProvider = new TestCredentialsProvider(
+                new UsernamePasswordCredentials("test", "test".toCharArray()));
+        final HttpClientContext context = HttpClientContext.create();
+        context.setCredentialsProvider(credsProvider);
+
+        final SimpleHttpRequest put = SimpleHttpRequest.put(target, "/");
+        put.setBodyText("Some important stuff", ContentType.TEXT_PLAIN);
+        final Future<SimpleHttpResponse> future = httpclient.execute(put, context, null);
+        final HttpResponse response = future.get();
+
+        Assert.assertNotNull(response);
+        Assert.assertEquals(HttpStatus.SC_OK, response.getCode());
+        final AuthScope authscope = credsProvider.getAuthScope();
+        Assert.assertNotNull(authscope);
+        Assert.assertEquals("test realm", authscope.getRealm());
+    }
+
+    @Test
+    public void testBasicAuthenticationExpectationFailure() throws Exception {
+        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
+
+            @Override
+            public AsyncServerExchangeHandler get() {
+                return new AsyncEchoHandler();
+            }
+
+        });
+        final HttpHost target = start();
+
+        final TestCredentialsProvider credsProvider = new TestCredentialsProvider(
+                new UsernamePasswordCredentials("test", "all-wrong".toCharArray()));
+        final HttpClientContext context = HttpClientContext.create();
+        context.setCredentialsProvider(credsProvider);
+        context.setRequestConfig(RequestConfig.custom().setExpectContinueEnabled(true).build());
+
+        final SimpleHttpRequest put = SimpleHttpRequest.put(target, "/");
+        put.setBodyText("Some important stuff", ContentType.TEXT_PLAIN);
+        final Future<SimpleHttpResponse> future = httpclient.execute(put, context, null);
+        final HttpResponse response = future.get();
+
+        Assert.assertNotNull(response);
+        Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, response.getCode());
+    }
+
+    @Test
+    public void testBasicAuthenticationExpectationSuccess() throws Exception {
+        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
+
+            @Override
+            public AsyncServerExchangeHandler get() {
+                return new AsyncEchoHandler();
+            }
+
+        });
+        final HttpHost target = start();
+
+        final TestCredentialsProvider credsProvider = new TestCredentialsProvider(
+                new UsernamePasswordCredentials("test", "test".toCharArray()));
+        final HttpClientContext context = HttpClientContext.create();
+        context.setCredentialsProvider(credsProvider);
+        context.setRequestConfig(RequestConfig.custom().setExpectContinueEnabled(true).build());
+
+        final SimpleHttpRequest put = SimpleHttpRequest.put(target, "/");
+        put.setBodyText("Some important stuff", ContentType.TEXT_PLAIN);
+        final Future<SimpleHttpResponse> future = httpclient.execute(put, context, null);
+        final HttpResponse response = future.get();
+
+        Assert.assertNotNull(response);
+        Assert.assertEquals(HttpStatus.SC_OK, response.getCode());
+        final AuthScope authscope = credsProvider.getAuthScope();
+        Assert.assertNotNull(authscope);
+        Assert.assertEquals("test realm", authscope.getRealm());
+    }
+
+    @Test
+    public void testBasicAuthenticationCredentialsCaching() throws Exception {
+        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
+
+            @Override
+            public AsyncServerExchangeHandler get() {
+                return new AsyncEchoHandler();
+            }
+
+        });
+
+        final AtomicLong count = new AtomicLong(0);
+        setTargetAuthenticationStrategy(new DefaultAuthenticationStrategy() {
+
+            @Override
+            public List<AuthScheme> select(
+                    final ChallengeType challengeType,
+                    final Map<String, AuthChallenge> challenges,
+                    final HttpContext context) {
+                count.incrementAndGet();
+                return super.select(challengeType, challenges, context);
+            }
+        });
+        final HttpHost target = start();
+
+        final BasicCredentialsProvider credsProvider = new BasicCredentialsProvider();
+        credsProvider.setCredentials(AuthScope.ANY,
+                new UsernamePasswordCredentials("test", "test".toCharArray()));
+        final HttpClientContext context = HttpClientContext.create();
+        context.setCredentialsProvider(credsProvider);
+
+        final Future<SimpleHttpResponse> future1 = httpclient.execute(SimpleHttpRequest.get(target, "/"), context, null);
+        final HttpResponse response1 = future1.get();
+        Assert.assertNotNull(response1);
+        Assert.assertEquals(HttpStatus.SC_OK, response1.getCode());
+
+        final Future<SimpleHttpResponse> future2 = httpclient.execute(SimpleHttpRequest.get(target, "/"), context, null);
+        final HttpResponse response2 = future2.get();
+        Assert.assertNotNull(response2);
+        Assert.assertEquals(HttpStatus.SC_OK, response2.getCode());
+
+        Assert.assertEquals(1, count.get());
+    }
+
+    @Test
+    public void testAuthenticationUserinfoInRequestSuccess() throws Exception {
+        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
+
+            @Override
+            public AsyncServerExchangeHandler get() {
+                return new AsyncEchoHandler();
+            }
+
+        });
+        final HttpHost target = start();
+
+        final HttpClientContext context = HttpClientContext.create();
+        final Future<SimpleHttpResponse> future = httpclient.execute(
+                SimpleHttpRequest.get(target.getSchemeName() + "://test:test@" +  target.toHostString() + "/"), context, null);
+        final SimpleHttpResponse response = future.get();
+
+        Assert.assertNotNull(response);
+        Assert.assertEquals(HttpStatus.SC_OK, response.getCode());
+    }
+
+    @Test
+    public void testAuthenticationUserinfoInRequestFailure() throws Exception {
+        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
+
+            @Override
+            public AsyncServerExchangeHandler get() {
+                return new AsyncEchoHandler();
+            }
+
+        });
+        final HttpHost target = start();
+
+        final HttpClientContext context = HttpClientContext.create();
+        final Future<SimpleHttpResponse> future = httpclient.execute(
+                SimpleHttpRequest.get(target.getSchemeName() + "://test:all-worng@" +  target.toHostString() + "/"), context, null);
+        final SimpleHttpResponse response = future.get();
+
+        Assert.assertNotNull(response);
+        Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, response.getCode());
+    }
+
+    @Test
+    public void testAuthenticationUserinfoInRedirectSuccess() throws Exception {
+        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
+
+            @Override
+            public AsyncServerExchangeHandler get() {
+                return new AsyncEchoHandler();
+            }
+
+        });
+        final HttpHost target = start();
+        server.register("/thatway", new Supplier<AsyncServerExchangeHandler>() {
+
+            @Override
+            public AsyncServerExchangeHandler get() {
+                return new AbstractSimpleServerExchangeHandler() {
+
+                    @Override
+                    protected SimpleHttpResponse handle(
+                            final SimpleHttpRequest request, final HttpCoreContext context) throws HttpException {
+                        final SimpleHttpResponse response = new SimpleHttpResponse(HttpStatus.SC_MOVED_PERMANENTLY);
+                        response.addHeader(new BasicHeader("Location", target.getSchemeName() + "://test:test@" + target.toHostString() + "/"));
+                        return response;
+                    }
+                };
+            }
+
+        });
+
+        final HttpClientContext context = HttpClientContext.create();
+        final Future<SimpleHttpResponse> future = httpclient.execute(
+                SimpleHttpRequest.get(target.getSchemeName() + "://test:test@" +  target.toHostString() + "/thatway"), context, null);
+        final SimpleHttpResponse response = future.get();
+
+        Assert.assertNotNull(response);
+        Assert.assertEquals(HttpStatus.SC_OK, response.getCode());
+    }
+
+    @Test
+    public void testReauthentication() throws Exception {
+        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
+
+            @Override
+            public AsyncServerExchangeHandler get() {
+                return new AsyncEchoHandler();
+            }
+
+        });
+        final TestCredentialsProvider credsProvider = new TestCredentialsProvider(
+                new UsernamePasswordCredentials("test", "test".toCharArray()));
+
+        final Registry<AuthSchemeProvider> authSchemeRegistry = RegistryBuilder.<AuthSchemeProvider>create()
+                .register("MyBasic", new AuthSchemeProvider() {
+
+                    @Override
+                    public AuthScheme create(final HttpContext context) {
+                        return new BasicScheme() {
+
+                            @Override
+                            public String getName() {
+                                return "MyBasic";
+                            }
+
+                        };
+                    }
+
+                })
+                .build();
+        setDefaultAuthSchemeRegistry(authSchemeRegistry);
+
+        final Authenticator authenticator = new BasicTestAuthenticator("test:test", "test realm") {
+
+            private final AtomicLong count = new AtomicLong(0);
+
+            @Override
+            public boolean authenticate(final URIAuthority authority, final String requestUri, final String credentials) {
+                final boolean authenticated = super.authenticate(authority, requestUri, credentials);
+                if (authenticated) {
+                    if (this.count.incrementAndGet() % 4 != 0) {
+                        return true;
+                    } else {
+                        return false;
+                    }
+                } else {
+                    return false;
+                }
+            }
+        };
+
+        final HttpHost target = start(
+                new Decorator<AsyncServerExchangeHandler>() {
+
+                    @Override
+                    public AsyncServerExchangeHandler decorate(final AsyncServerExchangeHandler exchangeHandler) {
+                        return new AuthenticatingAsyncDecorator(exchangeHandler, authenticator) {
+
+                            @Override
+                            protected void customizeUnauthorizedResponse(final HttpResponse unauthorized) {
+                                unauthorized.removeHeaders(HttpHeaders.WWW_AUTHENTICATE);
+                                unauthorized.addHeader(HttpHeaders.WWW_AUTHENTICATE, "MyBasic realm=\"test realm\"");
+                            }
+
+                        };
+                    }
+
+                });
+
+        final RequestConfig config = RequestConfig.custom()
+                .setTargetPreferredAuthSchemes(Arrays.asList("MyBasic"))
+                .build();
+        final HttpClientContext context = HttpClientContext.create();
+        context.setCredentialsProvider(credsProvider);
+
+        for (int i = 0; i < 10; i++) {
+            final SimpleHttpRequest request = SimpleHttpRequest.get(target, "/");
+            request.setConfig(config);
+            final Future<SimpleHttpResponse> future = httpclient.execute(request, context, null);
+            final SimpleHttpResponse response = future.get();
+            Assert.assertNotNull(response);
+            Assert.assertEquals(HttpStatus.SC_OK, response.getCode());
+        }
+    }
+
+    @Test
+    public void testAuthenticationFallback() throws Exception {
+        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
+
+            @Override
+            public AsyncServerExchangeHandler get() {
+                return new AsyncEchoHandler();
+            }
+
+        });
+        final HttpHost target = start(
+                new Decorator<AsyncServerExchangeHandler>() {
+
+                    @Override
+                    public AsyncServerExchangeHandler decorate(final AsyncServerExchangeHandler exchangeHandler) {
+                        return new AuthenticatingAsyncDecorator(exchangeHandler, new BasicTestAuthenticator("test:test", "test realm")) {
+
+                            @Override
+                            protected void customizeUnauthorizedResponse(final HttpResponse unauthorized) {
+                                unauthorized.addHeader(HttpHeaders.WWW_AUTHENTICATE, "Digest realm=\"test realm\" invalid");
+                            }
+
+                        };
+                    }
+
+                });
+
+        final TestCredentialsProvider credsProvider = new TestCredentialsProvider(
+                new UsernamePasswordCredentials("test", "test".toCharArray()));
+        final HttpClientContext context = HttpClientContext.create();
+        context.setCredentialsProvider(credsProvider);
+
+        final Future<SimpleHttpResponse> future = httpclient.execute(SimpleHttpRequest.get(target, "/"), context, null);
+        final SimpleHttpResponse response = future.get();
+        Assert.assertNotNull(response);
+        Assert.assertEquals(HttpStatus.SC_OK, response.getCode());
+        final AuthScope authscope = credsProvider.getAuthScope();
+        Assert.assertNotNull(authscope);
+        Assert.assertEquals("test realm", authscope.getRealm());
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/6228a736/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/AbstractHttpAsyncFundamentalsTest.java
----------------------------------------------------------------------
diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/AbstractHttpAsyncFundamentalsTest.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/AbstractHttpAsyncFundamentalsTest.java
new file mode 100644
index 0000000..73c7409
--- /dev/null
+++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/AbstractHttpAsyncFundamentalsTest.java
@@ -0,0 +1,136 @@
+/*
+ * ====================================================================
+ * 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.testing.async;
+
+import java.util.LinkedList;
+import java.util.Queue;
+import java.util.Random;
+import java.util.concurrent.Future;
+
+import org.apache.hc.client5.http.async.methods.AsyncRequestBuilder;
+import org.apache.hc.client5.http.async.methods.SimpleHttpRequest;
+import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
+import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
+import org.apache.hc.client5.http.protocol.HttpClientContext;
+import org.apache.hc.core5.http.ContentType;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.HttpResponse;
+import org.apache.hc.core5.http.Message;
+import org.apache.hc.core5.http.URIScheme;
+import org.apache.hc.core5.http.nio.BasicResponseConsumer;
+import org.apache.hc.core5.http.nio.entity.BasicAsyncEntityConsumer;
+import org.hamcrest.CoreMatchers;
+import org.junit.Assert;
+import org.junit.Test;
+
+public abstract class AbstractHttpAsyncFundamentalsTest<T extends CloseableHttpAsyncClient> extends AbstractIntegrationTestBase<T> {
+
+    public AbstractHttpAsyncFundamentalsTest(final URIScheme scheme) {
+        super(scheme);
+    }
+
+    @Test
+    public void testSequenctialGetRequests() throws Exception {
+        final HttpHost target = start();
+        for (int i = 0; i < 3; i++) {
+            final Future<SimpleHttpResponse> future = httpclient.execute(
+                    SimpleHttpRequest.get(target, "/random/2048"), null);
+            final SimpleHttpResponse response = future.get();
+            Assert.assertThat(response, CoreMatchers.notNullValue());
+            Assert.assertThat(response.getCode(), CoreMatchers.equalTo(200));
+            final String body = response.getBodyText();
+            Assert.assertThat(body, CoreMatchers.notNullValue());
+            Assert.assertThat(body.length(), CoreMatchers.equalTo(2048));
+        }
+    }
+
+    @Test
+    public void testSequenctialHeadRequests() throws Exception {
+        final HttpHost target = start();
+        for (int i = 0; i < 3; i++) {
+            final Future<SimpleHttpResponse> future = httpclient.execute(
+                    SimpleHttpRequest.head(target, "/random/2048"), null);
+            final SimpleHttpResponse response = future.get();
+            Assert.assertThat(response, CoreMatchers.notNullValue());
+            Assert.assertThat(response.getCode(), CoreMatchers.equalTo(200));
+            final String body = response.getBodyText();
+            Assert.assertThat(body, CoreMatchers.nullValue());
+        }
+    }
+
+    @Test
+    public void testSequenctialPostRequests() throws Exception {
+        final HttpHost target = start();
+        for (int i = 0; i < 3; i++) {
+            final byte[] b1 = new byte[1024];
+            final Random rnd = new Random(System.currentTimeMillis());
+            rnd.nextBytes(b1);
+            final Future<Message<HttpResponse, byte[]>> future = httpclient.execute(
+                    AsyncRequestBuilder.post(target, "/echo/")
+                        .setEntity(b1, ContentType.APPLICATION_OCTET_STREAM)
+                        .build(),
+                    new BasicResponseConsumer<>(new BasicAsyncEntityConsumer()), HttpClientContext.create(), null);
+            final Message<HttpResponse, byte[]> responseMessage = future.get();
+            Assert.assertThat(responseMessage, CoreMatchers.notNullValue());
+            final HttpResponse response = responseMessage.getHead();
+            Assert.assertThat(response.getCode(), CoreMatchers.equalTo(200));
+            final byte[] b2 = responseMessage.getBody();
+            Assert.assertThat(b1, CoreMatchers.equalTo(b2));
+        }
+    }
+
+    @Test
+    public void testConcurrentPostRequests() throws Exception {
+        final HttpHost target = start();
+        final byte[] b1 = new byte[1024];
+        final Random rnd = new Random(System.currentTimeMillis());
+        rnd.nextBytes(b1);
+
+        final int reqCount = 20;
+
+        final Queue<Future<Message<HttpResponse, byte[]>>> queue = new LinkedList<>();
+        for (int i = 0; i < reqCount; i++) {
+            final Future<Message<HttpResponse, byte[]>> future = httpclient.execute(
+                    AsyncRequestBuilder.post(target, "/echo/")
+                            .setEntity(b1, ContentType.APPLICATION_OCTET_STREAM)
+                            .build(),
+                    new BasicResponseConsumer<>(new BasicAsyncEntityConsumer()), HttpClientContext.create(), null);
+            queue.add(future);
+        }
+
+        while (!queue.isEmpty()) {
+            final Future<Message<HttpResponse, byte[]>> future = queue.remove();
+            final Message<HttpResponse, byte[]> responseMessage = future.get();
+            Assert.assertThat(responseMessage, CoreMatchers.notNullValue());
+            final HttpResponse response = responseMessage.getHead();
+            Assert.assertThat(response.getCode(), CoreMatchers.equalTo(200));
+            final byte[] b2 = responseMessage.getBody();
+            Assert.assertThat(b1, CoreMatchers.equalTo(b2));
+        }
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/6228a736/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/AbstractHttpAsyncRedirectsTest.java
----------------------------------------------------------------------
diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/AbstractHttpAsyncRedirectsTest.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/AbstractHttpAsyncRedirectsTest.java
new file mode 100644
index 0000000..4649b50
--- /dev/null
+++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/AbstractHttpAsyncRedirectsTest.java
@@ -0,0 +1,825 @@
+/*
+ * ====================================================================
+ * 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.testing.async;
+
+import java.net.InetSocketAddress;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+
+import org.apache.hc.client5.http.CircularRedirectException;
+import org.apache.hc.client5.http.RedirectException;
+import org.apache.hc.client5.http.async.methods.SimpleHttpRequest;
+import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
+import org.apache.hc.client5.http.config.RequestConfig;
+import org.apache.hc.client5.http.cookie.BasicCookieStore;
+import org.apache.hc.client5.http.cookie.CookieStore;
+import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
+import org.apache.hc.client5.http.impl.cookie.BasicClientCookie;
+import org.apache.hc.client5.http.protocol.HttpClientContext;
+import org.apache.hc.client5.testing.SSLTestContexts;
+import org.apache.hc.core5.function.Supplier;
+import org.apache.hc.core5.http.ContentType;
+import org.apache.hc.core5.http.Header;
+import org.apache.hc.core5.http.HttpException;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.HttpRequest;
+import org.apache.hc.core5.http.HttpResponse;
+import org.apache.hc.core5.http.HttpStatus;
+import org.apache.hc.core5.http.ProtocolException;
+import org.apache.hc.core5.http.URIScheme;
+import org.apache.hc.core5.http.message.BasicHeader;
+import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler;
+import org.apache.hc.core5.http.protocol.HttpCoreContext;
+import org.apache.hc.core5.net.URIBuilder;
+import org.apache.hc.core5.reactor.IOReactorConfig;
+import org.apache.hc.core5.reactor.ListenerEndpoint;
+import org.apache.hc.core5.testing.nio.Http2TestServer;
+import org.apache.hc.core5.util.TimeValue;
+import org.junit.Assert;
+import org.junit.Test;
+
+public abstract class AbstractHttpAsyncRedirectsTest <T extends CloseableHttpAsyncClient> extends AbstractIntegrationTestBase<T> {
+
+    public AbstractHttpAsyncRedirectsTest(final URIScheme scheme) {
+        super(scheme);
+    }
+
+    static class BasicRedirectService extends AbstractSimpleServerExchangeHandler {
+
+        private final int statuscode;
+
+        public BasicRedirectService(final int statuscode) {
+            super();
+            this.statuscode = statuscode;
+        }
+
+        @Override
+        protected SimpleHttpResponse handle(
+                final SimpleHttpRequest request, final HttpCoreContext context) throws HttpException {
+            try {
+                final URI requestURI = request.getUri();
+                final String path = requestURI.getPath();
+                if (path.equals("/oldlocation/")) {
+                    final SimpleHttpResponse response = new SimpleHttpResponse(statuscode);
+                    response.addHeader(new BasicHeader("Location",
+                            new URIBuilder(requestURI).setPath("/newlocation/").build()));
+                    return response;
+                } else if (path.equals("/newlocation/")) {
+                    final SimpleHttpResponse response = new SimpleHttpResponse(HttpStatus.SC_OK);
+                    response.setBodyText("Successful redirect", ContentType.TEXT_PLAIN);
+                    return response;
+                } else {
+                    return new SimpleHttpResponse(HttpStatus.SC_NOT_FOUND);
+                }
+            } catch (final URISyntaxException ex) {
+                throw new ProtocolException(ex.getMessage(), ex);
+            }
+        }
+
+    }
+
+    static class CircularRedirectService extends AbstractSimpleServerExchangeHandler {
+
+        public CircularRedirectService() {
+            super();
+        }
+
+        @Override
+        protected SimpleHttpResponse handle(
+                final SimpleHttpRequest request, final HttpCoreContext context) throws HttpException {
+            try {
+                final URI requestURI = request.getUri();
+                final String path = requestURI.getPath();
+                if (path.startsWith("/circular-oldlocation")) {
+                    final SimpleHttpResponse response = new SimpleHttpResponse(HttpStatus.SC_MOVED_TEMPORARILY);
+                    response.addHeader(new BasicHeader("Location", "/circular-location2"));
+                    return response;
+                } else if (path.startsWith("/circular-location2")) {
+                    final SimpleHttpResponse response = new SimpleHttpResponse(HttpStatus.SC_MOVED_TEMPORARILY);
+                    response.addHeader(new BasicHeader("Location", "/circular-oldlocation"));
+                    return response;
+                } else {
+                    return new SimpleHttpResponse(HttpStatus.SC_NOT_FOUND);
+                }
+            } catch (final URISyntaxException ex) {
+                throw new ProtocolException(ex.getMessage(), ex);
+            }
+        }
+
+    }
+
+    static class RelativeRedirectService extends AbstractSimpleServerExchangeHandler {
+
+        @Override
+        protected SimpleHttpResponse handle(
+                final SimpleHttpRequest request, final HttpCoreContext context) throws HttpException {
+            try {
+                final URI requestURI = request.getUri();
+                final String path = requestURI.getPath();
+                if (path.equals("/oldlocation/")) {
+                    final SimpleHttpResponse response = new SimpleHttpResponse(HttpStatus.SC_MOVED_TEMPORARILY);
+                    response.addHeader(new BasicHeader("Location", "/relativelocation/"));
+                    return response;
+                } else if (path.equals("/relativelocation/")) {
+                    final SimpleHttpResponse response = new SimpleHttpResponse(HttpStatus.SC_OK);
+                    response.setBodyText("Successful redirect", ContentType.TEXT_PLAIN);
+                    return response;
+                } else {
+                    return new SimpleHttpResponse(HttpStatus.SC_NOT_FOUND);
+                }
+            } catch (final URISyntaxException ex) {
+                throw new ProtocolException(ex.getMessage(), ex);
+            }
+        }
+    }
+
+    static class RelativeRedirectService2 extends AbstractSimpleServerExchangeHandler {
+
+        @Override
+        protected SimpleHttpResponse handle(
+                final SimpleHttpRequest request, final HttpCoreContext context) throws HttpException {
+            try {
+                final URI requestURI = request.getUri();
+                final String path = requestURI.getPath();
+                if (path.equals("/test/oldlocation")) {
+                    final SimpleHttpResponse response = new SimpleHttpResponse(HttpStatus.SC_MOVED_TEMPORARILY);
+                    response.addHeader(new BasicHeader("Location", "relativelocation"));
+                    return response;
+                } else if (path.equals("/test/relativelocation")) {
+                    final SimpleHttpResponse response = new SimpleHttpResponse(HttpStatus.SC_OK);
+                    response.setBodyText("Successful redirect", ContentType.TEXT_PLAIN);
+                    return response;
+                } else {
+                    return new SimpleHttpResponse(HttpStatus.SC_NOT_FOUND);
+                }
+            } catch (final URISyntaxException ex) {
+                throw new ProtocolException(ex.getMessage(), ex);
+            }
+        }
+
+    }
+
+    @Test
+    public void testBasicRedirect300() throws Exception {
+        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
+
+            @Override
+            public AsyncServerExchangeHandler get() {
+                return new BasicRedirectService(HttpStatus.SC_MULTIPLE_CHOICES);
+            }
+
+        });
+        final HttpHost target = start();
+
+        final HttpClientContext context = HttpClientContext.create();
+        final Future<SimpleHttpResponse> future = httpclient.execute(
+                SimpleHttpRequest.get(target, "/oldlocation/"), context, null);
+        final HttpResponse response = future.get();
+        Assert.assertNotNull(response);
+
+        final HttpRequest request = context.getRequest();
+
+        Assert.assertEquals(HttpStatus.SC_MULTIPLE_CHOICES, response.getCode());
+        Assert.assertEquals("/oldlocation/", request.getRequestUri());
+    }
+
+    @Test
+    public void testBasicRedirect301() throws Exception {
+        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
+
+            @Override
+            public AsyncServerExchangeHandler get() {
+                return new BasicRedirectService(HttpStatus.SC_MOVED_PERMANENTLY);
+            }
+
+        });
+
+        final HttpHost target = start();
+        final HttpClientContext context = HttpClientContext.create();
+        final Future<SimpleHttpResponse> future = httpclient.execute(
+                SimpleHttpRequest.get(target, "/oldlocation/"), context, null);
+        final HttpResponse response = future.get();
+        Assert.assertNotNull(response);
+
+        final HttpRequest request = context.getRequest();
+
+        Assert.assertEquals(HttpStatus.SC_OK, response.getCode());
+        Assert.assertEquals("/newlocation/", request.getRequestUri());
+        Assert.assertEquals(target, new HttpHost(request.getAuthority(), request.getScheme()));
+    }
+
+    @Test
+    public void testBasicRedirect302() throws Exception {
+        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
+
+            @Override
+            public AsyncServerExchangeHandler get() {
+                return new BasicRedirectService(HttpStatus.SC_MOVED_TEMPORARILY);
+            }
+
+        });
+        final HttpHost target = start();
+        final HttpClientContext context = HttpClientContext.create();
+        final Future<SimpleHttpResponse> future = httpclient.execute(
+                SimpleHttpRequest.get(target, "/oldlocation/"), context, null);
+        final HttpResponse response = future.get();
+        Assert.assertNotNull(response);
+
+        final HttpRequest request = context.getRequest();
+
+        Assert.assertEquals(HttpStatus.SC_OK, response.getCode());
+        Assert.assertEquals("/newlocation/", request.getRequestUri());
+        Assert.assertEquals(target, new HttpHost(request.getAuthority(), request.getScheme()));
+    }
+
+    @Test
+    public void testBasicRedirect302NoLocation() throws Exception {
+        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
+
+            @Override
+            public AsyncServerExchangeHandler get() {
+                return new AbstractSimpleServerExchangeHandler() {
+
+                    @Override
+                    protected SimpleHttpResponse handle(
+                            final SimpleHttpRequest request, final HttpCoreContext context) throws HttpException {
+                        return new SimpleHttpResponse(HttpStatus.SC_MOVED_TEMPORARILY);
+                    }
+
+                };
+            }
+
+        });
+        final HttpHost target = start();
+        final HttpClientContext context = HttpClientContext.create();
+        final Future<SimpleHttpResponse> future = httpclient.execute(
+                SimpleHttpRequest.get(target, "/oldlocation/"), context, null);
+        final HttpResponse response = future.get();
+        Assert.assertNotNull(response);
+
+        final HttpRequest request = context.getRequest();
+        Assert.assertEquals(HttpStatus.SC_MOVED_TEMPORARILY, response.getCode());
+        Assert.assertEquals("/oldlocation/", request.getRequestUri());
+        Assert.assertEquals(target, new HttpHost(request.getAuthority(), request.getScheme()));
+    }
+
+    @Test
+    public void testBasicRedirect303() throws Exception {
+        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
+
+            @Override
+            public AsyncServerExchangeHandler get() {
+                return new BasicRedirectService(HttpStatus.SC_SEE_OTHER);
+            }
+
+        });
+        final HttpHost target = start();
+        final HttpClientContext context = HttpClientContext.create();
+        final Future<SimpleHttpResponse> future = httpclient.execute(
+                SimpleHttpRequest.get(target, "/oldlocation/"), context, null);
+        final HttpResponse response = future.get();
+        Assert.assertNotNull(response);
+
+        final HttpRequest request = context.getRequest();
+
+        Assert.assertEquals(HttpStatus.SC_OK, response.getCode());
+        Assert.assertEquals("/newlocation/", request.getRequestUri());
+        Assert.assertEquals(target, new HttpHost(request.getAuthority(), request.getScheme()));
+    }
+
+    @Test
+    public void testBasicRedirect304() throws Exception {
+        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
+
+            @Override
+            public AsyncServerExchangeHandler get() {
+                return new BasicRedirectService(HttpStatus.SC_NOT_MODIFIED);
+            }
+
+        });
+        final HttpHost target = start();
+        final HttpClientContext context = HttpClientContext.create();
+        final Future<SimpleHttpResponse> future = httpclient.execute(
+                SimpleHttpRequest.get(target, "/oldlocation/"), context, null);
+        final HttpResponse response = future.get();
+        Assert.assertNotNull(response);
+
+        final HttpRequest request = context.getRequest();
+
+        Assert.assertEquals(HttpStatus.SC_NOT_MODIFIED, response.getCode());
+        Assert.assertEquals("/oldlocation/", request.getRequestUri());
+    }
+
+    @Test
+    public void testBasicRedirect305() throws Exception {
+        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
+
+            @Override
+            public AsyncServerExchangeHandler get() {
+                return new BasicRedirectService(HttpStatus.SC_USE_PROXY);
+            }
+
+        });
+        final HttpHost target = start();
+        final HttpClientContext context = HttpClientContext.create();
+        final Future<SimpleHttpResponse> future = httpclient.execute(
+                SimpleHttpRequest.get(target, "/oldlocation/"), context, null);
+        final HttpResponse response = future.get();
+        Assert.assertNotNull(response);
+
+        final HttpRequest request = context.getRequest();
+
+        Assert.assertEquals(HttpStatus.SC_USE_PROXY, response.getCode());
+        Assert.assertEquals("/oldlocation/", request.getRequestUri());
+    }
+
+    @Test
+    public void testBasicRedirect307() throws Exception {
+        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
+
+            @Override
+            public AsyncServerExchangeHandler get() {
+                return new BasicRedirectService(HttpStatus.SC_TEMPORARY_REDIRECT);
+            }
+
+        });
+        final HttpHost target = start();
+        final HttpClientContext context = HttpClientContext.create();
+        final Future<SimpleHttpResponse> future = httpclient.execute(
+                SimpleHttpRequest.get(target, "/oldlocation/"), context, null);
+        final HttpResponse response = future.get();
+        Assert.assertNotNull(response);
+
+        final HttpRequest request = context.getRequest();
+
+        Assert.assertEquals(HttpStatus.SC_OK, response.getCode());
+        Assert.assertEquals("/newlocation/", request.getRequestUri());
+        Assert.assertEquals(target, new HttpHost(request.getAuthority(), request.getScheme()));
+    }
+
+    @Test(expected=ExecutionException.class)
+    public void testMaxRedirectCheck() throws Exception {
+        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
+
+            @Override
+            public AsyncServerExchangeHandler get() {
+                return new CircularRedirectService();
+            }
+
+        });
+        final HttpHost target = start();
+
+        final RequestConfig config = RequestConfig.custom()
+                .setCircularRedirectsAllowed(true)
+                .setMaxRedirects(5).build();
+        try {
+            final SimpleHttpRequest request = SimpleHttpRequest.get(target, "/circular-oldlocation/");
+            request.setConfig(config);
+            final Future<SimpleHttpResponse> future = httpclient.execute(request, null);
+            future.get();
+        } catch (final ExecutionException e) {
+            Assert.assertTrue(e.getCause() instanceof RedirectException);
+            throw e;
+        }
+    }
+
+    @Test(expected=ExecutionException.class)
+    public void testCircularRedirect() throws Exception {
+        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
+
+            @Override
+            public AsyncServerExchangeHandler get() {
+                return new CircularRedirectService();
+            }
+
+        });
+        final HttpHost target = start();
+
+        final RequestConfig config = RequestConfig.custom()
+                .setCircularRedirectsAllowed(false)
+                .build();
+        try {
+            final SimpleHttpRequest request = SimpleHttpRequest.get(target, "/circular-oldlocation/");
+            request.setConfig(config);
+            final Future<SimpleHttpResponse> future = httpclient.execute(request, null);
+            future.get();
+        } catch (final ExecutionException e) {
+            Assert.assertTrue(e.getCause() instanceof CircularRedirectException);
+            throw e;
+        }
+    }
+
+    @Test
+    public void testPostRedirectSeeOther() throws Exception {
+        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
+
+            @Override
+            public AsyncServerExchangeHandler get() {
+                return new BasicRedirectService(HttpStatus.SC_SEE_OTHER);
+            }
+
+        });
+        final HttpHost target = start();
+
+        final HttpClientContext context = HttpClientContext.create();
+
+        final SimpleHttpRequest post = SimpleHttpRequest.post(target, "/oldlocation/");
+        post.setBodyText("stuff", ContentType.TEXT_PLAIN);
+        final Future<SimpleHttpResponse> future = httpclient.execute(post, context, null);
+        final HttpResponse response = future.get();
+        Assert.assertNotNull(response);
+
+        final HttpRequest request = context.getRequest();
+
+        Assert.assertEquals(HttpStatus.SC_OK, response.getCode());
+        Assert.assertEquals("/newlocation/", request.getRequestUri());
+        Assert.assertEquals("GET", request.getMethod());
+    }
+
+    @Test
+    public void testRelativeRedirect() throws Exception {
+        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
+
+            @Override
+            public AsyncServerExchangeHandler get() {
+                return new RelativeRedirectService();
+            }
+
+        });
+        final HttpHost target = start();
+
+        final HttpClientContext context = HttpClientContext.create();
+
+        final Future<SimpleHttpResponse> future = httpclient.execute(
+                SimpleHttpRequest.get(target, "/oldlocation/"), context, null);
+        final HttpResponse response = future.get();
+        Assert.assertNotNull(response);
+
+        final HttpRequest request = context.getRequest();
+
+        Assert.assertEquals(HttpStatus.SC_OK, response.getCode());
+        Assert.assertEquals("/relativelocation/", request.getRequestUri());
+        Assert.assertEquals(target, new HttpHost(request.getAuthority(), request.getScheme()));
+    }
+
+    @Test
+    public void testRelativeRedirect2() throws Exception {
+        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
+
+            @Override
+            public AsyncServerExchangeHandler get() {
+                return new RelativeRedirectService2();
+            }
+
+        });
+        final HttpHost target = start();
+
+        final HttpClientContext context = HttpClientContext.create();
+
+        final Future<SimpleHttpResponse> future = httpclient.execute(
+                SimpleHttpRequest.get(target, "/test/oldlocation"), context, null);
+        final HttpResponse response = future.get();
+        Assert.assertNotNull(response);
+
+        final HttpRequest request = context.getRequest();
+
+        Assert.assertEquals(HttpStatus.SC_OK, response.getCode());
+        Assert.assertEquals("/test/relativelocation", request.getRequestUri());
+        Assert.assertEquals(target, new HttpHost(request.getAuthority(), request.getScheme()));
+    }
+
+    static class BogusRedirectService extends AbstractSimpleServerExchangeHandler {
+
+        private final String url;
+
+        public BogusRedirectService(final String url) {
+            super();
+            this.url = url;
+        }
+
+        @Override
+        protected SimpleHttpResponse handle(
+                final SimpleHttpRequest request, final HttpCoreContext context) throws HttpException {
+            try {
+                final URI requestURI = request.getUri();
+                final String path = requestURI.getPath();
+                if (path.equals("/oldlocation/")) {
+                    final SimpleHttpResponse response = new SimpleHttpResponse(HttpStatus.SC_MOVED_TEMPORARILY);
+                    response.addHeader(new BasicHeader("Location", url));
+                    return response;
+                } else if (path.equals("/relativelocation/")) {
+                    final SimpleHttpResponse response = new SimpleHttpResponse(HttpStatus.SC_OK);
+                    response.setBodyText("Successful redirect", ContentType.TEXT_PLAIN);
+                    return response;
+                } else {
+                    return new SimpleHttpResponse(HttpStatus.SC_NOT_FOUND);
+                }
+            } catch (final URISyntaxException ex) {
+                throw new ProtocolException(ex.getMessage(), ex);
+            }
+        }
+
+    }
+
+    @Test(expected=ExecutionException.class)
+    public void testRejectBogusRedirectLocation() throws Exception {
+        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
+
+            @Override
+            public AsyncServerExchangeHandler get() {
+                return new BogusRedirectService("xxx://bogus");
+            }
+
+        });
+        final HttpHost target = start();
+
+        try {
+            final Future<SimpleHttpResponse> future = httpclient.execute(
+                    SimpleHttpRequest.get(target, "/oldlocation/"), null);
+            future.get();
+        } catch (final ExecutionException ex) {
+            Assert.assertTrue(ex.getCause() instanceof HttpException);
+            throw ex;
+        }
+    }
+
+    @Test(expected=ExecutionException.class)
+    public void testRejectInvalidRedirectLocation() throws Exception {
+        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
+
+            @Override
+            public AsyncServerExchangeHandler get() {
+                return new BogusRedirectService("/newlocation/?p=I have spaces");
+            }
+
+        });
+        final HttpHost target = start();
+
+        try {
+            final Future<SimpleHttpResponse> future = httpclient.execute(
+                    SimpleHttpRequest.get(target, "/oldlocation/"), null);
+            future.get();
+        } catch (final ExecutionException e) {
+            Assert.assertTrue(e.getCause() instanceof ProtocolException);
+            throw e;
+        }
+    }
+
+    @Test
+    public void testRedirectWithCookie() throws Exception {
+        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
+
+            @Override
+            public AsyncServerExchangeHandler get() {
+                return new BasicRedirectService(HttpStatus.SC_MOVED_TEMPORARILY);
+            }
+
+        });
+        final HttpHost target = start();
+
+        final CookieStore cookieStore = new BasicCookieStore();
+        final HttpClientContext context = HttpClientContext.create();
+        context.setCookieStore(cookieStore);
+
+        final BasicClientCookie cookie = new BasicClientCookie("name", "value");
+        cookie.setDomain(target.getHostName());
+        cookie.setPath("/");
+
+        cookieStore.addCookie(cookie);
+
+        final Future<SimpleHttpResponse> future = httpclient.execute(
+                SimpleHttpRequest.get(target, "/oldlocation/"), context, null);
+        final HttpResponse response = future.get();
+        Assert.assertNotNull(response);
+
+        final HttpRequest request = context.getRequest();
+
+        Assert.assertEquals(HttpStatus.SC_OK, response.getCode());
+        Assert.assertEquals("/newlocation/", request.getRequestUri());
+
+        final Header[] headers = request.getHeaders("Cookie");
+        Assert.assertEquals("There can only be one (cookie)", 1, headers.length);
+    }
+
+    static class CrossSiteRedirectService extends AbstractSimpleServerExchangeHandler {
+
+        private final HttpHost host;
+
+        public CrossSiteRedirectService(final HttpHost host) {
+            super();
+            this.host = host;
+        }
+
+        @Override
+        protected SimpleHttpResponse handle(
+                final SimpleHttpRequest request, final HttpCoreContext context) throws HttpException {
+            final String location;
+            try {
+                final URIBuilder uribuilder = new URIBuilder(request.getUri());
+                uribuilder.setScheme(host.getSchemeName());
+                uribuilder.setHost(host.getHostName());
+                uribuilder.setPort(host.getPort());
+                uribuilder.setPath("/random/1024");
+                location = uribuilder.build().toASCIIString();
+            } catch (final URISyntaxException ex) {
+                throw new ProtocolException("Invalid request URI", ex);
+            }
+            final SimpleHttpResponse response = new SimpleHttpResponse(HttpStatus.SC_TEMPORARY_REDIRECT);
+            response.addHeader(new BasicHeader("Location", location));
+            return response;
+        }
+    }
+
+    @Test
+    public void testCrossSiteRedirect() throws Exception {
+        server.register("/random/*", new Supplier<AsyncServerExchangeHandler>() {
+
+            @Override
+            public AsyncServerExchangeHandler get() {
+                return new AsyncRandomHandler();
+            }
+
+        });
+        final HttpHost redirectTarget = start();
+
+        final Http2TestServer secondServer = new Http2TestServer(IOReactorConfig.DEFAULT,
+                scheme == URIScheme.HTTPS ? SSLTestContexts.createServerSSLContext() : null);
+        try {
+            secondServer.register("/redirect/*", new Supplier<AsyncServerExchangeHandler>() {
+
+                @Override
+                public AsyncServerExchangeHandler get() {
+                    return new CrossSiteRedirectService(redirectTarget);
+                }
+
+            });
+
+            secondServer.start();
+            final Future<ListenerEndpoint> endpointFuture = secondServer.listen(new InetSocketAddress(0));
+            final ListenerEndpoint endpoint2 = endpointFuture.get();
+
+            final InetSocketAddress address2 = (InetSocketAddress) endpoint2.getAddress();
+            final HttpHost initialTarget = new HttpHost("localhost", address2.getPort(), scheme.name());
+
+            final Queue<Future<SimpleHttpResponse>> queue = new ConcurrentLinkedQueue<>();
+            for (int i = 0; i < 1; i++) {
+                queue.add(httpclient.execute(SimpleHttpRequest.get(initialTarget, "/redirect/anywhere"), null));
+            }
+            while (!queue.isEmpty()) {
+                final Future<SimpleHttpResponse> future = queue.remove();
+                final HttpResponse response = future.get();
+                Assert.assertNotNull(response);
+                Assert.assertEquals(200, response.getCode());
+            }
+        } finally {
+            server.shutdown(TimeValue.ofSeconds(5));
+        }
+    }
+
+    private static class RomeRedirectService extends AbstractSimpleServerExchangeHandler {
+
+        @Override
+        protected SimpleHttpResponse handle(
+                final SimpleHttpRequest request, final HttpCoreContext context) throws HttpException {
+            try {
+                final URI requestURI = request.getUri();
+                final String path = requestURI.getPath();
+                if (path.equals("/rome")) {
+                    final SimpleHttpResponse response = new SimpleHttpResponse(HttpStatus.SC_OK);
+                    response.setBodyText("Successful redirect", ContentType.TEXT_PLAIN);
+                    return response;
+                } else {
+                    final SimpleHttpResponse response = new SimpleHttpResponse(HttpStatus.SC_MOVED_TEMPORARILY);
+                    response.addHeader(new BasicHeader("Location", "/rome"));
+                    return response;
+                }
+            } catch (final URISyntaxException ex) {
+                throw new ProtocolException(ex.getMessage(), ex);
+            }
+        }
+
+    }
+
+    @Test
+    public void testRepeatRequest() throws Exception {
+        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
+
+            @Override
+            public AsyncServerExchangeHandler get() {
+                return new RomeRedirectService();
+            }
+
+        });
+        final HttpHost target = start();
+
+        final HttpClientContext context = HttpClientContext.create();
+
+        final Future<SimpleHttpResponse> future1 = httpclient.execute(
+                SimpleHttpRequest.get(target, "/rome"), context, null);
+        final HttpResponse response1 = future1.get();
+        Assert.assertNotNull(response1);
+
+        final Future<SimpleHttpResponse> future2 = httpclient.execute(
+                SimpleHttpRequest.get(target, "/rome"), context, null);
+        final HttpResponse response2 = future2.get();
+        Assert.assertNotNull(response2);
+
+        final HttpRequest request = context.getRequest();
+
+        Assert.assertEquals(HttpStatus.SC_OK, response2.getCode());
+        Assert.assertEquals("/rome", request.getRequestUri());
+        Assert.assertEquals(target, new HttpHost(request.getAuthority(), request.getScheme()));
+    }
+
+    @Test
+    public void testRepeatRequestRedirect() throws Exception {
+        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
+
+            @Override
+            public AsyncServerExchangeHandler get() {
+                return new RomeRedirectService();
+            }
+
+        });
+        final HttpHost target = start();
+
+        final HttpClientContext context = HttpClientContext.create();
+
+        final Future<SimpleHttpResponse> future1 = httpclient.execute(
+                SimpleHttpRequest.get(target, "/lille"), context, null);
+        final HttpResponse response1 = future1.get();
+        Assert.assertNotNull(response1);
+
+        final Future<SimpleHttpResponse> future2 = httpclient.execute(
+                SimpleHttpRequest.get(target, "/lille"), context, null);
+        final HttpResponse response2 = future2.get();
+        Assert.assertNotNull(response2);
+
+        final HttpRequest request = context.getRequest();
+
+        Assert.assertEquals(HttpStatus.SC_OK, response2.getCode());
+        Assert.assertEquals("/rome", request.getRequestUri());
+        Assert.assertEquals(target, new HttpHost(request.getAuthority(), request.getScheme()));
+    }
+
+    @Test
+    public void testDifferentRequestSameRedirect() throws Exception {
+        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
+
+            @Override
+            public AsyncServerExchangeHandler get() {
+                return new RomeRedirectService();
+            }
+
+        });
+        final HttpHost target = start();
+
+        final HttpClientContext context = HttpClientContext.create();
+
+        final Future<SimpleHttpResponse> future1 = httpclient.execute(
+                SimpleHttpRequest.get(target, "/alian"), context, null);
+        final HttpResponse response1 = future1.get();
+        Assert.assertNotNull(response1);
+
+        final Future<SimpleHttpResponse> future2 = httpclient.execute(
+                SimpleHttpRequest.get(target, "/lille"), context, null);
+        final HttpResponse response2 = future2.get();
+        Assert.assertNotNull(response2);
+
+
+        final HttpRequest request = context.getRequest();
+
+        Assert.assertEquals(HttpStatus.SC_OK, response2.getCode());
+        Assert.assertEquals("/rome", request.getRequestUri());
+        Assert.assertEquals(target, new HttpHost(request.getAuthority(), request.getScheme()));
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/6228a736/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/AbstractIntegrationTestBase.java
----------------------------------------------------------------------
diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/AbstractIntegrationTestBase.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/AbstractIntegrationTestBase.java
new file mode 100644
index 0000000..c193785
--- /dev/null
+++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/AbstractIntegrationTestBase.java
@@ -0,0 +1,114 @@
+/*
+ * ====================================================================
+ * 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.testing.async;
+
+import java.net.InetSocketAddress;
+import java.util.concurrent.Future;
+
+import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
+import org.apache.hc.core5.function.Decorator;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.URIScheme;
+import org.apache.hc.core5.http.config.H1Config;
+import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler;
+import org.apache.hc.core5.http.protocol.HttpProcessor;
+import org.apache.hc.core5.http2.config.H2Config;
+import org.apache.hc.core5.io.ShutdownType;
+import org.apache.hc.core5.reactor.ListenerEndpoint;
+import org.junit.Rule;
+import org.junit.rules.ExternalResource;
+
+public abstract class AbstractIntegrationTestBase<T extends CloseableHttpAsyncClient> extends AbstractServerTestBase {
+
+    public AbstractIntegrationTestBase(final URIScheme scheme) {
+        super(scheme);
+    }
+
+    public AbstractIntegrationTestBase() {
+        super(URIScheme.HTTP);
+    }
+
+    protected T httpclient;
+
+    protected abstract T createClient() throws Exception;
+
+    @Rule
+    public ExternalResource clientResource = new ExternalResource() {
+
+        @Override
+        protected void after() {
+            if (httpclient != null) {
+                httpclient.shutdown(ShutdownType.GRACEFUL);
+                httpclient = null;
+            }
+        }
+
+    };
+
+    public abstract HttpHost start() throws Exception;
+
+    public final HttpHost start(
+            final HttpProcessor httpProcessor,
+            final Decorator<AsyncServerExchangeHandler> exchangeHandlerDecorator,
+            final H1Config h1Config) throws Exception {
+        server.start(httpProcessor, exchangeHandlerDecorator, h1Config);
+        final Future<ListenerEndpoint> endpointFuture = server.listen(new InetSocketAddress(0));
+        httpclient = createClient();
+        httpclient.start();
+        final ListenerEndpoint endpoint = endpointFuture.get();
+        final InetSocketAddress address = (InetSocketAddress) endpoint.getAddress();
+        return new HttpHost("localhost", address.getPort(), scheme.name());
+    }
+
+    public final HttpHost start(
+            final HttpProcessor httpProcessor,
+            final H1Config h1Config) throws Exception {
+        return start(httpProcessor, null, h1Config);
+    }
+
+    public final HttpHost start(
+            final HttpProcessor httpProcessor,
+            final Decorator<AsyncServerExchangeHandler> exchangeHandlerDecorator,
+            final H2Config h2Config) throws Exception {
+        server.start(httpProcessor, exchangeHandlerDecorator, h2Config);
+        final Future<ListenerEndpoint> endpointFuture = server.listen(new InetSocketAddress(0));
+        httpclient = createClient();
+        httpclient.start();
+        final ListenerEndpoint endpoint = endpointFuture.get();
+        final InetSocketAddress address = (InetSocketAddress) endpoint.getAddress();
+        return new HttpHost("localhost", address.getPort(), scheme.name());
+    }
+
+
+    public final HttpHost start(
+            final HttpProcessor httpProcessor,
+            final H2Config h2Config) throws Exception {
+        return start(httpProcessor, null, h2Config);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/6228a736/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/AbstractServerTestBase.java
----------------------------------------------------------------------
diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/AbstractServerTestBase.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/AbstractServerTestBase.java
new file mode 100644
index 0000000..090dd68
--- /dev/null
+++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/AbstractServerTestBase.java
@@ -0,0 +1,96 @@
+/*
+ * ====================================================================
+ * 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.testing.async;
+
+import org.apache.hc.client5.testing.SSLTestContexts;
+import org.apache.hc.core5.function.Supplier;
+import org.apache.hc.core5.http.URIScheme;
+import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler;
+import org.apache.hc.core5.reactor.IOReactorConfig;
+import org.apache.hc.core5.testing.nio.Http2TestServer;
+import org.apache.hc.core5.util.TimeValue;
+import org.apache.hc.core5.util.Timeout;
+import org.junit.Rule;
+import org.junit.rules.ExternalResource;
+
+public abstract class AbstractServerTestBase {
+
+    public static final Timeout TIMEOUT = Timeout.ofSeconds(30);
+    public static final Timeout LONG_TIMEOUT = Timeout.ofSeconds(60);
+
+    protected final URIScheme scheme;
+
+    public AbstractServerTestBase(final URIScheme scheme) {
+        this.scheme = scheme;
+    }
+
+    public AbstractServerTestBase() {
+        this(URIScheme.HTTP);
+    }
+
+    protected Http2TestServer server;
+
+    @Rule
+    public ExternalResource serverResource = new ExternalResource() {
+
+        @Override
+        protected void before() throws Throwable {
+            server = new Http2TestServer(
+                    IOReactorConfig.custom()
+                        .setSoTimeout(TIMEOUT)
+                        .build(),
+                    scheme == URIScheme.HTTPS ? SSLTestContexts.createServerSSLContext() : null);
+            server.register("/echo/*", new Supplier<AsyncServerExchangeHandler>() {
+
+                @Override
+                public AsyncServerExchangeHandler get() {
+                    return new AsyncEchoHandler();
+                }
+
+            });
+            server.register("/random/*", new Supplier<AsyncServerExchangeHandler>() {
+
+                @Override
+                public AsyncServerExchangeHandler get() {
+                    return new AsyncRandomHandler();
+                }
+
+            });
+        }
+
+        @Override
+        protected void after() {
+            if (server != null) {
+                server.shutdown(TimeValue.ofSeconds(5));
+                server = null;
+            }
+        }
+
+    };
+
+}

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/6228a736/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/IntegrationTestBase.java
----------------------------------------------------------------------
diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/IntegrationTestBase.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/IntegrationTestBase.java
deleted file mode 100644
index 3180227..0000000
--- a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/IntegrationTestBase.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * ====================================================================
- * 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.testing.async;
-
-import java.net.InetSocketAddress;
-import java.util.concurrent.Future;
-
-import org.apache.hc.client5.http.config.RequestConfig;
-import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
-import org.apache.hc.client5.http.impl.async.HttpAsyncClientBuilder;
-import org.apache.hc.core5.function.Decorator;
-import org.apache.hc.core5.http.HttpHost;
-import org.apache.hc.core5.http.URIScheme;
-import org.apache.hc.core5.http.config.H1Config;
-import org.apache.hc.core5.http.impl.HttpProcessors;
-import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler;
-import org.apache.hc.core5.http.protocol.HttpProcessor;
-import org.apache.hc.core5.io.ShutdownType;
-import org.apache.hc.core5.reactor.ListenerEndpoint;
-import org.junit.Rule;
-import org.junit.rules.ExternalResource;
-
-public abstract class IntegrationTestBase extends LocalAsyncServerTestBase {
-
-    public IntegrationTestBase(final URIScheme scheme) {
-        super(scheme);
-    }
-
-    public IntegrationTestBase() {
-        super(URIScheme.HTTP);
-    }
-
-    protected HttpAsyncClientBuilder clientBuilder;
-    protected CloseableHttpAsyncClient httpclient;
-
-    @Rule
-    public ExternalResource clientResource = new ExternalResource() {
-
-        @Override
-        protected void before() throws Throwable {
-            clientBuilder = HttpAsyncClientBuilder.create()
-                    .setDefaultRequestConfig(RequestConfig.custom()
-                            .setSocketTimeout(TIMEOUT)
-                            .setConnectTimeout(TIMEOUT)
-                            .setConnectionRequestTimeout(TIMEOUT)
-                            .build())
-                    .setConnectionManager(connManager);
-        }
-
-        @Override
-        protected void after() {
-            if (httpclient != null) {
-                httpclient.shutdown(ShutdownType.GRACEFUL);
-                httpclient = null;
-            }
-        }
-
-    };
-
-    public HttpHost start(
-            final HttpProcessor httpProcessor,
-            final Decorator<AsyncServerExchangeHandler> exchangeHandlerDecorator,
-            final H1Config h1Config) throws Exception {
-        server.start(httpProcessor, exchangeHandlerDecorator, h1Config);
-        final Future<ListenerEndpoint> endpointFuture = server.listen(new InetSocketAddress(0));
-        httpclient = clientBuilder.build();
-        httpclient.start();
-        final ListenerEndpoint endpoint = endpointFuture.get();
-        final InetSocketAddress address = (InetSocketAddress) endpoint.getAddress();
-        return new HttpHost("localhost", address.getPort(), scheme.name());
-    }
-
-    public HttpHost start(
-            final HttpProcessor httpProcessor,
-            final H1Config h1Config) throws Exception {
-        return start(httpProcessor, null, h1Config);
-    }
-
-    public HttpHost start() throws Exception {
-        return start(HttpProcessors.server(), H1Config.DEFAULT);
-    }
-
-}


[4/6] httpcomponents-client git commit: * HTTP/2 multiplexing HttpAsyncClient implementation * Restructured integration tests to reduce test duplication

Posted by ol...@apache.org.
http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/6228a736/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp1AsyncRedirects.java
----------------------------------------------------------------------
diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp1AsyncRedirects.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp1AsyncRedirects.java
new file mode 100644
index 0000000..48caa35
--- /dev/null
+++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp1AsyncRedirects.java
@@ -0,0 +1,254 @@
+/*
+ * ====================================================================
+ * 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.testing.async;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.Future;
+
+import org.apache.hc.client5.http.async.methods.SimpleHttpRequest;
+import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
+import org.apache.hc.client5.http.config.RequestConfig;
+import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
+import org.apache.hc.client5.http.impl.async.HttpAsyncClientBuilder;
+import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManager;
+import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBuilder;
+import org.apache.hc.client5.http.protocol.HttpClientContext;
+import org.apache.hc.client5.http.ssl.H2TlsStrategy;
+import org.apache.hc.client5.testing.SSLTestContexts;
+import org.apache.hc.core5.function.Supplier;
+import org.apache.hc.core5.http.ContentType;
+import org.apache.hc.core5.http.Header;
+import org.apache.hc.core5.http.HttpException;
+import org.apache.hc.core5.http.HttpHeaders;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.HttpRequest;
+import org.apache.hc.core5.http.HttpResponse;
+import org.apache.hc.core5.http.HttpStatus;
+import org.apache.hc.core5.http.ProtocolException;
+import org.apache.hc.core5.http.URIScheme;
+import org.apache.hc.core5.http.config.H1Config;
+import org.apache.hc.core5.http.message.BasicHeader;
+import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler;
+import org.apache.hc.core5.http.protocol.HttpCoreContext;
+import org.apache.hc.core5.net.URIBuilder;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExternalResource;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+/**
+ * Redirection test cases.
+ */
+@RunWith(Parameterized.class)
+public class TestHttp1AsyncRedirects extends AbstractHttpAsyncRedirectsTest<CloseableHttpAsyncClient> {
+
+    @Parameterized.Parameters(name = "HTTP/1.1 {0}")
+    public static Collection<Object[]> protocols() {
+        return Arrays.asList(new Object[][]{
+                {URIScheme.HTTP},
+                {URIScheme.HTTPS},
+        });
+    }
+
+    protected HttpAsyncClientBuilder clientBuilder;
+    protected PoolingAsyncClientConnectionManager connManager;
+
+    @Rule
+    public ExternalResource connManagerResource = new ExternalResource() {
+
+        @Override
+        protected void before() throws Throwable {
+            connManager = PoolingAsyncClientConnectionManagerBuilder.create()
+                    .setTlsStrategy(new H2TlsStrategy(SSLTestContexts.createClientSSLContext()))
+                    .build();
+        }
+
+        @Override
+        protected void after() {
+            if (connManager != null) {
+                connManager.close();
+                connManager = null;
+            }
+        }
+
+    };
+
+    @Rule
+    public ExternalResource clientResource = new ExternalResource() {
+
+        @Override
+        protected void before() throws Throwable {
+            clientBuilder = HttpAsyncClientBuilder.create()
+                    .setDefaultRequestConfig(RequestConfig.custom()
+                            .setSocketTimeout(TIMEOUT)
+                            .setConnectTimeout(TIMEOUT)
+                            .setConnectionRequestTimeout(TIMEOUT)
+                            .build())
+                    .setConnectionManager(connManager);
+        }
+
+    };
+
+    public TestHttp1AsyncRedirects(final URIScheme scheme) {
+        super(scheme);
+    }
+
+    @Override
+    protected CloseableHttpAsyncClient createClient() throws Exception {
+        return clientBuilder.build();
+    }
+
+    @Override
+    public final HttpHost start() throws Exception {
+        return super.start(null, H1Config.DEFAULT);
+    }
+
+    static class NoKeepAliveRedirectService extends AbstractSimpleServerExchangeHandler {
+
+        private final int statuscode;
+
+        public NoKeepAliveRedirectService(final int statuscode) {
+            super();
+            this.statuscode = statuscode;
+        }
+
+        @Override
+        protected SimpleHttpResponse handle(
+                final SimpleHttpRequest request, final HttpCoreContext context) throws HttpException {
+            try {
+                final URI requestURI = request.getUri();
+                final String path = requestURI.getPath();
+                if (path.equals("/oldlocation/")) {
+                    final SimpleHttpResponse response = new SimpleHttpResponse(statuscode);
+                    response.addHeader(new BasicHeader("Location",
+                            new URIBuilder(requestURI).setPath("/newlocation/").build()));
+                    response.addHeader(new BasicHeader("Connection", "close"));
+                    return response;
+                } else if (path.equals("/newlocation/")) {
+                    final SimpleHttpResponse response = new SimpleHttpResponse(HttpStatus.SC_OK);
+                    response.setBodyText("Successful redirect", ContentType.TEXT_PLAIN);
+                    return response;
+                } else {
+                    return new SimpleHttpResponse(HttpStatus.SC_NOT_FOUND);
+                }
+            } catch (final URISyntaxException ex) {
+                throw new ProtocolException(ex.getMessage(), ex);
+            }
+        }
+
+    }
+
+    @Test
+    public void testBasicRedirect300() throws Exception {
+        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
+
+            @Override
+            public AsyncServerExchangeHandler get() {
+                return new NoKeepAliveRedirectService(HttpStatus.SC_MULTIPLE_CHOICES);
+            }
+
+        });
+        final HttpHost target = start();
+
+        final HttpClientContext context = HttpClientContext.create();
+        final Future<SimpleHttpResponse> future = httpclient.execute(
+                SimpleHttpRequest.get(target, "/oldlocation/"), context, null);
+        final HttpResponse response = future.get();
+        Assert.assertNotNull(response);
+
+        final HttpRequest request = context.getRequest();
+
+        Assert.assertEquals(HttpStatus.SC_MULTIPLE_CHOICES, response.getCode());
+        Assert.assertEquals("/oldlocation/", request.getRequestUri());
+    }
+
+    @Test
+    public void testBasicRedirect301NoKeepAlive() throws Exception {
+        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
+
+            @Override
+            public AsyncServerExchangeHandler get() {
+                return new NoKeepAliveRedirectService(HttpStatus.SC_MOVED_PERMANENTLY);
+            }
+
+        });
+
+        final HttpHost target = start();
+        final HttpClientContext context = HttpClientContext.create();
+        final Future<SimpleHttpResponse> future = httpclient.execute(
+                SimpleHttpRequest.get(target, "/oldlocation/"), context, null);
+        final HttpResponse response = future.get();
+        Assert.assertNotNull(response);
+
+        final HttpRequest request = context.getRequest();
+
+        Assert.assertEquals(HttpStatus.SC_OK, response.getCode());
+        Assert.assertEquals("/newlocation/", request.getRequestUri());
+        Assert.assertEquals(target, new HttpHost(request.getAuthority(), request.getScheme()));
+    }
+
+    @Test
+    public void testDefaultHeadersRedirect() throws Exception {
+        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
+
+            @Override
+            public AsyncServerExchangeHandler get() {
+                return new NoKeepAliveRedirectService(HttpStatus.SC_MOVED_TEMPORARILY);
+            }
+
+        });
+
+        final List<Header> defaultHeaders = new ArrayList<>(1);
+        defaultHeaders.add(new BasicHeader(HttpHeaders.USER_AGENT, "my-test-client"));
+        clientBuilder.setDefaultHeaders(defaultHeaders);
+
+        final HttpHost target = start();
+
+        final HttpClientContext context = HttpClientContext.create();
+
+        final Future<SimpleHttpResponse> future = httpclient.execute(
+                SimpleHttpRequest.get(target, "/oldlocation/"), context, null);
+        final HttpResponse response = future.get();
+        Assert.assertNotNull(response);
+
+        final HttpRequest request = context.getRequest();
+
+        Assert.assertEquals(HttpStatus.SC_OK, response.getCode());
+        Assert.assertEquals("/newlocation/", request.getRequestUri());
+
+        final Header header = request.getFirstHeader(HttpHeaders.USER_AGENT);
+        Assert.assertEquals("my-test-client", header.getValue());
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/6228a736/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp1AsyncStatefulConnManagement.java
----------------------------------------------------------------------
diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp1AsyncStatefulConnManagement.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp1AsyncStatefulConnManagement.java
new file mode 100644
index 0000000..7880f6c
--- /dev/null
+++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp1AsyncStatefulConnManagement.java
@@ -0,0 +1,319 @@
+/*
+ * ====================================================================
+ * 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.testing.async;
+
+import java.util.concurrent.Future;
+
+import org.apache.hc.client5.http.HttpRoute;
+import org.apache.hc.client5.http.UserTokenHandler;
+import org.apache.hc.client5.http.async.methods.SimpleHttpRequest;
+import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
+import org.apache.hc.client5.http.config.RequestConfig;
+import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
+import org.apache.hc.client5.http.impl.async.HttpAsyncClientBuilder;
+import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManager;
+import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBuilder;
+import org.apache.hc.client5.http.protocol.HttpClientContext;
+import org.apache.hc.client5.http.ssl.H2TlsStrategy;
+import org.apache.hc.client5.testing.SSLTestContexts;
+import org.apache.hc.core5.function.Supplier;
+import org.apache.hc.core5.http.ContentType;
+import org.apache.hc.core5.http.EndpointDetails;
+import org.apache.hc.core5.http.HttpException;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.HttpResponse;
+import org.apache.hc.core5.http.HttpStatus;
+import org.apache.hc.core5.http.config.H1Config;
+import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler;
+import org.apache.hc.core5.http.protocol.BasicHttpContext;
+import org.apache.hc.core5.http.protocol.HttpContext;
+import org.apache.hc.core5.http.protocol.HttpCoreContext;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExternalResource;
+
+public class TestHttp1AsyncStatefulConnManagement extends AbstractIntegrationTestBase<CloseableHttpAsyncClient> {
+
+    protected HttpAsyncClientBuilder clientBuilder;
+    protected PoolingAsyncClientConnectionManager connManager;
+
+    @Rule
+    public ExternalResource connManagerResource = new ExternalResource() {
+
+        @Override
+        protected void before() throws Throwable {
+            connManager = PoolingAsyncClientConnectionManagerBuilder.create()
+                    .setTlsStrategy(new H2TlsStrategy(SSLTestContexts.createClientSSLContext()))
+                    .build();
+        }
+
+        @Override
+        protected void after() {
+            if (connManager != null) {
+                connManager.close();
+                connManager = null;
+            }
+        }
+
+    };
+
+    @Rule
+    public ExternalResource clientResource = new ExternalResource() {
+
+        @Override
+        protected void before() throws Throwable {
+            clientBuilder = HttpAsyncClientBuilder.create()
+                    .setDefaultRequestConfig(RequestConfig.custom()
+                            .setSocketTimeout(TIMEOUT)
+                            .setConnectTimeout(TIMEOUT)
+                            .setConnectionRequestTimeout(TIMEOUT)
+                            .build())
+                    .setConnectionManager(connManager);
+        }
+
+    };
+
+    @Override
+    protected CloseableHttpAsyncClient createClient() throws Exception {
+        return clientBuilder.build();
+    }
+
+    @Override
+    public HttpHost start() throws Exception {
+        return super.start(null, H1Config.DEFAULT);
+    }
+
+    @Test
+    public void testStatefulConnections() throws Exception {
+        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
+
+            @Override
+            public AsyncServerExchangeHandler get() {
+                return new AbstractSimpleServerExchangeHandler() {
+
+                    @Override
+                    protected SimpleHttpResponse handle(
+                            final SimpleHttpRequest request,
+                            final HttpCoreContext context) throws HttpException {
+                        final SimpleHttpResponse response = new SimpleHttpResponse(HttpStatus.SC_OK);
+                        response.setBodyText("Whatever", ContentType.TEXT_PLAIN);
+                        return response;
+                    }
+                };
+            }
+
+        });
+
+        final UserTokenHandler userTokenHandler = new UserTokenHandler() {
+
+            @Override
+            public Object getUserToken(final HttpRoute route, final HttpContext context) {
+                return context.getAttribute("user");
+            }
+
+        };
+        clientBuilder.setUserTokenHandler(userTokenHandler);
+        final HttpHost target = start();
+
+        final int workerCount = 2;
+        final int requestCount = 5;
+
+        final HttpContext[] contexts = new HttpContext[workerCount];
+        final HttpWorker[] workers = new HttpWorker[workerCount];
+        for (int i = 0; i < contexts.length; i++) {
+            final HttpClientContext context = HttpClientContext.create();
+            contexts[i] = context;
+            workers[i] = new HttpWorker(
+                    "user" + i,
+                    context, requestCount, target, httpclient);
+        }
+
+        for (final HttpWorker worker : workers) {
+            worker.start();
+        }
+        for (final HttpWorker worker : workers) {
+            worker.join(LONG_TIMEOUT.toMillis());
+        }
+        for (final HttpWorker worker : workers) {
+            final Exception ex = worker.getException();
+            if (ex != null) {
+                throw ex;
+            }
+            Assert.assertEquals(requestCount, worker.getCount());
+        }
+
+        for (final HttpContext context : contexts) {
+            final String state0 = (String) context.getAttribute("r0");
+            Assert.assertNotNull(state0);
+            for (int r = 1; r < requestCount; r++) {
+                Assert.assertEquals(state0, context.getAttribute("r" + r));
+            }
+        }
+
+    }
+
+    static class HttpWorker extends Thread {
+
+        private final String uid;
+        private final HttpClientContext context;
+        private final int requestCount;
+        private final HttpHost target;
+        private final CloseableHttpAsyncClient httpclient;
+
+        private volatile Exception exception;
+        private volatile int count;
+
+        public HttpWorker(
+                final String uid,
+                final HttpClientContext context,
+                final int requestCount,
+                final HttpHost target,
+                final CloseableHttpAsyncClient httpclient) {
+            super();
+            this.uid = uid;
+            this.context = context;
+            this.requestCount = requestCount;
+            this.target = target;
+            this.httpclient = httpclient;
+            this.count = 0;
+        }
+
+        public int getCount() {
+            return count;
+        }
+
+        public Exception getException() {
+            return exception;
+        }
+
+        @Override
+        public void run() {
+            try {
+                context.setAttribute("user", uid);
+                for (int r = 0; r < requestCount; r++) {
+                    final SimpleHttpRequest httpget = SimpleHttpRequest.get(target, "/");
+                    final Future<SimpleHttpResponse> future = httpclient.execute(httpget, null);
+                    future.get();
+
+                    count++;
+                    final EndpointDetails endpointDetails = context.getEndpointDetails();
+                    final String connuid = Integer.toHexString(System.identityHashCode(endpointDetails));
+                    context.setAttribute("r" + r, connuid);
+                }
+
+            } catch (final Exception ex) {
+                exception = ex;
+            }
+        }
+
+    }
+
+    @Test
+    public void testRouteSpecificPoolRecylcing() throws Exception {
+        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
+
+            @Override
+            public AsyncServerExchangeHandler get() {
+                return new AbstractSimpleServerExchangeHandler() {
+
+                    @Override
+                    protected SimpleHttpResponse handle(
+                            final SimpleHttpRequest request,
+                            final HttpCoreContext context) throws HttpException {
+                        final SimpleHttpResponse response = new SimpleHttpResponse(HttpStatus.SC_OK);
+                        response.setBodyText("Whatever", ContentType.TEXT_PLAIN);
+                        return response;
+                    }
+                };
+            }
+
+        });
+
+        // This tests what happens when a maxed connection pool needs
+        // to kill the last idle connection to a route to build a new
+        // one to the same route.
+        final UserTokenHandler userTokenHandler = new UserTokenHandler() {
+
+            @Override
+            public Object getUserToken(final HttpRoute route, final HttpContext context) {
+                return context.getAttribute("user");
+            }
+
+        };
+        clientBuilder.setUserTokenHandler(userTokenHandler);
+
+        final HttpHost target = start();
+        final int maxConn = 2;
+        // We build a client with 2 max active // connections, and 2 max per route.
+        connManager.setMaxTotal(maxConn);
+        connManager.setDefaultMaxPerRoute(maxConn);
+
+        // Bottom of the pool : a *keep alive* connection to Route 1.
+        final HttpContext context1 = new BasicHttpContext();
+        context1.setAttribute("user", "stuff");
+
+        final Future<SimpleHttpResponse> future1 = httpclient.execute(SimpleHttpRequest.get(target, "/"), context1, null);
+        final HttpResponse response1 = future1.get();
+        Assert.assertNotNull(response1);
+        Assert.assertEquals(200, response1.getCode());
+
+        // The ConnPoolByRoute now has 1 free connection, out of 2 max
+        // The ConnPoolByRoute has one RouteSpcfcPool, that has one free connection
+        // for [localhost][stuff]
+
+        Thread.sleep(100);
+
+        // Send a very simple HTTP get (it MUST be simple, no auth, no proxy, no 302, no 401, ...)
+        // Send it to another route. Must be a keepalive.
+        final HttpContext context2 = new BasicHttpContext();
+
+        final Future<SimpleHttpResponse> future2 = httpclient.execute(SimpleHttpRequest.get(
+                new HttpHost("127.0.0.1", target.getPort(), target.getSchemeName()),"/"), context2, null);
+        final HttpResponse response2 = future2.get();
+        Assert.assertNotNull(response2);
+        Assert.assertEquals(200, response2.getCode());
+
+        // ConnPoolByRoute now has 2 free connexions, out of its 2 max.
+        // The [localhost][stuff] RouteSpcfcPool is the same as earlier
+        // And there is a [127.0.0.1][null] pool with 1 free connection
+
+        Thread.sleep(100);
+
+        // This will put the ConnPoolByRoute to the targeted state :
+        // [localhost][stuff] will not get reused because this call is [localhost][null]
+        // So the ConnPoolByRoute will need to kill one connection (it is maxed out globally).
+        // The killed conn is the oldest, which means the first HTTPGet ([localhost][stuff]).
+        // When this happens, the RouteSpecificPool becomes empty.
+        final HttpContext context3 = new BasicHttpContext();
+        final Future<SimpleHttpResponse> future3 = httpclient.execute(SimpleHttpRequest.get(target, "/"), context3, null);
+        final HttpResponse response3 = future3.get();
+        Assert.assertNotNull(response3);
+        Assert.assertEquals(200, response3.getCode());
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/6228a736/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp1ClientAuthentication.java
----------------------------------------------------------------------
diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp1ClientAuthentication.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp1ClientAuthentication.java
new file mode 100644
index 0000000..e998c43
--- /dev/null
+++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp1ClientAuthentication.java
@@ -0,0 +1,180 @@
+/*
+ * ====================================================================
+ * 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.testing.async;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.concurrent.Future;
+
+import org.apache.hc.client5.http.AuthenticationStrategy;
+import org.apache.hc.client5.http.async.methods.SimpleHttpRequest;
+import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
+import org.apache.hc.client5.http.auth.AuthSchemeProvider;
+import org.apache.hc.client5.http.auth.AuthScope;
+import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;
+import org.apache.hc.client5.http.config.RequestConfig;
+import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
+import org.apache.hc.client5.http.impl.async.HttpAsyncClientBuilder;
+import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManager;
+import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBuilder;
+import org.apache.hc.client5.http.protocol.HttpClientContext;
+import org.apache.hc.client5.http.ssl.H2TlsStrategy;
+import org.apache.hc.client5.testing.BasicTestAuthenticator;
+import org.apache.hc.client5.testing.SSLTestContexts;
+import org.apache.hc.core5.function.Decorator;
+import org.apache.hc.core5.function.Supplier;
+import org.apache.hc.core5.http.HeaderElements;
+import org.apache.hc.core5.http.HttpHeaders;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.HttpResponse;
+import org.apache.hc.core5.http.HttpStatus;
+import org.apache.hc.core5.http.HttpVersion;
+import org.apache.hc.core5.http.URIScheme;
+import org.apache.hc.core5.http.config.H1Config;
+import org.apache.hc.core5.http.config.Lookup;
+import org.apache.hc.core5.http.impl.HttpProcessors;
+import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExternalResource;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class TestHttp1ClientAuthentication extends AbstractHttpAsyncClientAuthentication<CloseableHttpAsyncClient> {
+
+    @Parameterized.Parameters(name = "HTTP/1.1 {0}")
+    public static Collection<Object[]> protocols() {
+        return Arrays.asList(new Object[][]{
+                {URIScheme.HTTP},
+                {URIScheme.HTTPS},
+        });
+    }
+
+    protected HttpAsyncClientBuilder clientBuilder;
+    protected PoolingAsyncClientConnectionManager connManager;
+
+    @Rule
+    public ExternalResource connManagerResource = new ExternalResource() {
+
+        @Override
+        protected void before() throws Throwable {
+            connManager = PoolingAsyncClientConnectionManagerBuilder.create()
+                    .setTlsStrategy(new H2TlsStrategy(SSLTestContexts.createClientSSLContext()))
+                    .build();
+        }
+
+        @Override
+        protected void after() {
+            if (connManager != null) {
+                connManager.close();
+                connManager = null;
+            }
+        }
+
+    };
+
+    @Rule
+    public ExternalResource clientResource = new ExternalResource() {
+
+        @Override
+        protected void before() throws Throwable {
+            clientBuilder = HttpAsyncClientBuilder.create()
+                    .setDefaultRequestConfig(RequestConfig.custom()
+                            .setSocketTimeout(TIMEOUT)
+                            .setConnectTimeout(TIMEOUT)
+                            .setConnectionRequestTimeout(TIMEOUT)
+                            .build())
+                    .setConnectionManager(connManager);
+        }
+
+    };
+
+    public TestHttp1ClientAuthentication(final URIScheme scheme) {
+        super(scheme, HttpVersion.HTTP_1_1);
+    }
+
+    @Override
+    void setDefaultAuthSchemeRegistry(final Lookup<AuthSchemeProvider> authSchemeRegistry) {
+        clientBuilder.setDefaultAuthSchemeRegistry(authSchemeRegistry);
+    }
+
+    @Override
+    void setTargetAuthenticationStrategy(final AuthenticationStrategy targetAuthStrategy) {
+        clientBuilder.setTargetAuthenticationStrategy(targetAuthStrategy);
+    }
+
+    @Override
+    protected CloseableHttpAsyncClient createClient() throws Exception {
+        return clientBuilder.build();
+    }
+
+    @Test
+    public void testBasicAuthenticationSuccessNonPersistentConnection() throws Exception {
+        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
+
+            @Override
+            public AsyncServerExchangeHandler get() {
+                return new AsyncEchoHandler();
+            }
+
+        });
+        final HttpHost target = start(
+                HttpProcessors.server(),
+                new Decorator<AsyncServerExchangeHandler>() {
+
+                    @Override
+                    public AsyncServerExchangeHandler decorate(final AsyncServerExchangeHandler exchangeHandler) {
+                        return new AuthenticatingAsyncDecorator(exchangeHandler, new BasicTestAuthenticator("test:test", "test realm")) {
+
+                            @Override
+                            protected void customizeUnauthorizedResponse(final HttpResponse unauthorized) {
+                                unauthorized.addHeader(HttpHeaders.CONNECTION, HeaderElements.CLOSE);
+                            }
+                        };
+                    }
+
+                },
+                H1Config.DEFAULT);
+
+        final TestCredentialsProvider credsProvider = new TestCredentialsProvider(
+                new UsernamePasswordCredentials("test", "test".toCharArray()));
+        final HttpClientContext context = HttpClientContext.create();
+        context.setCredentialsProvider(credsProvider);
+
+        final Future<SimpleHttpResponse> future = httpclient.execute(SimpleHttpRequest.get(target, "/"), context, null);
+        final HttpResponse response = future.get();
+
+        Assert.assertNotNull(response);
+        Assert.assertEquals(HttpStatus.SC_OK, response.getCode());
+        final AuthScope authscope = credsProvider.getAuthScope();
+        Assert.assertNotNull(authscope);
+        Assert.assertEquals("test realm", authscope.getRealm());
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/6228a736/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp2Async.java
----------------------------------------------------------------------
diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp2Async.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp2Async.java
new file mode 100644
index 0000000..b0035e9
--- /dev/null
+++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp2Async.java
@@ -0,0 +1,88 @@
+/*
+ * ====================================================================
+ * 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.testing.async;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import org.apache.hc.client5.http.config.RequestConfig;
+import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
+import org.apache.hc.client5.http.impl.async.Http2AsyncClientBuilder;
+import org.apache.hc.client5.http.ssl.H2TlsStrategy;
+import org.apache.hc.client5.testing.SSLTestContexts;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.URIScheme;
+import org.apache.hc.core5.http2.config.H2Config;
+import org.junit.Rule;
+import org.junit.rules.ExternalResource;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class TestHttp2Async extends AbstractHttpAsyncFundamentalsTest<CloseableHttpAsyncClient> {
+
+    @Parameterized.Parameters(name = "HTTP/2 {0}")
+    public static Collection<Object[]> protocols() {
+        return Arrays.asList(new Object[][]{
+                { URIScheme.HTTP },
+                { URIScheme.HTTPS }
+        });
+    }
+
+    protected Http2AsyncClientBuilder clientBuilder;
+
+    @Rule
+    public ExternalResource clientResource = new ExternalResource() {
+
+        @Override
+        protected void before() throws Throwable {
+            clientBuilder = Http2AsyncClientBuilder.create()
+                    .setDefaultRequestConfig(RequestConfig.custom()
+                            .setSocketTimeout(TIMEOUT)
+                            .setConnectTimeout(TIMEOUT)
+                            .setConnectionRequestTimeout(TIMEOUT)
+                            .build())
+                    .setTlsStrategy(new H2TlsStrategy(SSLTestContexts.createClientSSLContext()));
+        }
+
+    };
+
+    public TestHttp2Async(final URIScheme scheme) {
+        super(scheme);
+    }
+
+    @Override
+    protected CloseableHttpAsyncClient createClient() {
+        return clientBuilder.build();
+    }
+
+    @Override
+    public HttpHost start() throws Exception {
+        return super.start(null, H2Config.DEFAULT);
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/6228a736/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp2AsyncMinimal.java
----------------------------------------------------------------------
diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp2AsyncMinimal.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp2AsyncMinimal.java
index 58765a2..a3369de 100644
--- a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp2AsyncMinimal.java
+++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp2AsyncMinimal.java
@@ -26,50 +26,22 @@
  */
 package org.apache.hc.client5.testing.async;
 
-import java.net.InetSocketAddress;
 import java.util.Arrays;
 import java.util.Collection;
-import java.util.LinkedList;
-import java.util.Queue;
-import java.util.Random;
-import java.util.concurrent.Future;
 
-import org.apache.hc.client5.http.async.methods.AsyncRequestBuilder;
-import org.apache.hc.client5.http.async.methods.SimpleHttpRequest;
-import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
 import org.apache.hc.client5.http.impl.async.HttpAsyncClients;
 import org.apache.hc.client5.http.impl.async.MinimalHttp2AsyncClient;
-import org.apache.hc.client5.http.protocol.HttpClientContext;
 import org.apache.hc.client5.http.ssl.H2TlsStrategy;
 import org.apache.hc.client5.testing.SSLTestContexts;
-import org.apache.hc.core5.function.Supplier;
-import org.apache.hc.core5.http.ContentType;
 import org.apache.hc.core5.http.HttpHost;
-import org.apache.hc.core5.http.HttpResponse;
-import org.apache.hc.core5.http.Message;
 import org.apache.hc.core5.http.URIScheme;
-import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler;
-import org.apache.hc.core5.http.nio.BasicResponseConsumer;
-import org.apache.hc.core5.http.nio.entity.BasicAsyncEntityConsumer;
 import org.apache.hc.core5.http2.config.H2Config;
-import org.apache.hc.core5.io.ShutdownType;
 import org.apache.hc.core5.reactor.IOReactorConfig;
-import org.apache.hc.core5.reactor.ListenerEndpoint;
-import org.apache.hc.core5.testing.nio.Http2TestServer;
-import org.apache.hc.core5.util.TimeValue;
-import org.apache.hc.core5.util.Timeout;
-import org.hamcrest.CoreMatchers;
-import org.junit.Assert;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExternalResource;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 
 @RunWith(Parameterized.class)
-public class TestHttp2AsyncMinimal {
-
-    public static final Timeout TIMEOUT = Timeout.ofSeconds(30);
+public class TestHttp2AsyncMinimal extends AbstractHttpAsyncFundamentalsTest<MinimalHttp2AsyncClient> {
 
     @Parameterized.Parameters(name = "{0}")
     public static Collection<Object[]> protocols() {
@@ -79,141 +51,22 @@ public class TestHttp2AsyncMinimal {
         });
     }
 
-    protected final URIScheme scheme;
-
     public TestHttp2AsyncMinimal(final URIScheme scheme) {
-        this.scheme = scheme;
-    }
-
-    protected Http2TestServer server;
-    protected MinimalHttp2AsyncClient httpclient;
-
-    @Rule
-    public ExternalResource serverResource = new ExternalResource() {
-
-        @Override
-        protected void before() throws Throwable {
-            server = new Http2TestServer(
-                    IOReactorConfig.custom()
-                        .setSoTimeout(TIMEOUT)
-                        .build(),
-                    scheme == URIScheme.HTTPS ? SSLTestContexts.createServerSSLContext() : null);
-            server.register("/echo/*", new Supplier<AsyncServerExchangeHandler>() {
-
-                @Override
-                public AsyncServerExchangeHandler get() {
-                    return new AsyncEchoHandler();
-                }
-
-            });
-            server.register("/random/*", new Supplier<AsyncServerExchangeHandler>() {
-
-                @Override
-                public AsyncServerExchangeHandler get() {
-                    return new AsyncRandomHandler();
-                }
-
-            });
-        }
-
-        @Override
-        protected void after() {
-            if (server != null) {
-                server.shutdown(TimeValue.ofSeconds(5));
-                server = null;
-            }
-        }
-
-    };
-
-    @Rule
-    public ExternalResource clientResource = new ExternalResource() {
-
-        @Override
-        protected void before() throws Throwable {
-            final IOReactorConfig ioReactorConfig = IOReactorConfig.custom()
-                    .setSoTimeout(TIMEOUT)
-                    .build();
-            httpclient = HttpAsyncClients.createHttp2Minimal(
-                    H2Config.DEFAULT, ioReactorConfig, new H2TlsStrategy(SSLTestContexts.createClientSSLContext()));
-        }
-
-        @Override
-        protected void after() {
-            if (httpclient != null) {
-                httpclient.shutdown(ShutdownType.GRACEFUL);
-                httpclient = null;
-            }
-        }
-
-    };
-
-    public HttpHost start() throws Exception {
-        server.start(H2Config.DEFAULT);
-        final Future<ListenerEndpoint> endpointFuture = server.listen(new InetSocketAddress(0));
-        httpclient.start();
-        final ListenerEndpoint endpoint = endpointFuture.get();
-        final InetSocketAddress address = (InetSocketAddress) endpoint.getAddress();
-        return new HttpHost("localhost", address.getPort(), scheme.name());
+        super(scheme);
     }
 
-    @Test
-    public void testSequenctialGetRequests() throws Exception {
-        final HttpHost target = start();
-        for (int i = 0; i < 3; i++) {
-            final Future<SimpleHttpResponse> future = httpclient.execute(
-                    SimpleHttpRequest.get(target, "/random/2048"), null);
-            final SimpleHttpResponse response = future.get();
-            Assert.assertThat(response, CoreMatchers.notNullValue());
-            Assert.assertThat(response.getCode(), CoreMatchers.equalTo(200));
-            final String body = response.getBodyText();
-            Assert.assertThat(body, CoreMatchers.notNullValue());
-            Assert.assertThat(body.length(), CoreMatchers.equalTo(2048));
-        }
+    @Override
+    protected MinimalHttp2AsyncClient createClient() throws Exception {
+        final IOReactorConfig ioReactorConfig = IOReactorConfig.custom()
+                .setSoTimeout(TIMEOUT)
+                .build();
+        return HttpAsyncClients.createHttp2Minimal(
+                H2Config.DEFAULT, ioReactorConfig, new H2TlsStrategy(SSLTestContexts.createClientSSLContext()));
     }
 
-    @Test
-    public void testSequenctialHeadRequests() throws Exception {
-        final HttpHost target = start();
-        for (int i = 0; i < 3; i++) {
-            final Future<SimpleHttpResponse> future = httpclient.execute(
-                    SimpleHttpRequest.head(target, "/random/2048"), null);
-            final SimpleHttpResponse response = future.get();
-            Assert.assertThat(response, CoreMatchers.notNullValue());
-            Assert.assertThat(response.getCode(), CoreMatchers.equalTo(200));
-            final String body = response.getBodyText();
-            Assert.assertThat(body, CoreMatchers.nullValue());
-        }
-    }
-
-    @Test
-    public void testConcurrentPostsOver() throws Exception {
-        final HttpHost target = start();
-        final byte[] b1 = new byte[1024];
-        final Random rnd = new Random(System.currentTimeMillis());
-        rnd.nextBytes(b1);
-
-        final int reqCount = 20;
-
-        final Queue<Future<Message<HttpResponse, byte[]>>> queue = new LinkedList<>();
-        for (int i = 0; i < reqCount; i++) {
-            final Future<Message<HttpResponse, byte[]>> future = httpclient.execute(
-                    AsyncRequestBuilder.post(target, "/echo/")
-                            .setEntity(b1, ContentType.APPLICATION_OCTET_STREAM)
-                            .build(),
-                    new BasicResponseConsumer<>(new BasicAsyncEntityConsumer()), HttpClientContext.create(), null);
-            queue.add(future);
-        }
-
-        while (!queue.isEmpty()) {
-            final Future<Message<HttpResponse, byte[]>> future = queue.remove();
-            final Message<HttpResponse, byte[]> responseMessage = future.get();
-            Assert.assertThat(responseMessage, CoreMatchers.notNullValue());
-            final HttpResponse response = responseMessage.getHead();
-            Assert.assertThat(response.getCode(), CoreMatchers.equalTo(200));
-            final byte[] b2 = responseMessage.getBody();
-            Assert.assertThat(b1, CoreMatchers.equalTo(b2));
-        }
+    @Override
+    public HttpHost start() throws Exception {
+        return super.start(null, H2Config.DEFAULT);
     }
 
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/6228a736/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp2AsyncRedirect.java
----------------------------------------------------------------------
diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp2AsyncRedirect.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp2AsyncRedirect.java
new file mode 100644
index 0000000..63011af
--- /dev/null
+++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp2AsyncRedirect.java
@@ -0,0 +1,88 @@
+/*
+ * ====================================================================
+ * 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.testing.async;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import org.apache.hc.client5.http.config.RequestConfig;
+import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
+import org.apache.hc.client5.http.impl.async.Http2AsyncClientBuilder;
+import org.apache.hc.client5.http.ssl.H2TlsStrategy;
+import org.apache.hc.client5.testing.SSLTestContexts;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.URIScheme;
+import org.apache.hc.core5.http2.config.H2Config;
+import org.junit.Rule;
+import org.junit.rules.ExternalResource;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class TestHttp2AsyncRedirect extends AbstractHttpAsyncRedirectsTest<CloseableHttpAsyncClient> {
+
+    @Parameterized.Parameters(name = "HTTP/2 {0}")
+    public static Collection<Object[]> protocols() {
+        return Arrays.asList(new Object[][]{
+                { URIScheme.HTTP },
+                { URIScheme.HTTPS }
+        });
+    }
+
+    protected Http2AsyncClientBuilder clientBuilder;
+
+    @Rule
+    public ExternalResource clientResource = new ExternalResource() {
+
+        @Override
+        protected void before() throws Throwable {
+            clientBuilder = Http2AsyncClientBuilder.create()
+                    .setDefaultRequestConfig(RequestConfig.custom()
+                            .setSocketTimeout(TIMEOUT)
+                            .setConnectTimeout(TIMEOUT)
+                            .setConnectionRequestTimeout(TIMEOUT)
+                            .build())
+                    .setTlsStrategy(new H2TlsStrategy(SSLTestContexts.createClientSSLContext()));
+        }
+
+    };
+
+    public TestHttp2AsyncRedirect(final URIScheme scheme) {
+        super(scheme);
+    }
+
+    @Override
+    protected CloseableHttpAsyncClient createClient() {
+        return clientBuilder.build();
+    }
+
+    @Override
+    public HttpHost start() throws Exception {
+        return super.start(null, H2Config.DEFAULT);
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/6228a736/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp2ClientAuthentication.java
----------------------------------------------------------------------
diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp2ClientAuthentication.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp2ClientAuthentication.java
new file mode 100644
index 0000000..5cb1d80
--- /dev/null
+++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp2ClientAuthentication.java
@@ -0,0 +1,97 @@
+/*
+ * ====================================================================
+ * 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.testing.async;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import org.apache.hc.client5.http.AuthenticationStrategy;
+import org.apache.hc.client5.http.auth.AuthSchemeProvider;
+import org.apache.hc.client5.http.config.RequestConfig;
+import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
+import org.apache.hc.client5.http.impl.async.Http2AsyncClientBuilder;
+import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManager;
+import org.apache.hc.client5.http.ssl.H2TlsStrategy;
+import org.apache.hc.client5.testing.SSLTestContexts;
+import org.apache.hc.core5.http.HttpVersion;
+import org.apache.hc.core5.http.URIScheme;
+import org.apache.hc.core5.http.config.Lookup;
+import org.junit.Rule;
+import org.junit.rules.ExternalResource;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class TestHttp2ClientAuthentication extends AbstractHttpAsyncClientAuthentication<CloseableHttpAsyncClient> {
+
+    @Parameterized.Parameters(name = "HTTP/2 {0}")
+    public static Collection<Object[]> protocols() {
+        return Arrays.asList(new Object[][]{
+                {URIScheme.HTTP},
+                {URIScheme.HTTPS},
+        });
+    }
+
+    protected Http2AsyncClientBuilder clientBuilder;
+    protected PoolingAsyncClientConnectionManager connManager;
+
+    @Rule
+    public ExternalResource clientResource = new ExternalResource() {
+
+        @Override
+        protected void before() throws Throwable {
+            clientBuilder = Http2AsyncClientBuilder.create()
+                    .setDefaultRequestConfig(RequestConfig.custom()
+                            .setSocketTimeout(TIMEOUT)
+                            .setConnectTimeout(TIMEOUT)
+                            .setConnectionRequestTimeout(TIMEOUT)
+                            .build())
+                    .setTlsStrategy(new H2TlsStrategy(SSLTestContexts.createClientSSLContext()));
+        }
+
+    };
+
+    public TestHttp2ClientAuthentication(final URIScheme scheme) {
+        super(scheme, HttpVersion.HTTP_2);
+    }
+
+    @Override
+    void setDefaultAuthSchemeRegistry(final Lookup<AuthSchemeProvider> authSchemeRegistry) {
+        clientBuilder.setDefaultAuthSchemeRegistry(authSchemeRegistry);
+    }
+
+    @Override
+    void setTargetAuthenticationStrategy(final AuthenticationStrategy targetAuthStrategy) {
+        clientBuilder.setTargetAuthenticationStrategy(targetAuthStrategy);
+    }
+
+    @Override
+    protected CloseableHttpAsyncClient createClient() throws Exception {
+        return clientBuilder.build();
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/6228a736/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttpAsync.java
----------------------------------------------------------------------
diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttpAsync.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttpAsync.java
deleted file mode 100644
index 5f32a6f..0000000
--- a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttpAsync.java
+++ /dev/null
@@ -1,252 +0,0 @@
-/*
- * ====================================================================
- * 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.testing.async;
-
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.LinkedList;
-import java.util.Queue;
-import java.util.Random;
-import java.util.concurrent.Future;
-
-import org.apache.hc.client5.http.async.methods.AsyncRequestBuilder;
-import org.apache.hc.client5.http.async.methods.SimpleHttpRequest;
-import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
-import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
-import org.apache.hc.client5.http.impl.async.HttpAsyncClients;
-import org.apache.hc.client5.http.protocol.HttpClientContext;
-import org.apache.hc.core5.http.ContentType;
-import org.apache.hc.core5.http.HeaderElements;
-import org.apache.hc.core5.http.HttpHeaders;
-import org.apache.hc.core5.http.HttpHost;
-import org.apache.hc.core5.http.HttpResponse;
-import org.apache.hc.core5.http.Message;
-import org.apache.hc.core5.http.URIScheme;
-import org.apache.hc.core5.http.nio.BasicResponseConsumer;
-import org.apache.hc.core5.http.nio.entity.BasicAsyncEntityConsumer;
-import org.hamcrest.CoreMatchers;
-import org.junit.Assert;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-
-@RunWith(Parameterized.class)
-public class TestHttpAsync extends IntegrationTestBase {
-
-    @Parameterized.Parameters(name = "{0}")
-    public static Collection<Object[]> protocols() {
-        return Arrays.asList(new Object[][]{
-                { URIScheme.HTTP },
-                { URIScheme.HTTPS },
-        });
-    }
-
-    public TestHttpAsync(final URIScheme scheme) {
-        super(scheme);
-    }
-
-    @Test
-    public void testSequenctialGetRequests() throws Exception {
-        final HttpHost target = start();
-        for (int i = 0; i < 3; i++) {
-            final Future<SimpleHttpResponse> future = httpclient.execute(
-                    SimpleHttpRequest.get(target, "/random/2048"), null);
-            final SimpleHttpResponse response = future.get();
-            Assert.assertThat(response, CoreMatchers.notNullValue());
-            Assert.assertThat(response.getCode(), CoreMatchers.equalTo(200));
-            final String body = response.getBodyText();
-            Assert.assertThat(body, CoreMatchers.notNullValue());
-            Assert.assertThat(body.length(), CoreMatchers.equalTo(2048));
-        }
-    }
-
-    @Test
-    public void testSequenctialHeadRequests() throws Exception {
-        final HttpHost target = start();
-        for (int i = 0; i < 3; i++) {
-            final Future<SimpleHttpResponse> future = httpclient.execute(
-                    SimpleHttpRequest.head(target, "/random/2048"), null);
-            final SimpleHttpResponse response = future.get();
-            Assert.assertThat(response, CoreMatchers.notNullValue());
-            Assert.assertThat(response.getCode(), CoreMatchers.equalTo(200));
-            final String body = response.getBodyText();
-            Assert.assertThat(body, CoreMatchers.nullValue());
-        }
-    }
-
-    @Test
-    public void testSequenctialGetRequestsCloseConnection() throws Exception {
-        final HttpHost target = start();
-        for (int i = 0; i < 3; i++) {
-            final SimpleHttpRequest get = SimpleHttpRequest.get(target, "/random/2048");
-            get.setHeader(HttpHeaders.CONNECTION, HeaderElements.CLOSE);
-            final Future<SimpleHttpResponse> future = httpclient.execute(get, null);
-            final SimpleHttpResponse response = future.get();
-            Assert.assertThat(response, CoreMatchers.notNullValue());
-            Assert.assertThat(response.getCode(), CoreMatchers.equalTo(200));
-            final String body = response.getBodyText();
-            Assert.assertThat(body, CoreMatchers.notNullValue());
-            Assert.assertThat(body.length(), CoreMatchers.equalTo(2048));
-        }
-    }
-
-    @Test
-    public void testSequenctialPostRequests() throws Exception {
-        final HttpHost target = start();
-        for (int i = 0; i < 3; i++) {
-            final byte[] b1 = new byte[1024];
-            final Random rnd = new Random(System.currentTimeMillis());
-            rnd.nextBytes(b1);
-            final Future<Message<HttpResponse, byte[]>> future = httpclient.execute(
-                    AsyncRequestBuilder.post(target, "/echo/")
-                        .setEntity(b1, ContentType.APPLICATION_OCTET_STREAM)
-                        .build(),
-                    new BasicResponseConsumer<>(new BasicAsyncEntityConsumer()), HttpClientContext.create(), null);
-            final Message<HttpResponse, byte[]> responseMessage = future.get();
-            Assert.assertThat(responseMessage, CoreMatchers.notNullValue());
-            final HttpResponse response = responseMessage.getHead();
-            Assert.assertThat(response.getCode(), CoreMatchers.equalTo(200));
-            final byte[] b2 = responseMessage.getBody();
-            Assert.assertThat(b1, CoreMatchers.equalTo(b2));
-        }
-    }
-
-    @Test
-    public void testConcurrentPostsOverMultipleConnections() throws Exception {
-        final HttpHost target = start();
-        final byte[] b1 = new byte[1024];
-        final Random rnd = new Random(System.currentTimeMillis());
-        rnd.nextBytes(b1);
-
-        final int reqCount = 20;
-
-        connManager.setDefaultMaxPerRoute(reqCount);
-        connManager.setMaxTotal(100);
-
-        final Queue<Future<Message<HttpResponse, byte[]>>> queue = new LinkedList<>();
-        for (int i = 0; i < reqCount; i++) {
-            final Future<Message<HttpResponse, byte[]>> future = httpclient.execute(
-                    AsyncRequestBuilder.post(target, "/echo/")
-                            .setEntity(b1, ContentType.APPLICATION_OCTET_STREAM)
-                            .build(),
-                    new BasicResponseConsumer<>(new BasicAsyncEntityConsumer()), HttpClientContext.create(), null);
-            queue.add(future);
-        }
-
-        while (!queue.isEmpty()) {
-            final Future<Message<HttpResponse, byte[]>> future = queue.remove();
-            final Message<HttpResponse, byte[]> responseMessage = future.get();
-            Assert.assertThat(responseMessage, CoreMatchers.notNullValue());
-            final HttpResponse response = responseMessage.getHead();
-            Assert.assertThat(response.getCode(), CoreMatchers.equalTo(200));
-            final byte[] b2 = responseMessage.getBody();
-            Assert.assertThat(b1, CoreMatchers.equalTo(b2));
-        }
-    }
-
-    @Test
-    public void testConcurrentPostsOverSingleConnection() throws Exception {
-        final HttpHost target = start();
-        final byte[] b1 = new byte[1024];
-        final Random rnd = new Random(System.currentTimeMillis());
-        rnd.nextBytes(b1);
-
-        final int reqCount = 20;
-
-        connManager.setDefaultMaxPerRoute(1);
-        connManager.setMaxTotal(100);
-
-        final Queue<Future<Message<HttpResponse, byte[]>>> queue = new LinkedList<>();
-        for (int i = 0; i < reqCount; i++) {
-            final Future<Message<HttpResponse, byte[]>> future = httpclient.execute(
-                    AsyncRequestBuilder.post(target, "/echo/")
-                            .setEntity(b1, ContentType.APPLICATION_OCTET_STREAM)
-                            .build(),
-                    new BasicResponseConsumer<>(new BasicAsyncEntityConsumer()), HttpClientContext.create(), null);
-            queue.add(future);
-        }
-
-        while (!queue.isEmpty()) {
-            final Future<Message<HttpResponse, byte[]>> future = queue.remove();
-            final Message<HttpResponse, byte[]> responseMessage = future.get();
-            Assert.assertThat(responseMessage, CoreMatchers.notNullValue());
-            final HttpResponse response = responseMessage.getHead();
-            Assert.assertThat(response.getCode(), CoreMatchers.equalTo(200));
-            final byte[] b2 = responseMessage.getBody();
-            Assert.assertThat(b1, CoreMatchers.equalTo(b2));
-        }
-    }
-
-    @Test
-    public void testSharedPool() throws Exception {
-        final HttpHost target = start();
-        final Future<SimpleHttpResponse> future1 = httpclient.execute(
-                SimpleHttpRequest.get(target, "/random/2048"), null);
-        final SimpleHttpResponse response1 = future1.get();
-        Assert.assertThat(response1, CoreMatchers.notNullValue());
-        Assert.assertThat(response1.getCode(), CoreMatchers.equalTo(200));
-        final String body1 = response1.getBodyText();
-        Assert.assertThat(body1, CoreMatchers.notNullValue());
-        Assert.assertThat(body1.length(), CoreMatchers.equalTo(2048));
-
-
-        try (final CloseableHttpAsyncClient httpclient2 = HttpAsyncClients.custom()
-                .setConnectionManager(connManager)
-                .setConnectionManagerShared(true)
-                .build()) {
-            httpclient2.start();
-            final Future<SimpleHttpResponse> future2 = httpclient2.execute(
-                    SimpleHttpRequest.get(target, "/random/2048"), null);
-            final SimpleHttpResponse response2 = future2.get();
-            Assert.assertThat(response2, CoreMatchers.notNullValue());
-            Assert.assertThat(response2.getCode(), CoreMatchers.equalTo(200));
-            final String body2 = response2.getBodyText();
-            Assert.assertThat(body2, CoreMatchers.notNullValue());
-            Assert.assertThat(body2.length(), CoreMatchers.equalTo(2048));
-        }
-
-        final Future<SimpleHttpResponse> future3 = httpclient.execute(
-                SimpleHttpRequest.get(target, "/random/2048"), null);
-        final SimpleHttpResponse response3 = future3.get();
-        Assert.assertThat(response3, CoreMatchers.notNullValue());
-        Assert.assertThat(response3.getCode(), CoreMatchers.equalTo(200));
-        final String body3 = response3.getBodyText();
-        Assert.assertThat(body3, CoreMatchers.notNullValue());
-        Assert.assertThat(body3.length(), CoreMatchers.equalTo(2048));
-    }
-
-    @Test
-    public void testBadRequest() throws Exception {
-        final HttpHost target = start();
-        final Future<SimpleHttpResponse> future = httpclient.execute(
-                SimpleHttpRequest.get(target, "/random/boom"), null);
-        final SimpleHttpResponse response = future.get();
-        Assert.assertThat(response, CoreMatchers.notNullValue());
-        Assert.assertThat(response.getCode(), CoreMatchers.equalTo(400));
-    }
-
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/6228a736/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttpAsyncMinimal.java
----------------------------------------------------------------------
diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttpAsyncMinimal.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttpAsyncMinimal.java
index b7b1ef5..6173711 100644
--- a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttpAsyncMinimal.java
+++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttpAsyncMinimal.java
@@ -26,7 +26,6 @@
  */
 package org.apache.hc.client5.testing.async;
 
-import java.net.InetSocketAddress;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.LinkedList;
@@ -36,8 +35,6 @@ import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
 
 import org.apache.hc.client5.http.async.methods.AsyncRequestBuilder;
-import org.apache.hc.client5.http.async.methods.SimpleHttpRequest;
-import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
 import org.apache.hc.client5.http.impl.async.HttpAsyncClients;
 import org.apache.hc.client5.http.impl.async.MinimalHttpAsyncClient;
 import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManager;
@@ -45,7 +42,6 @@ import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBu
 import org.apache.hc.client5.http.protocol.HttpClientContext;
 import org.apache.hc.client5.http.ssl.H2TlsStrategy;
 import org.apache.hc.client5.testing.SSLTestContexts;
-import org.apache.hc.core5.function.Supplier;
 import org.apache.hc.core5.http.ContentType;
 import org.apache.hc.core5.http.HttpHost;
 import org.apache.hc.core5.http.HttpResponse;
@@ -54,31 +50,21 @@ import org.apache.hc.core5.http.Message;
 import org.apache.hc.core5.http.URIScheme;
 import org.apache.hc.core5.http.config.H1Config;
 import org.apache.hc.core5.http.nio.AsyncClientEndpoint;
-import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler;
 import org.apache.hc.core5.http.nio.BasicResponseConsumer;
 import org.apache.hc.core5.http.nio.entity.BasicAsyncEntityConsumer;
 import org.apache.hc.core5.http2.HttpVersionPolicy;
 import org.apache.hc.core5.http2.config.H2Config;
-import org.apache.hc.core5.io.ShutdownType;
 import org.apache.hc.core5.reactor.IOReactorConfig;
-import org.apache.hc.core5.reactor.ListenerEndpoint;
-import org.apache.hc.core5.testing.nio.Http2TestServer;
-import org.apache.hc.core5.util.TimeValue;
-import org.apache.hc.core5.util.Timeout;
 import org.hamcrest.CoreMatchers;
 import org.junit.Assert;
-import org.junit.Rule;
 import org.junit.Test;
-import org.junit.rules.ExternalResource;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 
 @RunWith(Parameterized.class)
-public class TestHttpAsyncMinimal {
+public class TestHttpAsyncMinimal extends AbstractHttpAsyncFundamentalsTest<MinimalHttpAsyncClient> {
 
-    public static final Timeout TIMEOUT = Timeout.ofSeconds(30);
-
-    @Parameterized.Parameters(name = "{0} {1}")
+    @Parameterized.Parameters(name = "Minimal {0} {1}")
     public static Collection<Object[]> protocols() {
         return Arrays.asList(new Object[][]{
                 { HttpVersion.HTTP_1_1, URIScheme.HTTP },
@@ -89,158 +75,40 @@ public class TestHttpAsyncMinimal {
     }
 
     protected final HttpVersion version;
-    protected final URIScheme scheme;
 
     public TestHttpAsyncMinimal(final HttpVersion version, final URIScheme scheme) {
+        super(scheme);
         this.version = version;
-        this.scheme = scheme;
     }
 
-    protected Http2TestServer server;
-    protected MinimalHttpAsyncClient httpclient;
-
-    @Rule
-    public ExternalResource serverResource = new ExternalResource() {
-
-        @Override
-        protected void before() throws Throwable {
-            server = new Http2TestServer(
-                    IOReactorConfig.custom()
-                        .setSoTimeout(TIMEOUT)
-                        .build(),
-                    scheme == URIScheme.HTTPS ? SSLTestContexts.createServerSSLContext() : null);
-            server.register("/echo/*", new Supplier<AsyncServerExchangeHandler>() {
-
-                @Override
-                public AsyncServerExchangeHandler get() {
-                    return new AsyncEchoHandler();
-                }
-
-            });
-            server.register("/random/*", new Supplier<AsyncServerExchangeHandler>() {
-
-                @Override
-                public AsyncServerExchangeHandler get() {
-                    return new AsyncRandomHandler();
-                }
-
-            });
-        }
-
-        @Override
-        protected void after() {
-            if (server != null) {
-                server.shutdown(TimeValue.ofSeconds(5));
-                server = null;
-            }
-        }
-
-    };
-
-    @Rule
-    public ExternalResource clientResource = new ExternalResource() {
-
-        @Override
-        protected void before() throws Throwable {
-            final PoolingAsyncClientConnectionManager connectionManager = PoolingAsyncClientConnectionManagerBuilder.create()
-                    .setTlsStrategy(new H2TlsStrategy(SSLTestContexts.createClientSSLContext()))
-                    .build();
-            final IOReactorConfig ioReactorConfig = IOReactorConfig.custom()
-                    .setSoTimeout(TIMEOUT)
-                    .build();
-            if (version.greaterEquals(HttpVersion.HTTP_2)) {
-                httpclient = HttpAsyncClients.createMinimal(
-                        HttpVersionPolicy.FORCE_HTTP_2, H2Config.DEFAULT, H1Config.DEFAULT, ioReactorConfig, connectionManager);
-            } else {
-                httpclient = HttpAsyncClients.createMinimal(
-                        HttpVersionPolicy.FORCE_HTTP_1, H2Config.DEFAULT, H1Config.DEFAULT, ioReactorConfig, connectionManager);
-            }
-        }
-
-        @Override
-        protected void after() {
-            if (httpclient != null) {
-                httpclient.shutdown(ShutdownType.GRACEFUL);
-                httpclient = null;
-            }
-        }
-
-    };
-
-    public HttpHost start() throws Exception {
+    @Override
+    protected MinimalHttpAsyncClient createClient() throws Exception {
+        final PoolingAsyncClientConnectionManager connectionManager = PoolingAsyncClientConnectionManagerBuilder.create()
+                .setTlsStrategy(new H2TlsStrategy(SSLTestContexts.createClientSSLContext()))
+                .build();
+        final IOReactorConfig ioReactorConfig = IOReactorConfig.custom()
+                .setSoTimeout(TIMEOUT)
+                .build();
         if (version.greaterEquals(HttpVersion.HTTP_2)) {
-            server.start(H2Config.DEFAULT);
+            return HttpAsyncClients.createMinimal(
+                    HttpVersionPolicy.FORCE_HTTP_2, H2Config.DEFAULT, H1Config.DEFAULT, ioReactorConfig, connectionManager);
         } else {
-            server.start(H1Config.DEFAULT);
+            return HttpAsyncClients.createMinimal(
+                    HttpVersionPolicy.FORCE_HTTP_1, H2Config.DEFAULT, H1Config.DEFAULT, ioReactorConfig, connectionManager);
         }
-        final Future<ListenerEndpoint> endpointFuture = server.listen(new InetSocketAddress(0));
-        httpclient.start();
-        final ListenerEndpoint endpoint = endpointFuture.get();
-        final InetSocketAddress address = (InetSocketAddress) endpoint.getAddress();
-        return new HttpHost("localhost", address.getPort(), scheme.name());
     }
 
-    @Test
-    public void testSequenctialGetRequests() throws Exception {
-        final HttpHost target = start();
-        for (int i = 0; i < 3; i++) {
-            final Future<SimpleHttpResponse> future = httpclient.execute(
-                    SimpleHttpRequest.get(target, "/random/2048"), null);
-            final SimpleHttpResponse response = future.get();
-            Assert.assertThat(response, CoreMatchers.notNullValue());
-            Assert.assertThat(response.getCode(), CoreMatchers.equalTo(200));
-            final String body = response.getBodyText();
-            Assert.assertThat(body, CoreMatchers.notNullValue());
-            Assert.assertThat(body.length(), CoreMatchers.equalTo(2048));
-        }
-    }
-
-    @Test
-    public void testSequenctialHeadRequests() throws Exception {
-        final HttpHost target = start();
-        for (int i = 0; i < 3; i++) {
-            final Future<SimpleHttpResponse> future = httpclient.execute(
-                    SimpleHttpRequest.head(target, "/random/2048"), null);
-            final SimpleHttpResponse response = future.get();
-            Assert.assertThat(response, CoreMatchers.notNullValue());
-            Assert.assertThat(response.getCode(), CoreMatchers.equalTo(200));
-            final String body = response.getBodyText();
-            Assert.assertThat(body, CoreMatchers.nullValue());
-        }
-    }
-
-    @Test
-    public void testConcurrentPostsOverMultipleConnections() throws Exception {
-        final HttpHost target = start();
-        final byte[] b1 = new byte[1024];
-        final Random rnd = new Random(System.currentTimeMillis());
-        rnd.nextBytes(b1);
-
-        final int reqCount = 20;
-
-        final Queue<Future<Message<HttpResponse, byte[]>>> queue = new LinkedList<>();
-        for (int i = 0; i < reqCount; i++) {
-            final Future<Message<HttpResponse, byte[]>> future = httpclient.execute(
-                    AsyncRequestBuilder.post(target, "/echo/")
-                            .setEntity(b1, ContentType.APPLICATION_OCTET_STREAM)
-                            .build(),
-                    new BasicResponseConsumer<>(new BasicAsyncEntityConsumer()), HttpClientContext.create(), null);
-            queue.add(future);
-        }
-
-        while (!queue.isEmpty()) {
-            final Future<Message<HttpResponse, byte[]>> future = queue.remove();
-            final Message<HttpResponse, byte[]> responseMessage = future.get();
-            Assert.assertThat(responseMessage, CoreMatchers.notNullValue());
-            final HttpResponse response = responseMessage.getHead();
-            Assert.assertThat(response.getCode(), CoreMatchers.equalTo(200));
-            final byte[] b2 = responseMessage.getBody();
-            Assert.assertThat(b1, CoreMatchers.equalTo(b2));
+    @Override
+    public HttpHost start() throws Exception {
+        if (version.greaterEquals(HttpVersion.HTTP_2)) {
+            return super.start(null, H2Config.DEFAULT);
+        } else {
+            return super.start(null, H1Config.DEFAULT);
         }
     }
 
     @Test
-    public void testConcurrentPostsOverSingleConnection() throws Exception {
+    public void testConcurrentPostRequestsSameEndpoint() throws Exception {
         final HttpHost target = start();
         final byte[] b1 = new byte[1024];
         final Random rnd = new Random(System.currentTimeMillis());

http://git-wip-us.apache.org/repos/asf/httpcomponents-client/blob/6228a736/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncExecRuntimeImpl.java
----------------------------------------------------------------------
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncExecRuntimeImpl.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncExecRuntimeImpl.java
deleted file mode 100644
index 88d6bb3..0000000
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncExecRuntimeImpl.java
+++ /dev/null
@@ -1,276 +0,0 @@
-/*
- * ====================================================================
- * 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.async;
-
-import java.io.IOException;
-import java.io.InterruptedIOException;
-import java.util.concurrent.atomic.AtomicReference;
-
-import org.apache.hc.client5.http.HttpRoute;
-import org.apache.hc.client5.http.async.AsyncExecRuntime;
-import org.apache.hc.client5.http.config.RequestConfig;
-import org.apache.hc.client5.http.impl.ConnPoolSupport;
-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.FutureCallback;
-import org.apache.hc.core5.http.nio.AsyncClientExchangeHandler;
-import org.apache.hc.core5.http2.HttpVersionPolicy;
-import org.apache.hc.core5.reactor.ConnectionInitiator;
-import org.apache.hc.core5.util.TimeValue;
-import org.apache.logging.log4j.Logger;
-
-class AsyncExecRuntimeImpl implements AsyncExecRuntime {
-
-    private final Logger log;
-    private final AsyncClientConnectionManager manager;
-    private final ConnectionInitiator connectionInitiator;
-    private final HttpVersionPolicy versionPolicy;
-    private final AtomicReference<AsyncConnectionEndpoint> endpointRef;
-    private volatile boolean reusable;
-    private volatile Object state;
-    private volatile TimeValue validDuration;
-
-    AsyncExecRuntimeImpl(
-            final Logger log,
-            final AsyncClientConnectionManager manager,
-            final ConnectionInitiator connectionInitiator,
-            final HttpVersionPolicy versionPolicy) {
-        super();
-        this.log = log;
-        this.manager = manager;
-        this.connectionInitiator = connectionInitiator;
-        this.versionPolicy = versionPolicy;
-        this.endpointRef = new AtomicReference<>(null);
-        this.validDuration = TimeValue.NEG_ONE_MILLISECONDS;
-    }
-
-    @Override
-    public boolean isConnectionAcquired() {
-        return endpointRef.get() != null;
-    }
-
-    @Override
-    public void acquireConnection(
-            final HttpRoute route,
-            final Object object,
-            final HttpClientContext context,
-            final FutureCallback<AsyncExecRuntime> callback) {
-        if (endpointRef.get() == null) {
-            state = object;
-            final RequestConfig requestConfig = context.getRequestConfig();
-            manager.lease(route, object, requestConfig.getConnectionRequestTimeout(), new FutureCallback<AsyncConnectionEndpoint>() {
-
-                @Override
-                public void completed(final AsyncConnectionEndpoint connectionEndpoint) {
-                    endpointRef.set(connectionEndpoint);
-                    reusable = connectionEndpoint.isConnected();
-                    callback.completed(AsyncExecRuntimeImpl.this);
-                }
-
-                @Override
-                public void failed(final Exception ex) {
-                    callback.failed(ex);
-                }
-
-                @Override
-                public void cancelled() {
-                    callback.cancelled();
-                }
-            });
-        } else {
-            callback.completed(this);
-        }
-    }
-
-    private void discardEndpoint(final AsyncConnectionEndpoint endpoint) {
-        try {
-            endpoint.shutdown();
-            if (log.isDebugEnabled()) {
-                log.debug(ConnPoolSupport.getId(endpoint) + ": discarding endpoint");
-            }
-        } catch (final IOException ex) {
-            if (log.isDebugEnabled()) {
-                log.debug(ConnPoolSupport.getId(endpoint) + ": " + ex.getMessage(), ex);
-            }
-        } finally {
-            manager.release(endpoint, null, TimeValue.ZERO_MILLISECONDS);
-        }
-    }
-
-    @Override
-    public void releaseConnection() {
-        final AsyncConnectionEndpoint endpoint = endpointRef.getAndSet(null);
-        if (endpoint != null) {
-            if (reusable) {
-                if (log.isDebugEnabled()) {
-                    log.debug(ConnPoolSupport.getId(endpoint) + ": releasing valid endpoint");
-                }
-                manager.release(endpoint, state, validDuration);
-            } else {
-                discardEndpoint(endpoint);
-            }
-        }
-    }
-
-    @Override
-    public void discardConnection() {
-        final AsyncConnectionEndpoint endpoint = endpointRef.getAndSet(null);
-        if (endpoint != null) {
-            discardEndpoint(endpoint);
-        }
-    }
-
-    @Override
-    public boolean validateConnection() {
-        if (reusable) {
-            final AsyncConnectionEndpoint endpoint = endpointRef.get();
-            return endpoint != null && endpoint.isConnected();
-        } else {
-            final AsyncConnectionEndpoint endpoint = endpointRef.getAndSet(null);
-            if (endpoint != null) {
-                discardEndpoint(endpoint);
-            }
-        }
-        return false;
-    }
-
-    AsyncConnectionEndpoint ensureValid() {
-        final AsyncConnectionEndpoint endpoint = endpointRef.get();
-        if (endpoint == null) {
-            throw new IllegalStateException("Endpoint not acquired / already released");
-        }
-        return endpoint;
-    }
-
-    @Override
-    public boolean isConnected() {
-        final AsyncConnectionEndpoint endpoint = endpointRef.get();
-        return endpoint != null && endpoint.isConnected();
-    }
-
-    @Override
-    public void connect(
-            final HttpClientContext context,
-            final FutureCallback<AsyncExecRuntime> callback) {
-        final AsyncConnectionEndpoint endpoint = ensureValid();
-        if (endpoint.isConnected()) {
-            callback.completed(this);
-        } else {
-            final RequestConfig requestConfig = context.getRequestConfig();
-            manager.connect(
-                    endpoint,
-                    connectionInitiator,
-                    requestConfig.getConnectTimeout(),
-                    versionPolicy,
-                    context,
-                    new FutureCallback<AsyncConnectionEndpoint>() {
-
-                        @Override
-                        public void completed(final AsyncConnectionEndpoint endpoint) {
-                            final TimeValue socketTimeout = requestConfig.getSocketTimeout();
-                            if (TimeValue.isPositive(socketTimeout)) {
-                                endpoint.setSocketTimeout(socketTimeout.toMillisIntBound());
-                            }
-                            callback.completed(AsyncExecRuntimeImpl.this);
-                        }
-
-                        @Override
-                        public void failed(final Exception ex) {
-                            callback.failed(ex);
-                        }
-
-                        @Override
-                        public void cancelled() {
-                            callback.cancelled();
-                        }
-
-            });
-        }
-
-    }
-
-    @Override
-    public void upgradeTls(final HttpClientContext context) {
-        final AsyncConnectionEndpoint endpoint = ensureValid();
-        manager.upgrade(endpoint, versionPolicy, context);
-    }
-
-    @Override
-    public void execute(final AsyncClientExchangeHandler exchangeHandler, final HttpClientContext context) {
-        final AsyncConnectionEndpoint endpoint = ensureValid();
-        if (endpoint.isConnected()) {
-            if (log.isDebugEnabled()) {
-                log.debug(ConnPoolSupport.getId(endpoint) + ": executing " + ConnPoolSupport.getId(exchangeHandler));
-            }
-            endpoint.execute(exchangeHandler, context);
-        } else {
-            connect(context, new FutureCallback<AsyncExecRuntime>() {
-
-                @Override
-                public void completed(final AsyncExecRuntime runtime) {
-                    if (log.isDebugEnabled()) {
-                        log.debug(ConnPoolSupport.getId(endpoint) + ": executing " + ConnPoolSupport.getId(exchangeHandler));
-                    }
-                    try {
-                        endpoint.execute(exchangeHandler, context);
-                    } catch (final RuntimeException ex) {
-                        failed(ex);
-                    }
-                }
-
-                @Override
-                public void failed(final Exception ex) {
-                    exchangeHandler.failed(ex);
-                }
-
-                @Override
-                public void cancelled() {
-                    exchangeHandler.failed(new InterruptedIOException());
-                }
-
-            });
-        }
-
-    }
-
-    @Override
-    public void markConnectionReusable(final Object newState, final TimeValue newValidDuration) {
-        reusable = true;
-        state = newState;
-        validDuration = newValidDuration;
-    }
-
-    @Override
-    public void markConnectionNonReusable() {
-        reusable = false;
-        state = null;
-        validDuration = null;
-    }
-
-}