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/05/01 12:39:18 UTC

svn commit: r1793320 [1/5] - in /httpcomponents/httpclient/trunk: httpclient5-testing/src/main/java/org/apache/hc/client5/testing/async/ httpclient5-testing/src/main/java/org/apache/hc/client5/testing/classic/ httpclient5-testing/src/test/java/org/apac...

Author: olegk
Date: Mon May  1 12:39:16 2017
New Revision: 1793320

URL: http://svn.apache.org/viewvc?rev=1793320&view=rev
Log:
Initial implementation of the async request execution chain

Added:
    httpcomponents/httpclient/trunk/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/async/
    httpcomponents/httpclient/trunk/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/async/AbstractSimpleServerExchangeHandler.java   (with props)
    httpcomponents/httpclient/trunk/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/async/AsyncEchoHandler.java   (with props)
    httpcomponents/httpclient/trunk/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/async/AsyncRandomHandler.java   (with props)
    httpcomponents/httpclient/trunk/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/SSLTestContexts.java   (contents, props changed)
      - copied, changed from r1793319, httpcomponents/httpclient/trunk/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/sync/SSLTestContexts.java
    httpcomponents/httpclient/trunk/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/
    httpcomponents/httpclient/trunk/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/IntegrationTestBase.java   (with props)
    httpcomponents/httpclient/trunk/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/LocalAsyncServerTestBase.java   (with props)
    httpcomponents/httpclient/trunk/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestAsyncRedirects.java   (with props)
    httpcomponents/httpclient/trunk/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestAsyncStatefulConnManagement.java   (with props)
    httpcomponents/httpclient/trunk/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttpAsync.java   (with props)
    httpcomponents/httpclient/trunk/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttpAsyncMinimal.java   (with props)
    httpcomponents/httpclient/trunk/httpclient5/src/examples/org/apache/hc/client5/http/examples/AsyncClientCustomSSL.java   (with props)
    httpcomponents/httpclient/trunk/httpclient5/src/examples/org/apache/hc/client5/http/examples/AsyncClientInterceptors.java   (with props)
    httpcomponents/httpclient/trunk/httpclient5/src/examples/org/apache/hc/client5/http/examples/AsyncClientMessageTrailers.java
      - copied, changed from r1793319, httpcomponents/httpclient/trunk/httpclient5/src/examples/org/apache/hc/client5/http/examples/AsyncClientConnectionEviction.java
    httpcomponents/httpclient/trunk/httpclient5/src/examples/org/apache/hc/client5/http/examples/AsyncQuickStart.java   (with props)
    httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/async/AsyncExecCallback.java
      - copied, changed from r1793319, httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/async/methods/SimpleRequestProducer.java
    httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/async/AsyncExecChain.java   (with props)
    httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/async/AsyncExecChainHandler.java
      - copied, changed from r1793319, httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/async/methods/SimpleRequestProducer.java
    httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/async/AsyncExecRuntime.java   (with props)
    httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncExecChainElement.java   (with props)
    httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncExecRuntimeImpl.java   (with props)
    httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncMainClientExec.java   (with props)
    httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncProtocolExec.java   (with props)
    httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncRedirectExec.java   (with props)
    httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AsyncRetryExec.java   (with props)
    httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/BasicAsyncEntityProducer.java   (with props)
    httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/LoggingAsyncClientExchangeHandler.java   (with props)
    httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/NoopHttpProcessor.java
      - copied, changed from r1793319, httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/async/methods/SimpleResponseConsumer.java
Removed:
    httpcomponents/httpclient/trunk/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/sync/SSLTestContexts.java
    httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/async/methods/AbstractAsyncResponseConsumer.java
    httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/impl/logging/LoggingIOEventHandler.java
Modified:
    httpcomponents/httpclient/trunk/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/classic/EchoHandler.java
    httpcomponents/httpclient/trunk/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/classic/RandomHandler.java
    httpcomponents/httpclient/trunk/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/external/HttpAsyncClientCompatibilityTest.java
    httpcomponents/httpclient/trunk/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/sync/LocalServerTestBase.java
    httpcomponents/httpclient/trunk/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/sync/TestRedirects.java
    httpcomponents/httpclient/trunk/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/sync/TestSSLSocketFactory.java
    httpcomponents/httpclient/trunk/httpclient5/src/examples/org/apache/hc/client5/http/examples/AsyncClientConnectionEviction.java
    httpcomponents/httpclient/trunk/httpclient5/src/examples/org/apache/hc/client5/http/examples/AsyncClientHttp1Pipelining.java
    httpcomponents/httpclient/trunk/httpclient5/src/examples/org/apache/hc/client5/http/examples/AsyncClientHttp2Multiplexing.java
    httpcomponents/httpclient/trunk/httpclient5/src/examples/org/apache/hc/client5/http/examples/AsyncClientHttpExchange.java
    httpcomponents/httpclient/trunk/httpclient5/src/examples/org/apache/hc/client5/http/examples/ClientCustomSSL.java
    httpcomponents/httpclient/trunk/httpclient5/src/examples/org/apache/hc/client5/http/examples/ClientInterceptors.java
    httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/async/HttpAsyncClient.java
    httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/async/methods/DefaultAsyncRequestProducer.java
    httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/async/methods/SimpleHttpRequest.java
    httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/async/methods/SimpleHttpResponse.java
    httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/async/methods/SimpleRequestProducer.java
    httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/async/methods/SimpleResponseConsumer.java
    httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/impl/ExecSupport.java
    httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/AbstractHttpAsyncClientBase.java
    httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/CloseableHttpAsyncClient.java
    httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClientBuilder.java
    httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClientEventHandlerFactory.java
    httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/HttpAsyncClients.java
    httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalHttpAsyncClient.java
    httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/MinimalHttpAsyncClient.java
    httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/impl/nio/AsyncClientConnectionOperator.java
    httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/impl/nio/ManagedAsyncClientConnection.java
    httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/impl/nio/PoolingAsyncClientConnectionManager.java
    httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/impl/sync/ExecRuntimeImpl.java
    httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/nio/AsyncClientConnectionManager.java
    httpcomponents/httpclient/trunk/httpclient5/src/main/java/org/apache/hc/client5/http/ssl/H2TlsStrategy.java

Added: httpcomponents/httpclient/trunk/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/async/AbstractSimpleServerExchangeHandler.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/async/AbstractSimpleServerExchangeHandler.java?rev=1793320&view=auto
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/async/AbstractSimpleServerExchangeHandler.java (added)
+++ httpcomponents/httpclient/trunk/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/async/AbstractSimpleServerExchangeHandler.java Mon May  1 12:39:16 2017
@@ -0,0 +1,79 @@
+/*
+ * ====================================================================
+ * 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.io.IOException;
+
+import org.apache.hc.client5.http.async.methods.SimpleHttpRequest;
+import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
+import org.apache.hc.core5.http.ContentType;
+import org.apache.hc.core5.http.HttpException;
+import org.apache.hc.core5.http.HttpRequest;
+import org.apache.hc.core5.http.nio.AsyncRequestConsumer;
+import org.apache.hc.core5.http.nio.entity.StringAsyncEntityConsumer;
+import org.apache.hc.core5.http.nio.entity.StringAsyncEntityProducer;
+import org.apache.hc.core5.http.nio.support.AbstractAsyncRequesterConsumer;
+import org.apache.hc.core5.http.nio.support.AbstractServerExchangeHandler;
+import org.apache.hc.core5.http.nio.support.BasicAsyncResponseProducer;
+import org.apache.hc.core5.http.nio.support.ResponseTrigger;
+import org.apache.hc.core5.http.protocol.HttpContext;
+import org.apache.hc.core5.http.protocol.HttpCoreContext;
+
+public abstract class AbstractSimpleServerExchangeHandler extends AbstractServerExchangeHandler<SimpleHttpRequest> {
+
+    protected abstract SimpleHttpResponse handle(SimpleHttpRequest request, HttpCoreContext context) throws HttpException;
+
+    @Override
+    protected final AsyncRequestConsumer<SimpleHttpRequest> supplyConsumer(
+            final HttpRequest request,
+            final HttpContext context) throws HttpException {
+        return new AbstractAsyncRequesterConsumer<SimpleHttpRequest, String>(new StringAsyncEntityConsumer()) {
+
+            @Override
+            protected SimpleHttpRequest buildResult(
+                    final HttpRequest request,
+                    final String entity,
+                    final ContentType contentType) {
+                return new SimpleHttpRequest(request, entity, contentType);
+            }
+
+        };
+    }
+
+    @Override
+    protected final void handle(
+            final SimpleHttpRequest request,
+            final ResponseTrigger responseTrigger,
+            final HttpContext context) throws HttpException, IOException {
+        final SimpleHttpResponse response = handle(request, HttpCoreContext.adapt(context));
+        responseTrigger.submitResponse(new BasicAsyncResponseProducer(
+                response,
+                response.getBody() != null ? new StringAsyncEntityProducer(response.getBody(), response.getContentType()) : null));
+
+    }
+
+}

Propchange: httpcomponents/httpclient/trunk/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/async/AbstractSimpleServerExchangeHandler.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: httpcomponents/httpclient/trunk/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/async/AbstractSimpleServerExchangeHandler.java
------------------------------------------------------------------------------
    svn:keywords = Date Revision

Propchange: httpcomponents/httpclient/trunk/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/async/AbstractSimpleServerExchangeHandler.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: httpcomponents/httpclient/trunk/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/async/AsyncEchoHandler.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/async/AsyncEchoHandler.java?rev=1793320&view=auto
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/async/AsyncEchoHandler.java (added)
+++ httpcomponents/httpclient/trunk/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/async/AsyncEchoHandler.java Mon May  1 12:39:16 2017
@@ -0,0 +1,154 @@
+/*
+ * ====================================================================
+ * 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.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.hc.core5.concurrent.FutureCallback;
+import org.apache.hc.core5.http.ContentType;
+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.HttpStatus;
+import org.apache.hc.core5.http.MethodNotSupportedException;
+import org.apache.hc.core5.http.message.BasicHttpResponse;
+import org.apache.hc.core5.http.nio.AsyncEntityProducer;
+import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler;
+import org.apache.hc.core5.http.nio.CapacityChannel;
+import org.apache.hc.core5.http.nio.DataStreamChannel;
+import org.apache.hc.core5.http.nio.ResponseChannel;
+import org.apache.hc.core5.http.nio.entity.BasicAsyncEntityConsumer;
+import org.apache.hc.core5.http.nio.entity.BasicAsyncEntityProducer;
+import org.apache.hc.core5.util.Asserts;
+
+/**
+ * A handler that echos the incoming request entity.
+ */
+public class AsyncEchoHandler implements AsyncServerExchangeHandler {
+
+    private final BasicAsyncEntityConsumer entityConsumer;
+    private final AtomicReference<AsyncEntityProducer> entityProducerRef;
+
+    public AsyncEchoHandler() {
+        this.entityConsumer = new BasicAsyncEntityConsumer();
+        this.entityProducerRef = new AtomicReference<>(null);
+    }
+
+    @Override
+    public void releaseResources() {
+        entityConsumer.releaseResources();
+        final AsyncEntityProducer producer = entityProducerRef.getAndSet(null);
+        if (producer != null) {
+            producer.releaseResources();
+        }
+    }
+
+    @Override
+    public void handleRequest(
+            final HttpRequest request,
+            final EntityDetails entityDetails,
+            final ResponseChannel responseChannel) throws HttpException, IOException {
+        final String method = request.getMethod();
+        if (!"GET".equalsIgnoreCase(method) &&
+                !"HEAD".equalsIgnoreCase(method) &&
+                !"POST".equalsIgnoreCase(method) &&
+                !"PUT".equalsIgnoreCase(method)) {
+            throw new MethodNotSupportedException(method + " not supported by " + getClass().getName());
+        }
+        if (entityDetails != null) {
+
+            final ContentType contentType = ContentType.parseLenient(entityDetails.getContentType());
+            entityConsumer.streamStart(entityDetails, new FutureCallback<byte[]>() {
+
+                @Override
+                public void completed(final byte[] content) {
+                    final BasicAsyncEntityProducer entityProducer = new BasicAsyncEntityProducer(content, contentType);
+                    entityProducerRef.set(entityProducer);
+                    try {
+                        responseChannel.sendResponse(new BasicHttpResponse(HttpStatus.SC_OK), entityProducer);
+                    } catch (final IOException | HttpException ex) {
+                        failed(ex);
+                    }
+                }
+
+                @Override
+                public void failed(final Exception ex) {
+                    releaseResources();
+                }
+
+                @Override
+                public void cancelled() {
+                    releaseResources();
+                }
+
+            });
+        } else {
+            responseChannel.sendResponse(new BasicHttpResponse(HttpStatus.SC_OK), null);
+            entityConsumer.releaseResources();
+        }
+    }
+
+    @Override
+    public void updateCapacity(final CapacityChannel capacityChannel) throws IOException {
+        entityConsumer.updateCapacity(capacityChannel);
+    }
+
+    @Override
+    public int consume(final ByteBuffer src) throws IOException {
+        return entityConsumer.consume(src);
+    }
+
+    @Override
+    public void streamEnd(final List<? extends Header> trailers) throws HttpException, IOException {
+        entityConsumer.streamEnd(trailers);
+    }
+
+    @Override
+    public int available() {
+        final AsyncEntityProducer producer = entityProducerRef.get();
+        Asserts.notNull(producer, "Entity producer");
+        return producer.available();
+    }
+
+    @Override
+    public void produce(final DataStreamChannel channel) throws IOException {
+        final AsyncEntityProducer producer = entityProducerRef.get();
+        Asserts.notNull(producer, "Entity producer");
+        producer.produce(channel);
+    }
+
+    @Override
+    public void failed(final Exception cause) {
+        releaseResources();
+    }
+
+}

Propchange: httpcomponents/httpclient/trunk/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/async/AsyncEchoHandler.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: httpcomponents/httpclient/trunk/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/async/AsyncEchoHandler.java
------------------------------------------------------------------------------
    svn:keywords = Date Revision

Propchange: httpcomponents/httpclient/trunk/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/async/AsyncEchoHandler.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: httpcomponents/httpclient/trunk/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/async/AsyncRandomHandler.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/async/AsyncRandomHandler.java?rev=1793320&view=auto
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/async/AsyncRandomHandler.java (added)
+++ httpcomponents/httpclient/trunk/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/async/AsyncRandomHandler.java Mon May  1 12:39:16 2017
@@ -0,0 +1,210 @@
+/*
+ * ====================================================================
+ * 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.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.hc.core5.http.ContentType;
+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.MethodNotSupportedException;
+import org.apache.hc.core5.http.ProtocolException;
+import org.apache.hc.core5.http.message.BasicHttpResponse;
+import org.apache.hc.core5.http.nio.AsyncEntityProducer;
+import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler;
+import org.apache.hc.core5.http.nio.CapacityChannel;
+import org.apache.hc.core5.http.nio.DataStreamChannel;
+import org.apache.hc.core5.http.nio.ResponseChannel;
+import org.apache.hc.core5.http.nio.StreamChannel;
+import org.apache.hc.core5.http.nio.entity.AbstractBinAsyncEntityProducer;
+import org.apache.hc.core5.util.Asserts;
+
+/**
+ * A handler that generates random data.
+ */
+public class AsyncRandomHandler implements AsyncServerExchangeHandler {
+
+    private final AtomicReference<AsyncEntityProducer> entityProducerRef;
+
+    public AsyncRandomHandler() {
+        this.entityProducerRef = new AtomicReference<>(null);
+    }
+
+    @Override
+    public void releaseResources() {
+        final AsyncEntityProducer producer = entityProducerRef.getAndSet(null);
+        if (producer != null) {
+            producer.releaseResources();
+        }
+    }
+
+    @Override
+    public void handleRequest(
+            final HttpRequest request,
+            final EntityDetails entityDetails,
+            final ResponseChannel responseChannel) throws HttpException, IOException {
+        final String method = request.getMethod();
+        if (!"GET".equalsIgnoreCase(method) &&
+                !"HEAD".equalsIgnoreCase(method) &&
+                !"POST".equalsIgnoreCase(method) &&
+                !"PUT".equalsIgnoreCase(method)) {
+            throw new MethodNotSupportedException(method + " not supported by " + getClass().getName());
+        }
+        final URI uri;
+        try {
+            uri = request.getUri();
+        } catch (URISyntaxException ex) {
+            throw new ProtocolException(ex.getMessage(), ex);
+        }
+        final String path = uri.getPath();
+        final int slash = path.lastIndexOf('/');
+        if (slash != -1) {
+            final String payload = path.substring(slash + 1, path.length());
+            final long n;
+            if (!payload.isEmpty()) {
+                try {
+                    n = Long.parseLong(payload);
+                } catch (final NumberFormatException ex) {
+                    throw new ProtocolException("Invalid request path: " + path);
+                }
+            } else {
+                // random length, but make sure at least something is sent
+                n = 1 + (int)(Math.random() * 79.0);
+            }
+            final HttpResponse response = new BasicHttpResponse(HttpStatus.SC_OK);
+            final AsyncEntityProducer entityProducer = new RandomBinAsyncEntityProducer(n);
+            entityProducerRef.set(entityProducer);
+            responseChannel.sendResponse(response, entityProducer);
+        } else {
+            throw new ProtocolException("Invalid request path: " + path);
+        }
+    }
+
+    @Override
+    public void updateCapacity(final CapacityChannel capacityChannel) throws IOException {
+        capacityChannel.update(Integer.MAX_VALUE);
+    }
+
+    @Override
+    public int consume(final ByteBuffer src) throws IOException {
+        return Integer.MAX_VALUE;
+    }
+
+    @Override
+    public void streamEnd(final List<? extends Header> trailers) throws HttpException, IOException {
+    }
+
+    @Override
+    public int available() {
+        final AsyncEntityProducer producer = entityProducerRef.get();
+        Asserts.notNull(producer, "Entity producer");
+        return producer.available();
+    }
+
+    @Override
+    public void produce(final DataStreamChannel channel) throws IOException {
+        final AsyncEntityProducer producer = entityProducerRef.get();
+        Asserts.notNull(producer, "Entity producer");
+        producer.produce(channel);
+    }
+
+    @Override
+    public void failed(final Exception cause) {
+        releaseResources();
+    }
+
+    /**
+     * An entity that generates random data.
+     */
+    public static class RandomBinAsyncEntityProducer extends AbstractBinAsyncEntityProducer {
+
+        /** The range from which to generate random data. */
+        private final static byte[] RANGE = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
+                .getBytes(StandardCharsets.US_ASCII);
+
+        /** The length of the random data to generate. */
+        private final long length;
+        private long remaining;
+        private final ByteBuffer buffer;
+
+        public RandomBinAsyncEntityProducer(final long len) {
+            super(2048, 512, ContentType.DEFAULT_TEXT);
+            length = len;
+            remaining = len;
+            buffer = ByteBuffer.allocate(1024);
+        }
+
+        @Override
+        public void releaseResources() {
+        }
+
+        @Override
+        public long getContentLength() {
+            return length;
+        }
+
+        @Override
+        public int available() {
+            return Integer.MAX_VALUE;
+        }
+
+        @Override
+        protected void produceData(final StreamChannel<ByteBuffer> channel) throws IOException {
+            final int chunk = Math.min((int) (remaining < Integer.MAX_VALUE ? remaining : Integer.MAX_VALUE), buffer.remaining());
+            for (int i = 0; i < chunk; i++) {
+                final byte b = RANGE[(int) (Math.random() * RANGE.length)];
+                buffer.put(b);
+            }
+            buffer.flip();
+            final int bytesWritten = channel.write(buffer);
+            if (bytesWritten > 0) {
+                remaining -= bytesWritten;
+            }
+            buffer.compact();
+            if (remaining <= 0) {
+                channel.endStream();
+            }
+        }
+
+        @Override
+        public void failed(final  Exception cause) {
+        }
+
+    }
+
+}

Propchange: httpcomponents/httpclient/trunk/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/async/AsyncRandomHandler.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: httpcomponents/httpclient/trunk/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/async/AsyncRandomHandler.java
------------------------------------------------------------------------------
    svn:keywords = Date Revision

Propchange: httpcomponents/httpclient/trunk/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/async/AsyncRandomHandler.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: httpcomponents/httpclient/trunk/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/classic/EchoHandler.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/classic/EchoHandler.java?rev=1793320&r1=1793319&r2=1793320&view=diff
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/classic/EchoHandler.java (original)
+++ httpcomponents/httpclient/trunk/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/classic/EchoHandler.java Mon May  1 12:39:16 2017
@@ -65,11 +65,10 @@ public class EchoHandler implements Http
 
         final String method = request.getMethod().toUpperCase(Locale.ROOT);
         if (!"GET".equals(method) &&
-            !"POST".equals(method) &&
-            !"PUT".equals(method)
-            ) {
-            throw new MethodNotSupportedException
-                (method + " not supported by " + getClass().getName());
+                !"HEAD".equals(method) &&
+                !"POST".equals(method) &&
+                !"PUT".equals(method)) {
+            throw new MethodNotSupportedException(method + " not supported by " + getClass().getName());
         }
 
         HttpEntity entity = request.getEntity();

Modified: httpcomponents/httpclient/trunk/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/classic/RandomHandler.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/classic/RandomHandler.java?rev=1793320&r1=1793319&r2=1793320&view=diff
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/classic/RandomHandler.java (original)
+++ httpcomponents/httpclient/trunk/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/classic/RandomHandler.java Mon May  1 12:39:16 2017
@@ -30,14 +30,16 @@ package org.apache.hc.client5.testing.cl
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
 import java.nio.charset.StandardCharsets;
-import java.util.Locale;
 
 import org.apache.hc.core5.http.ClassicHttpRequest;
 import org.apache.hc.core5.http.ClassicHttpResponse;
 import org.apache.hc.core5.http.HttpException;
 import org.apache.hc.core5.http.HttpStatus;
 import org.apache.hc.core5.http.MethodNotSupportedException;
+import org.apache.hc.core5.http.ProtocolException;
 import org.apache.hc.core5.http.io.HttpRequestHandler;
 import org.apache.hc.core5.http.io.entity.AbstractHttpEntity;
 import org.apache.hc.core5.http.protocol.HttpContext;
@@ -68,51 +70,40 @@ public class RandomHandler implements Ht
                        final HttpContext context)
         throws HttpException, IOException {
 
-        final String method = request.getMethod().toUpperCase(Locale.ROOT);
-        if (!"GET".equals(method) && !"HEAD".equals(method)) {
-            throw new MethodNotSupportedException
-                (method + " not supported by " + getClass().getName());
+        final String method = request.getMethod();
+        if (!"GET".equalsIgnoreCase(method) &&
+                !"HEAD".equalsIgnoreCase(method) &&
+                !"POST".equalsIgnoreCase(method) &&
+                !"PUT".equalsIgnoreCase(method)) {
+            throw new MethodNotSupportedException(method + " not supported by " + getClass().getName());
         }
-
-        final String uri = request.getRequestUri();
-        final int  slash = uri.lastIndexOf('/');
-        int length = -1;
-        if (slash < uri.length()-1) {
-            try {
-                // no more than Integer, 2 GB ought to be enough for anybody
-                length = Integer.parseInt(uri.substring(slash+1));
-
-                if (length < 0) {
-                    response.setCode(HttpStatus.SC_BAD_REQUEST);
-                    response.setReasonPhrase("LENGTH " + length);
-                }
-            } catch (final NumberFormatException nfx) {
-                response.setCode(HttpStatus.SC_BAD_REQUEST);
-                response.setReasonPhrase(nfx.toString());
-            }
-        } else {
-            // random length, but make sure at least something is sent
-            length = 1 + (int)(Math.random() * 79.0);
+        final URI uri;
+        try {
+            uri = request.getUri();
+        } catch (URISyntaxException ex) {
+            throw new ProtocolException(ex.getMessage(), ex);
         }
-
-        if (length >= 0) {
-
-            response.setCode(HttpStatus.SC_OK);
-
-            if (!"HEAD".equals(method)) {
-                final RandomEntity entity = new RandomEntity(length);
-                entity.setContentType("text/plain; charset=US-ASCII");
-                response.setEntity(entity);
+        final String path = uri.getPath();
+        final int slash = path.lastIndexOf('/');
+        if (slash != -1) {
+            final String payload = path.substring(slash + 1, path.length());
+            final long n;
+            if (!payload.isEmpty()) {
+                try {
+                    n = Long.parseLong(payload);
+                } catch (final NumberFormatException ex) {
+                    throw new ProtocolException("Invalid request path: " + path);
+                }
             } else {
-                response.setHeader("Content-Type",
-                                   "text/plain; charset=US-ASCII");
-                response.setHeader("Content-Length",
-                                   String.valueOf(length));
+                // random length, but make sure at least something is sent
+                n = 1 + (int)(Math.random() * 79.0);
             }
+            response.setCode(HttpStatus.SC_OK);
+            response.setEntity(new RandomEntity(n));
+        } else {
+            throw new ProtocolException("Invalid request path: " + path);
         }
-
-    } // handle
-
+    }
 
     /**
      * An entity that generates random data.

Copied: httpcomponents/httpclient/trunk/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/SSLTestContexts.java (from r1793319, httpcomponents/httpclient/trunk/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/sync/SSLTestContexts.java)
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/SSLTestContexts.java?p2=httpcomponents/httpclient/trunk/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/SSLTestContexts.java&p1=httpcomponents/httpclient/trunk/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/sync/SSLTestContexts.java&r1=1793319&r2=1793320&rev=1793320&view=diff
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/sync/SSLTestContexts.java (original)
+++ httpcomponents/httpclient/trunk/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/SSLTestContexts.java Mon May  1 12:39:16 2017
@@ -25,7 +25,7 @@
  *
  */
 
-package org.apache.hc.client5.testing.sync;
+package org.apache.hc.client5.testing;
 
 import javax.net.ssl.SSLContext;
 

Propchange: httpcomponents/httpclient/trunk/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/SSLTestContexts.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: httpcomponents/httpclient/trunk/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/SSLTestContexts.java
------------------------------------------------------------------------------
    svn:keywords = Date Revision

Propchange: httpcomponents/httpclient/trunk/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/SSLTestContexts.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: httpcomponents/httpclient/trunk/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/IntegrationTestBase.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/IntegrationTestBase.java?rev=1793320&view=auto
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/IntegrationTestBase.java (added)
+++ httpcomponents/httpclient/trunk/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/IntegrationTestBase.java Mon May  1 12:39:16 2017
@@ -0,0 +1,82 @@
+/*
+ * ====================================================================
+ * 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 org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
+import org.apache.hc.client5.http.impl.async.HttpAsyncClientBuilder;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.URIScheme;
+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().setConnectionManager(connManager);
+        }
+
+        @Override
+        protected void after() {
+            if (httpclient != null) {
+                httpclient.shutdown(ShutdownType.GRACEFUL);
+                httpclient = null;
+            }
+        }
+
+    };
+
+    public HttpHost start() throws Exception {
+        server.start();
+        final ListenerEndpoint listener = server.listen(new InetSocketAddress(0));
+        httpclient = clientBuilder.build();
+        httpclient.start();
+        listener.waitFor();
+        final InetSocketAddress address = (InetSocketAddress) listener.getAddress();
+        return new HttpHost("localhost", address.getPort(), scheme.name());
+    }
+
+}

Propchange: httpcomponents/httpclient/trunk/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/IntegrationTestBase.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: httpcomponents/httpclient/trunk/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/IntegrationTestBase.java
------------------------------------------------------------------------------
    svn:keywords = Date Revision

Propchange: httpcomponents/httpclient/trunk/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/IntegrationTestBase.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: httpcomponents/httpclient/trunk/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/LocalAsyncServerTestBase.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/LocalAsyncServerTestBase.java?rev=1793320&view=auto
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/LocalAsyncServerTestBase.java (added)
+++ httpcomponents/httpclient/trunk/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/LocalAsyncServerTestBase.java Mon May  1 12:39:16 2017
@@ -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 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.junit.Rule;
+import org.junit.rules.ExternalResource;
+
+public abstract class LocalAsyncServerTestBase {
+
+    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.DEFAULT,
+                    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;
+            }
+        }
+
+    };
+
+}

Propchange: httpcomponents/httpclient/trunk/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/LocalAsyncServerTestBase.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: httpcomponents/httpclient/trunk/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/LocalAsyncServerTestBase.java
------------------------------------------------------------------------------
    svn:keywords = Date Revision

Propchange: httpcomponents/httpclient/trunk/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/LocalAsyncServerTestBase.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: httpcomponents/httpclient/trunk/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestAsyncRedirects.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestAsyncRedirects.java?rev=1793320&view=auto
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestAsyncRedirects.java (added)
+++ httpcomponents/httpclient/trunk/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestAsyncRedirects.java Mon May  1 12:39:16 2017
@@ -0,0 +1,904 @@
+/*
+ * ====================================================================
+ * 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.async.methods.SimpleHttpRequest;
+import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
+import org.apache.hc.client5.http.async.methods.SimpleRequestProducer;
+import org.apache.hc.client5.http.async.methods.SimpleResponseConsumer;
+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.CircularRedirectException;
+import org.apache.hc.client5.http.protocol.HttpClientContext;
+import org.apache.hc.client5.http.protocol.RedirectException;
+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.Ignore;
+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/")) {
+                    return new SimpleHttpResponse(HttpStatus.SC_OK, "Successful redirect", ContentType.TEXT_PLAIN);
+                } else {
+                    return new SimpleHttpResponse(HttpStatus.SC_NOT_FOUND, null, null);
+                }
+            } 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, null, null);
+                }
+            } 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/")) {
+                    return new SimpleHttpResponse(HttpStatus.SC_OK, "Successful redirect", ContentType.TEXT_PLAIN);
+                } 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")) {
+                    return new SimpleHttpResponse(HttpStatus.SC_OK, "Successful redirect", ContentType.TEXT_PLAIN);
+                } 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 Future<SimpleHttpResponse> future = httpclient.execute(
+                    new SimpleRequestProducer(
+                            SimpleHttpRequest.get(target, "/circular-oldlocation/"), config),
+                    new SimpleResponseConsumer(), 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 Future<SimpleHttpResponse> future = httpclient.execute(
+                    new SimpleRequestProducer(
+                            SimpleHttpRequest.get(target, "/circular-oldlocation/"), config),
+                    new SimpleResponseConsumer(), 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 Future<SimpleHttpResponse> future = httpclient.execute(
+                SimpleHttpRequest.post(target, "/oldlocation/", "stuff", ContentType.TEXT_PLAIN), 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/")) {
+                    return new SimpleHttpResponse(HttpStatus.SC_OK, "Successful redirect", ContentType.TEXT_PLAIN);
+                } 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 @Ignore
+    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 ListenerEndpoint endpoint2 = secondServer.listen(new InetSocketAddress(0));
+            endpoint2.waitFor();
+
+            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")) {
+                    return new SimpleHttpResponse(HttpStatus.SC_OK, "Successful redirect", ContentType.TEXT_PLAIN);
+                } 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

Propchange: httpcomponents/httpclient/trunk/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestAsyncRedirects.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: httpcomponents/httpclient/trunk/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestAsyncRedirects.java
------------------------------------------------------------------------------
    svn:keywords = Date Revision

Propchange: httpcomponents/httpclient/trunk/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestAsyncRedirects.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain