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 2020/03/17 09:08:34 UTC

[httpcomponents-client] branch development updated (ac53070 -> cbca2ef)

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

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


 discard ac53070  Fix NPE for null HttpContext in MinimalHttpAsyncClient
    omit b74e520  Removed unnecessary BasicFuture wrapping
    omit d89b275  Minor test code simplification in reactive test suite
     add afa8f5e  Upgraded HttpClient version to 5.0-beta8-SNAPSHOT
     add 95dbbf0  Now that ClassicHttpRequests is no longer an enum, we need to way to generically build requests from method names. Update all factory classes with matching APIs for Method and String method name inputs.
     add 3575cff  Bug fix: fixed handling of private domains by PublicSuffixMatcher
     add 4401991  HTTPCLIENT-2047: fixed regression in DefaultHostnameVerifier causing rejection of certs with non-standard domains.
     add d1c4199  No need to use the type name of an input arg in the method name. (#208)
     add 6559b60  DefaultHttpRequestRetryStrategy: Allow zero retry interval
     add 5bcf6b6  Bug fix: custom SSL context is ignored
     add d601aff  Upgraded HttpCore dependency to version 5.0
     add 934a6d5  Removed dodgy work-around for resumed TLS sessions given that JDK-8212885 fix has been ported to Java 11 and released in Oracle JDK 11.0.3
     add 85eec39  Updated Travis CI JDK matrix; replaced Oracle JDK 11 with OpenJDK 12 and Oracle JDK 12
     add 42cae69  Use finite (3 minutes) connection keep-alive period by default
     add 53e1725  HTTPCLIENT-2051: Change POST to GET for 301, 302 and 303 redirects. Other unsafe methods to be redirected as is.
     add 24b3f71  Removed deprecated methods
     add ad47e95  Updated release notes for HttpClient 5.0 release
     add 61f3a94  Upgraded HttpClient version to 5.0.1-SNAPSHOT
     add f4fb8b5  Added Clirr API compatibility check
     add 56ef8b8  Use try-with-resources in examples
     add 99f7d2b  Removed unnecessary BasicFuture wrapping
     add ffa0530  Fix NPE for null HttpContext in minimal async clients
     new cbca2ef  Rewrite of redirect integration test cases

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

 * -- * -- B -- O -- O -- O   (ac53070)
            \
             N -- N -- N   refs/heads/development (cbca2ef)

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

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

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


Summary of changes:
 .travis.yml                                        |   8 +-
 RELEASE_NOTES.txt                                  |  59 +-
 httpclient5-cache/pom.xml                          |   2 +-
 .../impl/cache/CachedHttpResponseGenerator.java    |   2 +-
 httpclient5-fluent/pom.xml                         |   2 +-
 httpclient5-testing/pom.xml                        |   2 +-
 .../async/AbstractSimpleServerExchangeHandler.java |   2 +-
 .../testing/async/RedirectingAsyncDecorator.java   | 158 +++++
 .../testing/classic/RedirectingDecorator.java      |  86 +++
 .../hc/client5/testing/redirect/Redirect.java      |  27 +-
 .../client5/testing/redirect/RedirectResolver.java |  15 +-
 .../client5/testing/OldPathRedirectResolver.java   |  68 ++
 .../AbstractHttpAsyncClientAuthentication.java     |   6 +-
 .../async/AbstractHttpAsyncRedirectsTest.java      | 623 +++++------------
 .../AbstractHttpReactiveFundamentalsTest.java      |  67 +-
 .../testing/async/TestHttp1AsyncRedirects.java     |   2 +-
 .../TestHttp1AsyncStatefulConnManagement.java      |   4 +-
 .../hc/client5/testing/sync/TestRedirects.java     | 772 ++++++++++-----------
 httpclient5-win/pom.xml                            |   2 +-
 httpclient5/pom.xml                                |   2 +-
 .../http/async/methods/BasicHttpRequests.java      |  34 +
 .../http/async/methods/SimpleHttpRequest.java      |   4 +-
 .../http/async/methods/SimpleHttpRequests.java     |  34 +
 .../http/async/methods/SimpleHttpResponse.java     |   8 +-
 .../http/async/methods/SimpleResponseConsumer.java |   2 +-
 .../http/classic/methods/ClassicHttpRequests.java  |  77 ++
 .../hc/client5/http/config/RequestConfig.java      |  44 +-
 .../impl/DefaultConnectionKeepAliveStrategy.java   |   6 +-
 .../http/impl/DefaultHttpRequestRetryStrategy.java |   2 +-
 .../client5/http/impl/async/AsyncRedirectExec.java |   2 +-
 .../http/impl/async/CloseableHttpAsyncClient.java  |   9 -
 .../hc/client5/http/impl/classic/RedirectExec.java |   2 +-
 .../hc/client5/http/psl/PublicSuffixMatcher.java   |  33 +-
 .../client5/http/ssl/DefaultHostnameVerifier.java  |   4 +-
 .../hc/client5/http/ssl/TlsSessionValidator.java   |  27 +-
 ...pRequests.java => SimpleBasicHttpRequests.java} |  24 +-
 .../http/async/methods/TestBasicHttpRequests.java  |  15 +-
 .../classic/methods/TestClassicHttpRequests.java   |  10 +
 .../hc/client5/http/examples/AsyncQuickStart.java  |   5 +-
 .../hc/client5/http/examples/ClientCustomSSL.java  |   2 +-
 .../impl/TestDefaultConnKeepAliveStrategy.java     |  22 +-
 .../impl/TestDefaultHttpRequestRetryStrategy.java  |  18 +-
 .../client5/http/psl/TestPublicSuffixMatcher.java  |  18 +-
 .../http/ssl/TestDefaultHostnameVerifier.java      |  25 +
 .../src/test/resources/suffixlistmatcher.txt       |   2 +
 pom.xml                                            |  23 +-
 46 files changed, 1311 insertions(+), 1050 deletions(-)
 create mode 100644 httpclient5-testing/src/main/java/org/apache/hc/client5/testing/async/RedirectingAsyncDecorator.java
 create mode 100644 httpclient5-testing/src/main/java/org/apache/hc/client5/testing/classic/RedirectingDecorator.java
 copy httpclient5/src/main/java/org/apache/hc/client5/http/impl/ExecSupport.java => httpclient5-testing/src/main/java/org/apache/hc/client5/testing/redirect/Redirect.java (67%)
 copy httpclient5/src/main/java/org/apache/hc/client5/http/auth/ChallengeType.java => httpclient5-testing/src/main/java/org/apache/hc/client5/testing/redirect/RedirectResolver.java (85%)
 create mode 100644 httpclient5-testing/src/test/java/org/apache/hc/client5/testing/OldPathRedirectResolver.java
 copy httpclient5/src/test/java/org/apache/hc/client5/http/async/methods/{TestBasicHttpRequests.java => SimpleBasicHttpRequests.java} (69%)


[httpcomponents-client] 01/01: Rewrite of redirect integration test cases

Posted by ol...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit cbca2efbe91e500a02c68623e726b584b1f057e9
Author: Oleg Kalnichevski <ol...@apache.org>
AuthorDate: Mon Mar 16 19:24:35 2020 +0100

    Rewrite of redirect integration test cases
---
 .../testing/async/RedirectingAsyncDecorator.java   | 158 +++++
 .../testing/classic/RedirectingDecorator.java      |  86 +++
 .../hc/client5/testing/redirect/Redirect.java      |  48 ++
 .../client5/testing/redirect/RedirectResolver.java |  37 +
 .../client5/testing/OldPathRedirectResolver.java   |  68 ++
 .../async/AbstractHttpAsyncRedirectsTest.java      | 621 +++++------------
 .../hc/client5/testing/sync/TestRedirects.java     | 772 ++++++++++-----------
 7 files changed, 938 insertions(+), 852 deletions(-)

diff --git a/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/async/RedirectingAsyncDecorator.java b/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/async/RedirectingAsyncDecorator.java
new file mode 100644
index 0000000..9fe15b5
--- /dev/null
+++ b/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/async/RedirectingAsyncDecorator.java
@@ -0,0 +1,158 @@
+/*
+ * ====================================================================
+ * 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.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.hc.client5.testing.redirect.Redirect;
+import org.apache.hc.client5.testing.redirect.RedirectResolver;
+import org.apache.hc.core5.http.EntityDetails;
+import org.apache.hc.core5.http.Header;
+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.HttpRequest;
+import org.apache.hc.core5.http.HttpResponse;
+import org.apache.hc.core5.http.ProtocolException;
+import org.apache.hc.core5.http.message.BasicHeader;
+import org.apache.hc.core5.http.message.BasicHttpResponse;
+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.protocol.HttpContext;
+import org.apache.hc.core5.util.Args;
+
+public class RedirectingAsyncDecorator implements AsyncServerExchangeHandler {
+
+    private final AsyncServerExchangeHandler exchangeHandler;
+    private final RedirectResolver redirectResolver;
+    private final AtomicBoolean redirecting;
+
+    public RedirectingAsyncDecorator(final AsyncServerExchangeHandler exchangeHandler,
+                                     final RedirectResolver redirectResolver) {
+        this.exchangeHandler = Args.notNull(exchangeHandler, "Exchange handler");
+        this.redirectResolver = redirectResolver;
+        this.redirecting = new AtomicBoolean();
+    }
+
+    private Redirect resolveRedirect(final HttpRequest request) throws HttpException {
+        try {
+            final URI requestURI = request.getUri();
+            return redirectResolver != null ? redirectResolver.resolve(requestURI) : null;
+        } catch (final URISyntaxException ex) {
+            throw new ProtocolException(ex.getMessage(), ex);
+        }
+    }
+
+    private HttpResponse createRedirectResponse(final Redirect redirect) {
+        final HttpResponse response = new BasicHttpResponse(redirect.status);
+        if (redirect.location != null) {
+            response.addHeader(new BasicHeader(HttpHeaders.LOCATION, redirect.location));
+        }
+        switch (redirect.connControl) {
+            case KEEP_ALIVE:
+                response.addHeader(new BasicHeader(HttpHeaders.CONNECTION, HeaderElements.KEEP_ALIVE));
+                break;
+            case CLOSE:
+                response.addHeader(new BasicHeader(HttpHeaders.CONNECTION, HeaderElements.CLOSE));
+        }
+        return response;
+    }
+
+    @Override
+    public void handleRequest(final HttpRequest request,
+                              final EntityDetails entityDetails,
+                              final ResponseChannel responseChannel,
+                              final HttpContext context) throws HttpException, IOException {
+        final Redirect redirect = resolveRedirect(request);
+        if (redirect != null) {
+            responseChannel.sendResponse(createRedirectResponse(redirect), null, context);
+            redirecting.set(true);
+        } else {
+            exchangeHandler.handleRequest(request, entityDetails, responseChannel, context);
+        }
+    }
+
+    @Override
+    public final void updateCapacity(final CapacityChannel capacityChannel) throws IOException {
+        if (!redirecting.get()) {
+            exchangeHandler.updateCapacity(capacityChannel);
+        } else {
+            capacityChannel.update(Integer.MAX_VALUE);
+        }
+    }
+
+    @Override
+    public final void consume(final ByteBuffer src) throws IOException {
+        if (!redirecting.get()) {
+            exchangeHandler.consume(src);
+        }
+    }
+
+    @Override
+    public final void streamEnd(final List<? extends Header> trailers) throws HttpException, IOException {
+        if (!redirecting.get()) {
+            exchangeHandler.streamEnd(trailers);
+        }
+    }
+
+    @Override
+    public int available() {
+        if (!redirecting.get()) {
+            return exchangeHandler.available();
+        } else {
+            return 0;
+        }
+    }
+
+    @Override
+    public void produce(final DataStreamChannel channel) throws IOException {
+        if (!redirecting.get()) {
+            exchangeHandler.produce(channel);
+        }
+    }
+
+    @Override
+    public void failed(final Exception cause) {
+        if (!redirecting.get()) {
+            exchangeHandler.failed(cause);
+        }
+    }
+
+    @Override
+    public void releaseResources() {
+        exchangeHandler.releaseResources();
+    }
+
+}
diff --git a/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/classic/RedirectingDecorator.java b/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/classic/RedirectingDecorator.java
new file mode 100644
index 0000000..37160a7
--- /dev/null
+++ b/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/classic/RedirectingDecorator.java
@@ -0,0 +1,86 @@
+/*
+ * ====================================================================
+ * 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.classic;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import org.apache.hc.client5.testing.redirect.Redirect;
+import org.apache.hc.client5.testing.redirect.RedirectResolver;
+import org.apache.hc.core5.http.ClassicHttpRequest;
+import org.apache.hc.core5.http.ClassicHttpResponse;
+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.ProtocolException;
+import org.apache.hc.core5.http.io.HttpServerRequestHandler;
+import org.apache.hc.core5.http.message.BasicClassicHttpResponse;
+import org.apache.hc.core5.http.message.BasicHeader;
+import org.apache.hc.core5.http.protocol.HttpContext;
+import org.apache.hc.core5.util.Args;
+
+public class RedirectingDecorator implements HttpServerRequestHandler {
+
+    private final HttpServerRequestHandler requestHandler;
+    private final RedirectResolver redirectResolver;
+
+    public RedirectingDecorator(final HttpServerRequestHandler requestHandler,
+                                final RedirectResolver redirectResolver) {
+        this.requestHandler = Args.notNull(requestHandler, "Request handler");
+        this.redirectResolver = redirectResolver;
+    }
+
+    @Override
+    public void handle(final ClassicHttpRequest request,
+                       final ResponseTrigger responseTrigger,
+                       final HttpContext context) throws HttpException, IOException {
+        try {
+            final URI requestURI = request.getUri();
+            final Redirect redirect = redirectResolver != null ? redirectResolver.resolve(requestURI) : null;
+            if (redirect != null) {
+                final ClassicHttpResponse response = new BasicClassicHttpResponse(redirect.status);
+                if (redirect.location != null) {
+                    response.addHeader(new BasicHeader(HttpHeaders.LOCATION, redirect.location));
+                }
+                switch (redirect.connControl) {
+                    case KEEP_ALIVE:
+                        response.addHeader(new BasicHeader(HttpHeaders.CONNECTION, HeaderElements.KEEP_ALIVE));
+                        break;
+                    case CLOSE:
+                        response.addHeader(new BasicHeader(HttpHeaders.CONNECTION, HeaderElements.CLOSE));
+                }
+                responseTrigger.submitResponse(response);
+            } else {
+                requestHandler.handle(request, responseTrigger, context);
+            }
+        } catch (final URISyntaxException ex) {
+            throw new ProtocolException(ex.getMessage(), ex);
+        }
+    }
+}
diff --git a/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/redirect/Redirect.java b/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/redirect/Redirect.java
new file mode 100644
index 0000000..b52fb35
--- /dev/null
+++ b/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/redirect/Redirect.java
@@ -0,0 +1,48 @@
+/*
+ * ====================================================================
+ * 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.redirect;
+
+public class Redirect {
+
+    public enum ConnControl { PROTOCOL_DEFAULT, KEEP_ALIVE, CLOSE }
+
+    public final int status;
+    public final String location;
+    public final ConnControl connControl;
+
+    public Redirect(final int status, final String location, final ConnControl connControl) {
+        this.status = status;
+        this.location = location;
+        this.connControl = connControl;
+    }
+
+    public Redirect(final int status, final String location) {
+        this(status , location, ConnControl.PROTOCOL_DEFAULT);
+    }
+
+}
diff --git a/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/redirect/RedirectResolver.java b/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/redirect/RedirectResolver.java
new file mode 100644
index 0000000..2ffa2db
--- /dev/null
+++ b/httpclient5-testing/src/main/java/org/apache/hc/client5/testing/redirect/RedirectResolver.java
@@ -0,0 +1,37 @@
+/*
+ * ====================================================================
+ * 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.redirect;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+
+public interface RedirectResolver {
+
+    Redirect resolve(URI requestUri) throws URISyntaxException;
+
+}
diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/OldPathRedirectResolver.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/OldPathRedirectResolver.java
new file mode 100644
index 0000000..84aaf09
--- /dev/null
+++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/OldPathRedirectResolver.java
@@ -0,0 +1,68 @@
+/*
+ * ====================================================================
+ * 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;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import org.apache.hc.client5.testing.redirect.Redirect;
+import org.apache.hc.client5.testing.redirect.RedirectResolver;
+import org.apache.hc.core5.net.URIBuilder;
+
+public class OldPathRedirectResolver implements RedirectResolver {
+
+    private final String oldPath;
+    private final String newPath;
+    private final int status;
+    private final Redirect.ConnControl connControl;
+
+    public OldPathRedirectResolver(
+            final String oldPath, final String newPath, final int status, final Redirect.ConnControl connControl) {
+        this.oldPath = oldPath;
+        this.newPath = newPath;
+        this.status = status;
+        this.connControl = connControl;
+    }
+
+    public OldPathRedirectResolver(final String oldPath, final String newPath, final int status) {
+        this(oldPath, newPath, status, Redirect.ConnControl.PROTOCOL_DEFAULT);
+    }
+
+    @Override
+    public Redirect resolve(final URI requestUri) throws URISyntaxException {
+        final String path = requestUri.getPath();
+        if (path.startsWith(oldPath)) {
+            final URI location = new URIBuilder(requestUri)
+                    .setPath(newPath + path.substring(oldPath.length()))
+                    .build();
+            return new Redirect(status, location.toString(), connControl);
+
+        }
+        return null;
+    }
+}
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
index e92af3a..75939e2 100644
--- 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
@@ -26,11 +26,8 @@
  */
 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;
 
@@ -45,7 +42,10 @@ 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.client5.testing.OldPathRedirectResolver;
+import org.apache.hc.client5.testing.redirect.Redirect;
+import org.apache.hc.client5.testing.redirect.RedirectResolver;
+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.Header;
@@ -58,15 +58,9 @@ import org.apache.hc.core5.http.HttpVersion;
 import org.apache.hc.core5.http.ProtocolException;
 import org.apache.hc.core5.http.URIScheme;
 import org.apache.hc.core5.http.config.Http1Config;
-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.http2.config.H2Config;
-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.H2TestServer;
-import org.apache.hc.core5.util.TimeValue;
 import org.junit.Assert;
 import org.junit.Test;
 
@@ -88,132 +82,26 @@ public abstract class AbstractHttpAsyncRedirectsTest <T extends CloseableHttpAsy
         }
     }
 
-    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.setBody("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.setBody("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.setBody("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);
-            }
+    public final HttpHost start(final Decorator<AsyncServerExchangeHandler> exchangeHandlerDecorator) throws Exception {
+        if (version.greaterEquals(HttpVersion.HTTP_2)) {
+            return super.start(null, exchangeHandlerDecorator, H2Config.DEFAULT);
+        } else {
+            return super.start(null, exchangeHandlerDecorator, Http1Config.DEFAULT);
         }
-
     }
 
     @Test
     public void testBasicRedirect300() throws Exception {
-        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
+        final HttpHost target = start(new Decorator<AsyncServerExchangeHandler>() {
 
             @Override
-            public AsyncServerExchangeHandler get() {
-                return new BasicRedirectService(HttpStatus.SC_MULTIPLE_CHOICES);
+            public AsyncServerExchangeHandler decorate(final AsyncServerExchangeHandler exchangeHandler) {
+                return new RedirectingAsyncDecorator(
+                        exchangeHandler,
+                        new OldPathRedirectResolver("/oldlocation", "/random", HttpStatus.SC_MULTIPLE_CHOICES));
             }
 
         });
-        final HttpHost target = start();
 
         final HttpClientContext context = HttpClientContext.create();
         final Future<SimpleHttpResponse> future = httpclient.execute(
@@ -229,117 +117,131 @@ public abstract class AbstractHttpAsyncRedirectsTest <T extends CloseableHttpAsy
 
     @Test
     public void testBasicRedirect301() throws Exception {
-        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
+        final HttpHost target = start(new Decorator<AsyncServerExchangeHandler>() {
 
             @Override
-            public AsyncServerExchangeHandler get() {
-                return new BasicRedirectService(HttpStatus.SC_MOVED_PERMANENTLY);
+            public AsyncServerExchangeHandler decorate(final AsyncServerExchangeHandler exchangeHandler) {
+                return new RedirectingAsyncDecorator(
+                        exchangeHandler,
+                        new OldPathRedirectResolver("/oldlocation", "/random", HttpStatus.SC_MOVED_PERMANENTLY));
             }
 
         });
-
-        final HttpHost target = start();
         final HttpClientContext context = HttpClientContext.create();
         final Future<SimpleHttpResponse> future = httpclient.execute(
-                SimpleHttpRequests.get(target, "/oldlocation/"), context, null);
+                SimpleHttpRequests.get(target, "/oldlocation/100"), 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("/random/100", request.getRequestUri());
         Assert.assertEquals(target, new HttpHost(request.getScheme(), request.getAuthority()));
     }
 
     @Test
     public void testBasicRedirect302() throws Exception {
-        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
+        final HttpHost target = start(new Decorator<AsyncServerExchangeHandler>() {
 
             @Override
-            public AsyncServerExchangeHandler get() {
-                return new BasicRedirectService(HttpStatus.SC_MOVED_TEMPORARILY);
+            public AsyncServerExchangeHandler decorate(final AsyncServerExchangeHandler exchangeHandler) {
+                return new RedirectingAsyncDecorator(
+                        exchangeHandler,
+                        new OldPathRedirectResolver("/oldlocation", "/random", HttpStatus.SC_MOVED_TEMPORARILY));
             }
 
         });
-        final HttpHost target = start();
         final HttpClientContext context = HttpClientContext.create();
         final Future<SimpleHttpResponse> future = httpclient.execute(
-                SimpleHttpRequests.get(target, "/oldlocation/"), context, null);
+                SimpleHttpRequests.get(target, "/oldlocation/123"), 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("/random/123", request.getRequestUri());
         Assert.assertEquals(target, new HttpHost(request.getScheme(), request.getAuthority()));
     }
 
     @Test
     public void testBasicRedirect302NoLocation() throws Exception {
-        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
+        final HttpHost target = start(new Decorator<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);
-                    }
-
-                };
+            public AsyncServerExchangeHandler decorate(final AsyncServerExchangeHandler exchangeHandler) {
+                return new RedirectingAsyncDecorator(
+                        exchangeHandler,
+                        new RedirectResolver() {
+
+                            @Override
+                            public Redirect resolve(final URI requestUri) throws URISyntaxException {
+                                final String path = requestUri.getPath();
+                                if (path.startsWith("/oldlocation")) {
+                                    return new Redirect(HttpStatus.SC_MOVED_TEMPORARILY, null);
+                                }
+                                return null;
+                            }
+
+                        });
             }
 
         });
-        final HttpHost target = start();
         final HttpClientContext context = HttpClientContext.create();
         final Future<SimpleHttpResponse> future = httpclient.execute(
-                SimpleHttpRequests.get(target, "/oldlocation/"), context, null);
+                SimpleHttpRequests.get(target, "/oldlocation/100"), 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("/oldlocation/100", request.getRequestUri());
         Assert.assertEquals(target, new HttpHost(request.getScheme(), request.getAuthority()));
     }
 
     @Test
     public void testBasicRedirect303() throws Exception {
-        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
+        final HttpHost target = start(new Decorator<AsyncServerExchangeHandler>() {
 
             @Override
-            public AsyncServerExchangeHandler get() {
-                return new BasicRedirectService(HttpStatus.SC_SEE_OTHER);
+            public AsyncServerExchangeHandler decorate(final AsyncServerExchangeHandler exchangeHandler) {
+                return new RedirectingAsyncDecorator(
+                        exchangeHandler,
+                        new OldPathRedirectResolver("/oldlocation", "/random", HttpStatus.SC_SEE_OTHER));
             }
 
         });
-        final HttpHost target = start();
         final HttpClientContext context = HttpClientContext.create();
         final Future<SimpleHttpResponse> future = httpclient.execute(
-                SimpleHttpRequests.get(target, "/oldlocation/"), context, null);
+                SimpleHttpRequests.get(target, "/oldlocation/123"), 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("/random/123", request.getRequestUri());
         Assert.assertEquals(target, new HttpHost(request.getScheme(), request.getAuthority()));
     }
 
     @Test
     public void testBasicRedirect304() throws Exception {
-        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
+        server.register("/oldlocation/*", new Supplier<AsyncServerExchangeHandler>() {
 
             @Override
             public AsyncServerExchangeHandler get() {
-                return new BasicRedirectService(HttpStatus.SC_NOT_MODIFIED);
-            }
 
+                return new AbstractSimpleServerExchangeHandler() {
+
+                    @Override
+                    protected SimpleHttpResponse handle(final SimpleHttpRequest request,
+                                                        final HttpCoreContext context) throws HttpException {
+                        return SimpleHttpResponse.create(HttpStatus.SC_NOT_MODIFIED, (String) null);
+                    }
+                };
+
+            }
         });
         final HttpHost target = start();
         final HttpClientContext context = HttpClientContext.create();
@@ -356,13 +258,21 @@ public abstract class AbstractHttpAsyncRedirectsTest <T extends CloseableHttpAsy
 
     @Test
     public void testBasicRedirect305() throws Exception {
-        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
+        server.register("/oldlocation/*", new Supplier<AsyncServerExchangeHandler>() {
 
             @Override
             public AsyncServerExchangeHandler get() {
-                return new BasicRedirectService(HttpStatus.SC_USE_PROXY);
-            }
 
+                return new AbstractSimpleServerExchangeHandler() {
+
+                    @Override
+                    protected SimpleHttpResponse handle(final SimpleHttpRequest request,
+                                                        final HttpCoreContext context) throws HttpException {
+                        return SimpleHttpResponse.create(HttpStatus.SC_USE_PROXY, (String) null);
+                    }
+                };
+
+            }
         });
         final HttpHost target = start();
         final HttpClientContext context = HttpClientContext.create();
@@ -379,39 +289,42 @@ public abstract class AbstractHttpAsyncRedirectsTest <T extends CloseableHttpAsy
 
     @Test
     public void testBasicRedirect307() throws Exception {
-        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
+        final HttpHost target = start(new Decorator<AsyncServerExchangeHandler>() {
 
             @Override
-            public AsyncServerExchangeHandler get() {
-                return new BasicRedirectService(HttpStatus.SC_TEMPORARY_REDIRECT);
+            public AsyncServerExchangeHandler decorate(final AsyncServerExchangeHandler exchangeHandler) {
+                return new RedirectingAsyncDecorator(
+                        exchangeHandler,
+                        new OldPathRedirectResolver("/oldlocation", "/random", HttpStatus.SC_TEMPORARY_REDIRECT));
             }
 
         });
-        final HttpHost target = start();
         final HttpClientContext context = HttpClientContext.create();
         final Future<SimpleHttpResponse> future = httpclient.execute(
-                SimpleHttpRequests.get(target, "/oldlocation/"), context, null);
+                SimpleHttpRequests.get(target, "/oldlocation/123"), 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("/random/123", request.getRequestUri());
         Assert.assertEquals(target, new HttpHost(request.getScheme(), request.getAuthority()));
     }
 
     @Test(expected=ExecutionException.class)
     public void testMaxRedirectCheck() throws Exception {
-        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
+        final HttpHost target = start(new Decorator<AsyncServerExchangeHandler>() {
 
             @Override
-            public AsyncServerExchangeHandler get() {
-                return new CircularRedirectService();
+            public AsyncServerExchangeHandler decorate(final AsyncServerExchangeHandler exchangeHandler) {
+                return new RedirectingAsyncDecorator(
+                        exchangeHandler,
+                        new OldPathRedirectResolver("/circular-oldlocation/", "/circular-oldlocation/",
+                                HttpStatus.SC_MOVED_TEMPORARILY));
             }
 
         });
-        final HttpHost target = start();
 
         final RequestConfig config = RequestConfig.custom()
                 .setCircularRedirectsAllowed(true)
@@ -429,15 +342,17 @@ public abstract class AbstractHttpAsyncRedirectsTest <T extends CloseableHttpAsy
 
     @Test(expected=ExecutionException.class)
     public void testCircularRedirect() throws Exception {
-        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
+        final HttpHost target = start(new Decorator<AsyncServerExchangeHandler>() {
 
             @Override
-            public AsyncServerExchangeHandler get() {
-                return new CircularRedirectService();
+            public AsyncServerExchangeHandler decorate(final AsyncServerExchangeHandler exchangeHandler) {
+                return new RedirectingAsyncDecorator(
+                        exchangeHandler,
+                        new OldPathRedirectResolver("/circular-oldlocation/", "/circular-oldlocation/",
+                                HttpStatus.SC_MOVED_TEMPORARILY));
             }
 
         });
-        final HttpHost target = start();
 
         final RequestConfig config = RequestConfig.custom()
                 .setCircularRedirectsAllowed(false)
@@ -455,19 +370,20 @@ public abstract class AbstractHttpAsyncRedirectsTest <T extends CloseableHttpAsy
 
     @Test
     public void testPostRedirectSeeOther() throws Exception {
-        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
+        final HttpHost target = start(new Decorator<AsyncServerExchangeHandler>() {
 
             @Override
-            public AsyncServerExchangeHandler get() {
-                return new BasicRedirectService(HttpStatus.SC_SEE_OTHER);
+            public AsyncServerExchangeHandler decorate(final AsyncServerExchangeHandler exchangeHandler) {
+                return new RedirectingAsyncDecorator(
+                        exchangeHandler,
+                        new OldPathRedirectResolver("/oldlocation", "/echo", HttpStatus.SC_SEE_OTHER));
             }
 
         });
-        final HttpHost target = start();
 
         final HttpClientContext context = HttpClientContext.create();
 
-        final SimpleHttpRequest post = SimpleHttpRequests.post(target, "/oldlocation/");
+        final SimpleHttpRequest post = SimpleHttpRequests.post(target, "/oldlocation/stuff");
         post.setBody("stuff", ContentType.TEXT_PLAIN);
         final Future<SimpleHttpResponse> future = httpclient.execute(post, context, null);
         final HttpResponse response = future.get();
@@ -476,106 +392,112 @@ public abstract class AbstractHttpAsyncRedirectsTest <T extends CloseableHttpAsy
         final HttpRequest request = context.getRequest();
 
         Assert.assertEquals(HttpStatus.SC_OK, response.getCode());
-        Assert.assertEquals("/newlocation/", request.getRequestUri());
+        Assert.assertEquals("/echo/stuff", request.getRequestUri());
         Assert.assertEquals("GET", request.getMethod());
     }
 
     @Test
     public void testRelativeRedirect() throws Exception {
-        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
+        final HttpHost target = start(new Decorator<AsyncServerExchangeHandler>() {
 
             @Override
-            public AsyncServerExchangeHandler get() {
-                return new RelativeRedirectService();
+            public AsyncServerExchangeHandler decorate(final AsyncServerExchangeHandler exchangeHandler) {
+                return new RedirectingAsyncDecorator(
+                        exchangeHandler,
+                        new RedirectResolver() {
+
+                            @Override
+                            public Redirect resolve(final URI requestUri) throws URISyntaxException {
+                                final String path = requestUri.getPath();
+                                if (path.startsWith("/oldlocation")) {
+                                    return new Redirect(HttpStatus.SC_MOVED_TEMPORARILY, "/random/100");
+
+                                }
+                                return null;
+                            }
+
+                        });
             }
 
         });
-        final HttpHost target = start();
 
         final HttpClientContext context = HttpClientContext.create();
 
         final Future<SimpleHttpResponse> future = httpclient.execute(
-                SimpleHttpRequests.get(target, "/oldlocation/"), context, null);
+                SimpleHttpRequests.get(target, "/oldlocation/stuff"), 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("/random/100", request.getRequestUri());
         Assert.assertEquals(target, new HttpHost(request.getScheme(), request.getAuthority()));
     }
 
     @Test
     public void testRelativeRedirect2() throws Exception {
-        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
+        final HttpHost target = start(new Decorator<AsyncServerExchangeHandler>() {
 
             @Override
-            public AsyncServerExchangeHandler get() {
-                return new RelativeRedirectService2();
+            public AsyncServerExchangeHandler decorate(final AsyncServerExchangeHandler exchangeHandler) {
+                return new RedirectingAsyncDecorator(
+                        exchangeHandler,
+                        new RedirectResolver() {
+
+                            @Override
+                            public Redirect resolve(final URI requestUri) throws URISyntaxException {
+                                final String path = requestUri.getPath();
+                                if (path.equals("/random/oldlocation")) {
+                                    return new Redirect(HttpStatus.SC_MOVED_TEMPORARILY, "100");
+
+                                }
+                                return null;
+                            }
+
+                        });
             }
 
         });
-        final HttpHost target = start();
 
         final HttpClientContext context = HttpClientContext.create();
 
         final Future<SimpleHttpResponse> future = httpclient.execute(
-                SimpleHttpRequests.get(target, "/test/oldlocation"), context, null);
+                SimpleHttpRequests.get(target, "/random/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("/random/100", request.getRequestUri());
         Assert.assertEquals(target, new HttpHost(request.getScheme(), request.getAuthority()));
     }
 
-    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.setBody("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>() {
+        final HttpHost target = start(new Decorator<AsyncServerExchangeHandler>() {
 
             @Override
-            public AsyncServerExchangeHandler get() {
-                return new BogusRedirectService("xxx://bogus");
+            public AsyncServerExchangeHandler decorate(final AsyncServerExchangeHandler exchangeHandler) {
+                return new RedirectingAsyncDecorator(
+                        exchangeHandler,
+                        new RedirectResolver() {
+
+                            @Override
+                            public Redirect resolve(final URI requestUri) throws URISyntaxException {
+                                final String path = requestUri.getPath();
+                                if (path.equals("/oldlocation/")) {
+                                    return new Redirect(HttpStatus.SC_MOVED_TEMPORARILY, "xxx://bogus");
+
+                                }
+                                return null;
+                            }
+
+                        });
             }
 
         });
-        final HttpHost target = start();
 
         try {
             final Future<SimpleHttpResponse> future = httpclient.execute(
@@ -589,15 +511,28 @@ public abstract class AbstractHttpAsyncRedirectsTest <T extends CloseableHttpAsy
 
     @Test(expected=ExecutionException.class)
     public void testRejectInvalidRedirectLocation() throws Exception {
-        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
+        final HttpHost target = start(new Decorator<AsyncServerExchangeHandler>() {
 
             @Override
-            public AsyncServerExchangeHandler get() {
-                return new BogusRedirectService("/newlocation/?p=I have spaces");
+            public AsyncServerExchangeHandler decorate(final AsyncServerExchangeHandler exchangeHandler) {
+                return new RedirectingAsyncDecorator(
+                        exchangeHandler,
+                        new RedirectResolver() {
+
+                            @Override
+                            public Redirect resolve(final URI requestUri) throws URISyntaxException {
+                                final String path = requestUri.getPath();
+                                if (path.equals("/oldlocation/")) {
+                                    return new Redirect(HttpStatus.SC_MOVED_TEMPORARILY, "/newlocation/?p=I have spaces");
+
+                                }
+                                return null;
+                            }
+
+                        });
             }
 
         });
-        final HttpHost target = start();
 
         try {
             final Future<SimpleHttpResponse> future = httpclient.execute(
@@ -611,15 +546,16 @@ public abstract class AbstractHttpAsyncRedirectsTest <T extends CloseableHttpAsy
 
     @Test
     public void testRedirectWithCookie() throws Exception {
-        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
+        final HttpHost target = start(new Decorator<AsyncServerExchangeHandler>() {
 
             @Override
-            public AsyncServerExchangeHandler get() {
-                return new BasicRedirectService(HttpStatus.SC_MOVED_TEMPORARILY);
+            public AsyncServerExchangeHandler decorate(final AsyncServerExchangeHandler exchangeHandler) {
+                return new RedirectingAsyncDecorator(
+                        exchangeHandler,
+                        new OldPathRedirectResolver("/oldlocation", "/random", HttpStatus.SC_MOVED_TEMPORARILY));
             }
 
         });
-        final HttpHost target = start();
 
         final CookieStore cookieStore = new BasicCookieStore();
         final HttpClientContext context = HttpClientContext.create();
@@ -632,214 +568,17 @@ public abstract class AbstractHttpAsyncRedirectsTest <T extends CloseableHttpAsy
         cookieStore.addCookie(cookie);
 
         final Future<SimpleHttpResponse> future = httpclient.execute(
-                SimpleHttpRequests.get(target, "/oldlocation/"), context, null);
+                SimpleHttpRequests.get(target, "/oldlocation/100"), 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("/random/100", 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 H2TestServer secondServer = new H2TestServer(IOReactorConfig.DEFAULT,
-                scheme == URIScheme.HTTPS ? SSLTestContexts.createServerSSLContext() : null, null, null);
-        try {
-            secondServer.register("/redirect/*", new Supplier<AsyncServerExchangeHandler>() {
-
-                @Override
-                public AsyncServerExchangeHandler get() {
-                    return new CrossSiteRedirectService(redirectTarget);
-                }
-
-            });
-
-            if (version.greaterEquals(HttpVersion.HTTP_2)) {
-                secondServer.start(H2Config.DEFAULT);
-            } else {
-                secondServer.start(Http1Config.DEFAULT);
-            }
-            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(scheme.name(), "localhost", address2.getPort());
-
-            final Queue<Future<SimpleHttpResponse>> queue = new ConcurrentLinkedQueue<>();
-            for (int i = 0; i < 1; i++) {
-                queue.add(httpclient.execute(SimpleHttpRequests.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.setBody("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(
-                SimpleHttpRequests.get(target, "/rome"), context, null);
-        final HttpResponse response1 = future1.get();
-        Assert.assertNotNull(response1);
-
-        final Future<SimpleHttpResponse> future2 = httpclient.execute(
-                SimpleHttpRequests.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.getScheme(), request.getAuthority()));
-    }
-
-    @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(
-                SimpleHttpRequests.get(target, "/lille"), context, null);
-        final HttpResponse response1 = future1.get();
-        Assert.assertNotNull(response1);
-
-        final Future<SimpleHttpResponse> future2 = httpclient.execute(
-                SimpleHttpRequests.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.getScheme(), request.getAuthority()));
-    }
-
-    @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(
-                SimpleHttpRequests.get(target, "/alian"), context, null);
-        final HttpResponse response1 = future1.get();
-        Assert.assertNotNull(response1);
-
-        final Future<SimpleHttpResponse> future2 = httpclient.execute(
-                SimpleHttpRequests.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.getScheme(), request.getAuthority()));
-    }
-
 }
diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/sync/TestRedirects.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/sync/TestRedirects.java
index aedefd6..78b12b1 100644
--- a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/sync/TestRedirects.java
+++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/sync/TestRedirects.java
@@ -43,6 +43,11 @@ import org.apache.hc.client5.http.impl.cookie.BasicClientCookie;
 import org.apache.hc.client5.http.protocol.HttpClientContext;
 import org.apache.hc.client5.http.protocol.RedirectLocations;
 import org.apache.hc.client5.http.utils.URIUtils;
+import org.apache.hc.client5.testing.OldPathRedirectResolver;
+import org.apache.hc.client5.testing.classic.RedirectingDecorator;
+import org.apache.hc.client5.testing.redirect.Redirect;
+import org.apache.hc.client5.testing.redirect.RedirectResolver;
+import org.apache.hc.core5.function.Decorator;
 import org.apache.hc.core5.http.ClassicHttpRequest;
 import org.apache.hc.core5.http.ClassicHttpResponse;
 import org.apache.hc.core5.http.Header;
@@ -53,11 +58,11 @@ import org.apache.hc.core5.http.HttpRequest;
 import org.apache.hc.core5.http.HttpStatus;
 import org.apache.hc.core5.http.ProtocolException;
 import org.apache.hc.core5.http.io.HttpRequestHandler;
+import org.apache.hc.core5.http.io.HttpServerRequestHandler;
 import org.apache.hc.core5.http.io.entity.EntityUtils;
 import org.apache.hc.core5.http.io.entity.StringEntity;
 import org.apache.hc.core5.http.message.BasicHeader;
 import org.apache.hc.core5.http.protocol.HttpContext;
-import org.apache.hc.core5.net.URIBuilder;
 import org.junit.Assert;
 import org.junit.Test;
 
@@ -66,388 +71,306 @@ import org.junit.Test;
  */
 public class TestRedirects extends LocalServerTestBase {
 
-    private static class BasicRedirectService implements HttpRequestHandler {
-
-        private final int statuscode;
-
-        public BasicRedirectService(final int statuscode) {
-            super();
-            this.statuscode = statuscode > 0 ? statuscode : HttpStatus.SC_MOVED_TEMPORARILY;
-        }
-
-        public BasicRedirectService() {
-            this(-1);
-        }
-
-        @Override
-        public void handle(
-                final ClassicHttpRequest request,
-                final ClassicHttpResponse response,
-                final HttpContext context) throws HttpException, IOException {
-
-            try {
-                final URI requestURI = request.getUri();
-                final String path = requestURI.getPath();
-                if (path.equals("/oldlocation/")) {
-                    response.setCode(this.statuscode);
-                    response.addHeader(new BasicHeader("Location",
-                            new URIBuilder(requestURI).setPath("/newlocation/").build()));
-                    response.addHeader(new BasicHeader("Connection", "close"));
-                } else if (path.equals("/newlocation/")) {
-                    response.setCode(HttpStatus.SC_OK);
-                    final StringEntity entity = new StringEntity("Successful redirect");
-                    response.setEntity(entity);
-                } else {
-                    response.setCode(HttpStatus.SC_NOT_FOUND);
-                }
-
-            } catch (final URISyntaxException ex) {
-                throw new ProtocolException(ex.getMessage(), ex);
-            }
-        }
-
-    }
-
-    private static class CircularRedirectService implements HttpRequestHandler {
-
-        public CircularRedirectService() {
-            super();
-        }
+    @Test
+    public void testBasicRedirect300() throws Exception {
+        final HttpHost target = start(null, new Decorator<HttpServerRequestHandler>() {
 
-        @Override
-        public void handle(
-                final ClassicHttpRequest request,
-                final ClassicHttpResponse response,
-                final HttpContext context) throws HttpException, IOException {
-            try {
-                final URI requestURI = request.getUri();
-                final String path = requestURI.getPath();
-                if (path.startsWith("/circular-oldlocation")) {
-                    response.setCode(HttpStatus.SC_MOVED_TEMPORARILY);
-                    response.addHeader(new BasicHeader("Location", "/circular-location2"));
-                } else if (path.startsWith("/circular-location2")) {
-                    response.setCode(HttpStatus.SC_MOVED_TEMPORARILY);
-                    response.addHeader(new BasicHeader("Location", "/circular-oldlocation"));
-                } else {
-                    response.setCode(HttpStatus.SC_NOT_FOUND);
-                }
-            } catch (final URISyntaxException ex) {
-                throw new ProtocolException(ex.getMessage(), ex);
+            @Override
+            public HttpServerRequestHandler decorate(final HttpServerRequestHandler requestHandler) {
+                return new RedirectingDecorator(
+                        requestHandler,
+                        new OldPathRedirectResolver("/oldlocation", "/random", HttpStatus.SC_MULTIPLE_CHOICES));
             }
-        }
-    }
 
-    private static class RelativeRedirectService implements HttpRequestHandler {
-
-        public RelativeRedirectService() {
-            super();
-        }
-
-        @Override
-        public void handle(
-                final ClassicHttpRequest request,
-                final ClassicHttpResponse response,
-                final HttpContext context) throws HttpException, IOException {
-            try {
-                final URI requestURI = request.getUri();
-                final String path = requestURI.getPath();
-                if (path.equals("/oldlocation/")) {
-                    response.setCode(HttpStatus.SC_MOVED_TEMPORARILY);
-                    response.addHeader(new BasicHeader("Location", "/relativelocation/"));
-                } else if (path.equals("/relativelocation/")) {
-                    response.setCode(HttpStatus.SC_OK);
-                    final StringEntity entity = new StringEntity("Successful redirect");
-                    response.setEntity(entity);
-                } else {
-                    response.setCode(HttpStatus.SC_NOT_FOUND);
-                }
-            } catch (final URISyntaxException ex) {
-                throw new ProtocolException(ex.getMessage(), ex);
-            }
-        }
-    }
-
-    private static class RelativeRedirectService2 implements HttpRequestHandler {
-
-        public RelativeRedirectService2() {
-            super();
-        }
+        });
 
-        @Override
-        public void handle(
-                final ClassicHttpRequest request,
-                final ClassicHttpResponse response,
-                final HttpContext context) throws HttpException, IOException {
-            try {
-                final URI requestURI = request.getUri();
-                final String path = requestURI.getPath();
-                if (path.equals("/test/oldlocation")) {
-                    response.setCode(HttpStatus.SC_MOVED_TEMPORARILY);
-                    response.addHeader(new BasicHeader("Location", "relativelocation"));
-                } else if (path.equals("/test/relativelocation")) {
-                    response.setCode(HttpStatus.SC_OK);
-                    final StringEntity entity = new StringEntity("Successful redirect");
-                    response.setEntity(entity);
-                } else {
-                    response.setCode(HttpStatus.SC_NOT_FOUND);
-                }
-            } catch (final URISyntaxException ex) {
-                throw new ProtocolException(ex.getMessage(), ex);
-            }
-        }
-    }
+        final HttpClientContext context = HttpClientContext.create();
+        final HttpGet httpget = new HttpGet("/oldlocation/100");
+        try (final ClassicHttpResponse response = this.httpclient.execute(target, httpget, context)) {
+            final HttpRequest reqWrapper = context.getRequest();
 
-    private static class RomeRedirectService implements HttpRequestHandler {
+            Assert.assertEquals(HttpStatus.SC_MULTIPLE_CHOICES, response.getCode());
+            Assert.assertEquals(URIUtils.create(target, "/oldlocation/100"), reqWrapper.getUri());
 
-        public RomeRedirectService() {
-            super();
-        }
+            final RedirectLocations redirects = context.getRedirectLocations();
+            Assert.assertNotNull(redirects);
+            Assert.assertEquals(0, redirects.size());
 
-        @Override
-        public void handle(
-                final ClassicHttpRequest request,
-                final ClassicHttpResponse response,
-                final HttpContext context) throws HttpException, IOException {
-            try {
-                final URI requestURI = request.getUri();
-                final String path = requestURI.getPath();
-                if (path.equals("/rome")) {
-                    response.setCode(HttpStatus.SC_OK);
-                    final StringEntity entity = new StringEntity("Successful redirect");
-                    response.setEntity(entity);
-                } else {
-                    response.setCode(HttpStatus.SC_MOVED_TEMPORARILY);
-                    response.addHeader(new BasicHeader("Location", "/rome"));
-                }
-            } catch (final URISyntaxException ex) {
-                throw new ProtocolException(ex.getMessage(), ex);
-            }
+            EntityUtils.consume(response.getEntity());
         }
     }
 
-    interface UriTransformation {
-
-        String rewrite(URI requestUri);
-
-    }
-
-    private static class TransformingRedirectService implements HttpRequestHandler {
-
-        private final UriTransformation uriTransformation;
-
-        public TransformingRedirectService(final UriTransformation uriTransformation) {
-            super();
-            this.uriTransformation = uriTransformation;
-        }
+    @Test
+    public void testBasicRedirect300NoKeepAlive() throws Exception {
+        final HttpHost target = start(null, new Decorator<HttpServerRequestHandler>() {
 
-        @Override
-        public void handle(
-                final ClassicHttpRequest request,
-                final ClassicHttpResponse response,
-                final HttpContext context) throws HttpException, IOException {
-            try {
-                final URI requestURI = request.getUri();
-                final String path = requestURI.getPath();
-                if (path.equals("/oldlocation/")) {
-                    response.setCode(HttpStatus.SC_MOVED_TEMPORARILY);
-                    response.addHeader(new BasicHeader("Location", uriTransformation.rewrite(requestURI)));
-                } else if (path.equals("/relativelocation/")) {
-                    response.setCode(HttpStatus.SC_OK);
-                    final StringEntity entity = new StringEntity("Successful redirect");
-                    response.setEntity(entity);
-                } else {
-                    response.setCode(HttpStatus.SC_NOT_FOUND);
-                }
-            } catch (final URISyntaxException ex) {
-                throw new ProtocolException(ex.getMessage(), ex);
+            @Override
+            public HttpServerRequestHandler decorate(final HttpServerRequestHandler requestHandler) {
+                return new RedirectingDecorator(
+                        requestHandler,
+                        new OldPathRedirectResolver("/oldlocation", "/random", HttpStatus.SC_MULTIPLE_CHOICES,
+                                Redirect.ConnControl.CLOSE));
             }
-        }
-    }
 
-    @Test
-    public void testBasicRedirect300() throws Exception {
-        this.server.registerHandler("*", new BasicRedirectService(HttpStatus.SC_MULTIPLE_CHOICES));
-
-        final HttpHost target = start();
+        });
 
         final HttpClientContext context = HttpClientContext.create();
+        final HttpGet httpget = new HttpGet("/oldlocation/100");
+        try (final ClassicHttpResponse response = this.httpclient.execute(target, httpget, context)) {
+            final HttpRequest reqWrapper = context.getRequest();
 
-        final HttpGet httpget = new HttpGet("/oldlocation/");
-
-        final ClassicHttpResponse response = this.httpclient.execute(target, httpget, context);
-        EntityUtils.consume(response.getEntity());
+            Assert.assertEquals(HttpStatus.SC_MULTIPLE_CHOICES, response.getCode());
+            Assert.assertEquals(URIUtils.create(target, "/oldlocation/100"), reqWrapper.getUri());
 
-        final HttpRequest reqWrapper = context.getRequest();
+            final RedirectLocations redirects = context.getRedirectLocations();
+            Assert.assertNotNull(redirects);
+            Assert.assertEquals(0, redirects.size());
 
-        Assert.assertEquals(HttpStatus.SC_MULTIPLE_CHOICES, response.getCode());
-        Assert.assertEquals(URIUtils.create(target, "/oldlocation/"), reqWrapper.getUri());
-
-        final RedirectLocations redirects = context.getRedirectLocations();
-        Assert.assertNotNull(redirects);
-        Assert.assertEquals(0, redirects.size());
+            EntityUtils.consume(response.getEntity());
+        }
     }
 
     @Test
     public void testBasicRedirect301() throws Exception {
-        this.server.registerHandler("*", new BasicRedirectService(HttpStatus.SC_MOVED_PERMANENTLY));
+        final HttpHost target = start(null, new Decorator<HttpServerRequestHandler>() {
 
-        final HttpHost target = start();
+            @Override
+            public HttpServerRequestHandler decorate(final HttpServerRequestHandler requestHandler) {
+                return new RedirectingDecorator(
+                        requestHandler,
+                        new OldPathRedirectResolver("/oldlocation", "/random", HttpStatus.SC_MOVED_PERMANENTLY));
+            }
+
+        });
 
         final HttpClientContext context = HttpClientContext.create();
 
-        final HttpGet httpget = new HttpGet("/oldlocation/");
+        final HttpGet httpget = new HttpGet("/oldlocation/100");
 
-        final ClassicHttpResponse response = this.httpclient.execute(target, httpget, context);
-        EntityUtils.consume(response.getEntity());
+        try (final ClassicHttpResponse response = this.httpclient.execute(target, httpget, context)) {
+            final HttpRequest reqWrapper = context.getRequest();
 
-        final HttpRequest reqWrapper = context.getRequest();
+            Assert.assertEquals(HttpStatus.SC_OK, response.getCode());
+            Assert.assertEquals(URIUtils.create(target, "/random/100"), reqWrapper.getUri());
 
-        Assert.assertEquals(HttpStatus.SC_OK, response.getCode());
-        Assert.assertEquals(URIUtils.create(target, "/newlocation/"), reqWrapper.getUri());
+            final RedirectLocations redirects = context.getRedirectLocations();
+            Assert.assertNotNull(redirects);
+            Assert.assertEquals(1, redirects.size());
 
-        final RedirectLocations redirects = context.getRedirectLocations();
-        Assert.assertNotNull(redirects);
-        Assert.assertEquals(1, redirects.size());
+            final URI redirect = URIUtils.rewriteURI(new URI("/random/100"), target);
+            Assert.assertTrue(redirects.contains(redirect));
 
-        final URI redirect = URIUtils.rewriteURI(new URI("/newlocation/"), target);
-        Assert.assertTrue(redirects.contains(redirect));
+            EntityUtils.consume(response.getEntity());
+        }
     }
 
     @Test
     public void testBasicRedirect302() throws Exception {
-        this.server.registerHandler("*", new BasicRedirectService(HttpStatus.SC_MOVED_TEMPORARILY));
+        final HttpHost target = start(null, new Decorator<HttpServerRequestHandler>() {
 
-        final HttpHost target = start();
+            @Override
+            public HttpServerRequestHandler decorate(final HttpServerRequestHandler requestHandler) {
+                return new RedirectingDecorator(
+                        requestHandler,
+                        new OldPathRedirectResolver("/oldlocation", "/random", HttpStatus.SC_MOVED_TEMPORARILY));
+            }
+
+        });
 
         final HttpClientContext context = HttpClientContext.create();
 
-        final HttpGet httpget = new HttpGet("/oldlocation/");
+        final HttpGet httpget = new HttpGet("/oldlocation/50");
 
-        final ClassicHttpResponse response = this.httpclient.execute(target, httpget, context);
-        EntityUtils.consume(response.getEntity());
+        try (final ClassicHttpResponse response = this.httpclient.execute(target, httpget, context)) {
+            final HttpRequest reqWrapper = context.getRequest();
 
-        final HttpRequest reqWrapper = context.getRequest();
+            Assert.assertEquals(HttpStatus.SC_OK, response.getCode());
+            Assert.assertEquals(URIUtils.create(target, "/random/50"), reqWrapper.getUri());
 
-        Assert.assertEquals(HttpStatus.SC_OK, response.getCode());
-        Assert.assertEquals(URIUtils.create(target, "/newlocation/"), reqWrapper.getUri());
+            EntityUtils.consume(response.getEntity());
+        }
     }
 
     @Test
     public void testBasicRedirect302NoLocation() throws Exception {
-        this.server.registerHandler("*", new HttpRequestHandler() {
+        final HttpHost target = start(null, new Decorator<HttpServerRequestHandler>() {
 
             @Override
-            public void handle(
-                    final ClassicHttpRequest request,
-                    final ClassicHttpResponse response,
-                    final HttpContext context) throws HttpException, IOException {
-                response.setCode(HttpStatus.SC_MOVED_TEMPORARILY);
+            public HttpServerRequestHandler decorate(final HttpServerRequestHandler requestHandler) {
+                return new RedirectingDecorator(
+                        requestHandler,
+                        new RedirectResolver() {
+
+                            @Override
+                            public Redirect resolve(final URI requestUri) throws URISyntaxException {
+                                final String path = requestUri.getPath();
+                                if (path.startsWith("/oldlocation")) {
+                                    return new Redirect(HttpStatus.SC_MOVED_TEMPORARILY, null);
+                                }
+                                return null;
+                            }
+
+                        });
             }
 
         });
 
-        final HttpHost target = start();
-
         final HttpClientContext context = HttpClientContext.create();
 
-        final HttpGet httpget = new HttpGet("/oldlocation/");
+        final HttpGet httpget = new HttpGet("/oldlocation/100");
 
-        final ClassicHttpResponse response = this.httpclient.execute(target, httpget, context);
-        EntityUtils.consume(response.getEntity());
+        try (final ClassicHttpResponse response = this.httpclient.execute(target, httpget, context)) {
+            final HttpRequest reqWrapper = context.getRequest();
 
-        final HttpRequest reqWrapper = context.getRequest();
+            Assert.assertEquals(HttpStatus.SC_MOVED_TEMPORARILY, response.getCode());
+            Assert.assertEquals("/oldlocation/100", reqWrapper.getRequestUri());
 
-        Assert.assertEquals(HttpStatus.SC_MOVED_TEMPORARILY, response.getCode());
-        Assert.assertEquals("/oldlocation/", reqWrapper.getRequestUri());
+            EntityUtils.consume(response.getEntity());
+        }
     }
 
     @Test
     public void testBasicRedirect303() throws Exception {
-        this.server.registerHandler("*", new BasicRedirectService(HttpStatus.SC_SEE_OTHER));
+        final HttpHost target = start(null, new Decorator<HttpServerRequestHandler>() {
 
-        final HttpHost target = start();
+            @Override
+            public HttpServerRequestHandler decorate(final HttpServerRequestHandler requestHandler) {
+                return new RedirectingDecorator(
+                        requestHandler,
+                        new OldPathRedirectResolver("/oldlocation", "/random", HttpStatus.SC_SEE_OTHER));
+            }
+
+        });
 
         final HttpClientContext context = HttpClientContext.create();
 
-        final HttpGet httpget = new HttpGet("/oldlocation/");
+        final HttpGet httpget = new HttpGet("/oldlocation/123");
 
-        final ClassicHttpResponse response = this.httpclient.execute(target, httpget, context);
-        EntityUtils.consume(response.getEntity());
+        try (final ClassicHttpResponse response = this.httpclient.execute(target, httpget, context)) {
+            final HttpRequest reqWrapper = context.getRequest();
 
-        final HttpRequest reqWrapper = context.getRequest();
+            Assert.assertEquals(HttpStatus.SC_OK, response.getCode());
+            Assert.assertEquals(URIUtils.create(target, "/random/123"), reqWrapper.getUri());
 
-        Assert.assertEquals(HttpStatus.SC_OK, response.getCode());
-        Assert.assertEquals(URIUtils.create(target, "/newlocation/"), reqWrapper.getUri());
+            EntityUtils.consume(response.getEntity());
+        }
     }
 
     @Test
     public void testBasicRedirect304() throws Exception {
-        this.server.registerHandler("*", new BasicRedirectService(HttpStatus.SC_NOT_MODIFIED));
+        this.server.registerHandler("/oldlocation/*", new HttpRequestHandler() {
+
+            @Override
+            public void handle(final ClassicHttpRequest request,
+                               final ClassicHttpResponse response,
+                               final HttpContext context) throws HttpException, IOException {
+                response.setCode(HttpStatus.SC_NOT_MODIFIED);
+                response.addHeader(HttpHeaders.LOCATION, "/random/100");
+            }
+
+        });
 
         final HttpHost target = start();
 
         final HttpClientContext context = HttpClientContext.create();
 
-        final HttpGet httpget = new HttpGet("/oldlocation/");
+        final HttpGet httpget = new HttpGet("/oldlocation/stuff");
 
-        final ClassicHttpResponse response = this.httpclient.execute(target, httpget, context);
-        EntityUtils.consume(response.getEntity());
+        try (final ClassicHttpResponse response = this.httpclient.execute(target, httpget, context)) {
+            final HttpRequest reqWrapper = context.getRequest();
 
-        final HttpRequest reqWrapper = context.getRequest();
+            Assert.assertEquals(HttpStatus.SC_NOT_MODIFIED, response.getCode());
+            Assert.assertEquals(URIUtils.create(target, "/oldlocation/stuff"), reqWrapper.getUri());
 
-        Assert.assertEquals(HttpStatus.SC_NOT_MODIFIED, response.getCode());
-        Assert.assertEquals(URIUtils.create(target, "/oldlocation/"), reqWrapper.getUri());
+            final RedirectLocations redirects = context.getRedirectLocations();
+            Assert.assertNotNull(redirects);
+            Assert.assertEquals(0, redirects.size());
+
+            EntityUtils.consume(response.getEntity());
+        }
     }
 
     @Test
     public void testBasicRedirect305() throws Exception {
-        this.server.registerHandler("*", new BasicRedirectService(HttpStatus.SC_USE_PROXY));
+        this.server.registerHandler("/oldlocation/*", new HttpRequestHandler() {
+
+            @Override
+            public void handle(final ClassicHttpRequest request,
+                               final ClassicHttpResponse response,
+                               final HttpContext context) throws HttpException, IOException {
+                response.setCode(HttpStatus.SC_USE_PROXY);
+                response.addHeader(HttpHeaders.LOCATION, "/random/100");
+            }
+
+        });
+
         final HttpHost target = start();
 
         final HttpClientContext context = HttpClientContext.create();
 
-        final HttpGet httpget = new HttpGet("/oldlocation/");
+        final HttpGet httpget = new HttpGet("/oldlocation/stuff");
+
+        try (final ClassicHttpResponse response = this.httpclient.execute(target, httpget, context)) {
+            final HttpRequest reqWrapper = context.getRequest();
 
-        final ClassicHttpResponse response = this.httpclient.execute(target, httpget, context);
-        EntityUtils.consume(response.getEntity());
+            Assert.assertEquals(HttpStatus.SC_USE_PROXY, response.getCode());
+            Assert.assertEquals(URIUtils.create(target, "/oldlocation/stuff"), reqWrapper.getUri());
 
-        final HttpRequest reqWrapper = context.getRequest();
+            final RedirectLocations redirects = context.getRedirectLocations();
+            Assert.assertNotNull(redirects);
+            Assert.assertEquals(0, redirects.size());
 
-        Assert.assertEquals(HttpStatus.SC_USE_PROXY, response.getCode());
-        Assert.assertEquals(URIUtils.create(target, "/oldlocation/"), reqWrapper.getUri());
+            EntityUtils.consume(response.getEntity());
+        }
     }
 
     @Test
     public void testBasicRedirect307() throws Exception {
-        this.server.registerHandler("*", new BasicRedirectService(HttpStatus.SC_TEMPORARY_REDIRECT));
+        final HttpHost target = start(null, new Decorator<HttpServerRequestHandler>() {
 
-        final HttpHost target = start();
+            @Override
+            public HttpServerRequestHandler decorate(final HttpServerRequestHandler requestHandler) {
+                return new RedirectingDecorator(
+                        requestHandler,
+                        new OldPathRedirectResolver("/oldlocation", "/random", HttpStatus.SC_TEMPORARY_REDIRECT));
+            }
+
+        });
 
         final HttpClientContext context = HttpClientContext.create();
 
-        final HttpGet httpget = new HttpGet("/oldlocation/");
+        final HttpGet httpget = new HttpGet("/oldlocation/123");
 
-        final ClassicHttpResponse response = this.httpclient.execute(target, httpget, context);
-        EntityUtils.consume(response.getEntity());
+        try (final ClassicHttpResponse response = this.httpclient.execute(target, httpget, context)) {
+            final HttpRequest reqWrapper = context.getRequest();
 
-        final HttpRequest reqWrapper = context.getRequest();
+            Assert.assertEquals(HttpStatus.SC_OK, response.getCode());
+            Assert.assertEquals(URIUtils.create(target, "/random/123"), reqWrapper.getUri());
 
-        Assert.assertEquals(HttpStatus.SC_OK, response.getCode());
-        Assert.assertEquals(URIUtils.create(target, "/newlocation/"), reqWrapper.getUri());
+            EntityUtils.consume(response.getEntity());
+        }
     }
 
-    @Test(expected=ClientProtocolException.class)
+    @Test(expected = ClientProtocolException.class)
     public void testMaxRedirectCheck() throws Exception {
-        this.server.registerHandler("*", new CircularRedirectService());
+        final HttpHost target = start(null, new Decorator<HttpServerRequestHandler>() {
 
-        final HttpHost target = start();
+            @Override
+            public HttpServerRequestHandler decorate(final HttpServerRequestHandler requestHandler) {
+                return new RedirectingDecorator(
+                        requestHandler,
+                        new OldPathRedirectResolver("/circular-oldlocation/", "/circular-oldlocation/",
+                                HttpStatus.SC_MOVED_TEMPORARILY));
+            }
+
+        });
 
         final RequestConfig config = RequestConfig.custom()
-            .setCircularRedirectsAllowed(true)
-            .setMaxRedirects(5)
-            .build();
+                .setCircularRedirectsAllowed(true)
+                .setMaxRedirects(5)
+                .build();
 
-        final HttpGet httpget = new HttpGet("/circular-oldlocation/");
+        final HttpGet httpget = new HttpGet("/circular-oldlocation/123");
         httpget.setConfig(config);
         try {
             this.httpclient.execute(target, httpget);
@@ -457,17 +380,25 @@ public class TestRedirects extends LocalServerTestBase {
         }
     }
 
-    @Test(expected=ClientProtocolException.class)
+    @Test(expected = ClientProtocolException.class)
     public void testCircularRedirect() throws Exception {
-        this.server.registerHandler("*", new CircularRedirectService());
+        final HttpHost target = start(null, new Decorator<HttpServerRequestHandler>() {
 
-        final HttpHost target = start();
+            @Override
+            public HttpServerRequestHandler decorate(final HttpServerRequestHandler requestHandler) {
+                return new RedirectingDecorator(
+                        requestHandler,
+                        new OldPathRedirectResolver("/circular-oldlocation/", "/circular-oldlocation/",
+                                HttpStatus.SC_MOVED_TEMPORARILY));
+            }
+
+        });
 
         final RequestConfig config = RequestConfig.custom()
-            .setCircularRedirectsAllowed(false)
-            .build();
+                .setCircularRedirectsAllowed(false)
+                .build();
 
-        final HttpGet httpget = new HttpGet("/circular-oldlocation/");
+        final HttpGet httpget = new HttpGet("/circular-oldlocation/123");
         httpget.setConfig(config);
         try {
             this.httpclient.execute(target, httpget);
@@ -478,148 +409,139 @@ public class TestRedirects extends LocalServerTestBase {
     }
 
     @Test
-    public void testRepeatRequest() throws Exception {
-        this.server.registerHandler("*", new RomeRedirectService());
-
-        final HttpHost target = start();
-
-        final HttpClientContext context = HttpClientContext.create();
-
-        final HttpGet first = new HttpGet("/rome");
-
-        EntityUtils.consume(this.httpclient.execute(target, first, context).getEntity());
-
-        final HttpGet second = new HttpGet("/rome");
-
-        final ClassicHttpResponse response = this.httpclient.execute(target, second, context);
-        EntityUtils.consume(response.getEntity());
-
-        final HttpRequest reqWrapper = context.getRequest();
-
-        Assert.assertEquals(HttpStatus.SC_OK, response.getCode());
-        Assert.assertEquals(URIUtils.create(target, "/rome"), reqWrapper.getUri());
-    }
-
-    @Test
-    public void testRepeatRequestRedirect() throws Exception {
-        this.server.registerHandler("*", new RomeRedirectService());
-
-        final HttpHost target = start();
-
-        final HttpClientContext context = HttpClientContext.create();
-
-        final HttpGet first = new HttpGet("/lille");
-        final ClassicHttpResponse response1 = this.httpclient.execute(target, first, context);
-        EntityUtils.consume(response1.getEntity());
-
-        final HttpGet second = new HttpGet("/lille");
-
-        final ClassicHttpResponse response2 = this.httpclient.execute(target, second, context);
-        EntityUtils.consume(response2.getEntity());
-
-        final HttpRequest reqWrapper = context.getRequest();
-
-        Assert.assertEquals(HttpStatus.SC_OK, response2.getCode());
-        Assert.assertEquals(URIUtils.create(target, "/rome"), reqWrapper.getUri());
-    }
-
-    @Test
-    public void testDifferentRequestSameRedirect() throws Exception {
-        this.server.registerHandler("*", new RomeRedirectService());
-
-        final HttpHost target = start();
-
-        final HttpClientContext context = HttpClientContext.create();
-
-        final HttpGet first = new HttpGet("/alian");
-
-        final ClassicHttpResponse response1 = this.httpclient.execute(target, first, context);
-        EntityUtils.consume(response1.getEntity());
-
-        final HttpGet second = new HttpGet("/lille");
-
-        final ClassicHttpResponse response2 = this.httpclient.execute(target, second, context);
-        EntityUtils.consume(response2.getEntity());
-
-        final HttpRequest reqWrapper = context.getRequest();
-
-        Assert.assertEquals(HttpStatus.SC_OK, response2.getCode());
-        Assert.assertEquals(URIUtils.create(target, "/rome"), reqWrapper.getUri());
-    }
-
-    @Test
     public void testPostRedirectSeeOther() throws Exception {
-        this.server.registerHandler("*", new BasicRedirectService(HttpStatus.SC_SEE_OTHER));
+        final HttpHost target = start(null, new Decorator<HttpServerRequestHandler>() {
 
-        final HttpHost target = start();
+            @Override
+            public HttpServerRequestHandler decorate(final HttpServerRequestHandler requestHandler) {
+                return new RedirectingDecorator(
+                        requestHandler,
+                        new OldPathRedirectResolver("/oldlocation", "/echo", HttpStatus.SC_SEE_OTHER));
+            }
+
+        });
 
         final HttpClientContext context = HttpClientContext.create();
 
-        final HttpPost httppost = new HttpPost("/oldlocation/");
+        final HttpPost httppost = new HttpPost("/oldlocation/stuff");
         httppost.setEntity(new StringEntity("stuff"));
 
-        final ClassicHttpResponse response = this.httpclient.execute(target, httppost, context);
-        EntityUtils.consume(response.getEntity());
+        try (final ClassicHttpResponse response = this.httpclient.execute(target, httppost, context)) {
+            final HttpRequest reqWrapper = context.getRequest();
+
+            Assert.assertEquals(HttpStatus.SC_OK, response.getCode());
+            Assert.assertEquals(URIUtils.create(target, "/echo/stuff"), reqWrapper.getUri());
+            Assert.assertEquals("GET", reqWrapper.getMethod());
 
-        final HttpRequest reqWrapper = context.getRequest();
+            EntityUtils.consume(response.getEntity());
+        }
 
-        Assert.assertEquals(HttpStatus.SC_OK, response.getCode());
-        Assert.assertEquals(URIUtils.create(target, "/newlocation/"), reqWrapper.getUri());
-        Assert.assertEquals("GET", reqWrapper.getMethod());
     }
 
     @Test
     public void testRelativeRedirect() throws Exception {
-        this.server.registerHandler("*", new RelativeRedirectService());
+        final HttpHost target = start(null, new Decorator<HttpServerRequestHandler>() {
 
-        final HttpHost target = start();
+            @Override
+            public HttpServerRequestHandler decorate(final HttpServerRequestHandler requestHandler) {
+                return new RedirectingDecorator(
+                        requestHandler,
+                        new RedirectResolver() {
+
+                            @Override
+                            public Redirect resolve(final URI requestUri) throws URISyntaxException {
+                                final String path = requestUri.getPath();
+                                if (path.startsWith("/oldlocation")) {
+                                    return new Redirect(HttpStatus.SC_MOVED_TEMPORARILY, "/random/100");
+
+                                }
+                                return null;
+                            }
+
+                        });
+            }
 
+        });
         final HttpClientContext context = HttpClientContext.create();
 
-        final HttpGet httpget = new HttpGet("/oldlocation/");
+        final HttpGet httpget = new HttpGet("/oldlocation/stuff");
 
-        final ClassicHttpResponse response = this.httpclient.execute(target, httpget, context);
-        EntityUtils.consume(response.getEntity());
+        try (final ClassicHttpResponse response = this.httpclient.execute(target, httpget, context)) {
+            final HttpRequest reqWrapper = context.getRequest();
 
-        final HttpRequest reqWrapper = context.getRequest();
+            Assert.assertEquals(HttpStatus.SC_OK, response.getCode());
+            Assert.assertEquals(URIUtils.create(target, "/random/100"), reqWrapper.getUri());
 
-        Assert.assertEquals(HttpStatus.SC_OK, response.getCode());
-        Assert.assertEquals(URIUtils.create(target, "/relativelocation/"), reqWrapper.getUri());
+            EntityUtils.consume(response.getEntity());
+        }
     }
 
     @Test
     public void testRelativeRedirect2() throws Exception {
-        this.server.registerHandler("*", new RelativeRedirectService2());
+        final HttpHost target = start(null, new Decorator<HttpServerRequestHandler>() {
 
-        final HttpHost target = start();
+            @Override
+            public HttpServerRequestHandler decorate(final HttpServerRequestHandler requestHandler) {
+                return new RedirectingDecorator(
+                        requestHandler,
+                        new RedirectResolver() {
+
+                            @Override
+                            public Redirect resolve(final URI requestUri) throws URISyntaxException {
+                                final String path = requestUri.getPath();
+                                if (path.equals("/random/oldlocation")) {
+                                    return new Redirect(HttpStatus.SC_MOVED_TEMPORARILY, "100");
+
+                                }
+                                return null;
+                            }
+
+                        });
+            }
+
+        });
 
         final HttpClientContext context = HttpClientContext.create();
 
-        final HttpGet httpget = new HttpGet("/test/oldlocation");
+        final HttpGet httpget = new HttpGet("/random/oldlocation");
+
+        try (final ClassicHttpResponse response = this.httpclient.execute(target, httpget, context)) {
+            final HttpRequest reqWrapper = context.getRequest();
 
-        final ClassicHttpResponse response = this.httpclient.execute(target, httpget, context);
-        EntityUtils.consume(response.getEntity());
+            Assert.assertEquals(HttpStatus.SC_OK, response.getCode());
+            Assert.assertEquals(URIUtils.create(target, "/random/100"), reqWrapper.getUri());
 
-        final HttpRequest reqWrapper = context.getRequest();
+            EntityUtils.consume(response.getEntity());
+        }
 
-        Assert.assertEquals(HttpStatus.SC_OK, response.getCode());
-        Assert.assertEquals(URIUtils.create(target, "/test/relativelocation"), reqWrapper.getUri());
     }
 
-    @Test(expected=ClientProtocolException.class)
+    @Test(expected = ClientProtocolException.class)
     public void testRejectBogusRedirectLocation() throws Exception {
-        this.server.registerHandler("*", new TransformingRedirectService(new UriTransformation() {
+        final HttpHost target = start(null, new Decorator<HttpServerRequestHandler>() {
 
             @Override
-            public String rewrite(final URI requestUri) {
-                return "xxx://bogus";
+            public HttpServerRequestHandler decorate(final HttpServerRequestHandler requestHandler) {
+                return new RedirectingDecorator(
+                        requestHandler,
+                        new RedirectResolver() {
+
+                            @Override
+                            public Redirect resolve(final URI requestUri) throws URISyntaxException {
+                                final String path = requestUri.getPath();
+                                if (path.equals("/oldlocation")) {
+                                    return new Redirect(HttpStatus.SC_MOVED_TEMPORARILY, "xxx://bogus");
+
+                                }
+                                return null;
+                            }
+
+                        });
             }
 
-        }));
-
-        final HttpHost target = start();
+        });
 
-        final HttpGet httpget = new HttpGet("/oldlocation/");
+        final HttpGet httpget = new HttpGet("/oldlocation");
 
         try {
             this.httpclient.execute(target, httpget);
@@ -630,19 +552,32 @@ public class TestRedirects extends LocalServerTestBase {
         }
     }
 
-    @Test(expected=ClientProtocolException.class)
+    @Test(expected = ClientProtocolException.class)
     public void testRejectInvalidRedirectLocation() throws Exception {
-        this.server.registerHandler("*", new TransformingRedirectService(new UriTransformation() {
+        final HttpHost target = start(null, new Decorator<HttpServerRequestHandler>() {
 
             @Override
-            public String rewrite(final URI requestUri) {
-                return "/newlocation/?p=I have spaces";
+            public HttpServerRequestHandler decorate(final HttpServerRequestHandler requestHandler) {
+                return new RedirectingDecorator(
+                        requestHandler,
+                        new RedirectResolver() {
+
+                            @Override
+                            public Redirect resolve(final URI requestUri) throws URISyntaxException {
+                                final String path = requestUri.getPath();
+                                if (path.equals("/oldlocation")) {
+                                    return new Redirect(HttpStatus.SC_MOVED_TEMPORARILY, "/newlocation/?p=I have spaces");
+
+                                }
+                                return null;
+                            }
+
+                        });
             }
 
-        }));
-        final HttpHost target = start();
+        });
 
-        final HttpGet httpget = new HttpGet("/oldlocation/");
+        final HttpGet httpget = new HttpGet("/oldlocation");
 
         try {
             this.httpclient.execute(target, httpget);
@@ -654,9 +589,16 @@ public class TestRedirects extends LocalServerTestBase {
 
     @Test
     public void testRedirectWithCookie() throws Exception {
-        this.server.registerHandler("*", new BasicRedirectService());
+        final HttpHost target = start(null, new Decorator<HttpServerRequestHandler>() {
 
-        final HttpHost target = start();
+            @Override
+            public HttpServerRequestHandler decorate(final HttpServerRequestHandler requestHandler) {
+                return new RedirectingDecorator(
+                        requestHandler,
+                        new OldPathRedirectResolver("/oldlocation", "/random", HttpStatus.SC_MOVED_TEMPORARILY));
+            }
+
+        });
 
         final CookieStore cookieStore = new BasicCookieStore();
 
@@ -668,43 +610,51 @@ public class TestRedirects extends LocalServerTestBase {
 
         final HttpClientContext context = HttpClientContext.create();
         context.setCookieStore(cookieStore);
-        final HttpGet httpget = new HttpGet("/oldlocation/");
+        final HttpGet httpget = new HttpGet("/oldlocation/100");
 
-        final ClassicHttpResponse response = this.httpclient.execute(target, httpget, context);
-        EntityUtils.consume(response.getEntity());
+        try (final ClassicHttpResponse response = this.httpclient.execute(target, httpget, context)) {
+            final HttpRequest reqWrapper = context.getRequest();
 
-        final HttpRequest reqWrapper = context.getRequest();
+            Assert.assertEquals(HttpStatus.SC_OK, response.getCode());
+            Assert.assertEquals(URIUtils.create(target, "/random/100"), reqWrapper.getUri());
 
-        Assert.assertEquals(HttpStatus.SC_OK, response.getCode());
-        Assert.assertEquals(URIUtils.create(target, "/newlocation/"), reqWrapper.getUri());
+            final Header[] headers = reqWrapper.getHeaders("Cookie");
+            Assert.assertEquals("There can only be one (cookie)", 1, headers.length);
 
-        final Header[] headers = reqWrapper.getHeaders("Cookie");
-        Assert.assertEquals("There can only be one (cookie)", 1, headers.length);
+            EntityUtils.consume(response.getEntity());
+        }
     }
 
     @Test
     public void testDefaultHeadersRedirect() throws Exception {
         this.clientBuilder.setDefaultHeaders(Arrays.asList(new BasicHeader(HttpHeaders.USER_AGENT, "my-test-client")));
 
-        this.server.registerHandler("*", new BasicRedirectService());
+        final HttpHost target = start(null, new Decorator<HttpServerRequestHandler>() {
 
-        final HttpHost target = start();
+            @Override
+            public HttpServerRequestHandler decorate(final HttpServerRequestHandler requestHandler) {
+                return new RedirectingDecorator(
+                        requestHandler,
+                        new OldPathRedirectResolver("/oldlocation", "/random", HttpStatus.SC_MOVED_TEMPORARILY));
+            }
 
-        final HttpClientContext context = HttpClientContext.create();
+        });
 
-        final HttpGet httpget = new HttpGet("/oldlocation/");
+        final HttpClientContext context = HttpClientContext.create();
 
+        final HttpGet httpget = new HttpGet("/oldlocation/100");
 
-        final ClassicHttpResponse response = this.httpclient.execute(target, httpget, context);
-        EntityUtils.consume(response.getEntity());
+        try (final ClassicHttpResponse response = this.httpclient.execute(target, httpget, context)) {
+            final HttpRequest reqWrapper = context.getRequest();
 
-        final HttpRequest reqWrapper = context.getRequest();
+            Assert.assertEquals(HttpStatus.SC_OK, response.getCode());
+            Assert.assertEquals(URIUtils.create(target, "/random/100"), reqWrapper.getUri());
 
-        Assert.assertEquals(HttpStatus.SC_OK, response.getCode());
-        Assert.assertEquals(URIUtils.create(target, "/newlocation/"), reqWrapper.getUri());
+            final Header header = reqWrapper.getFirstHeader(HttpHeaders.USER_AGENT);
+            Assert.assertEquals("my-test-client", header.getValue());
 
-        final Header header = reqWrapper.getFirstHeader(HttpHeaders.USER_AGENT);
-        Assert.assertEquals("my-test-client", header.getValue());
+            EntityUtils.consume(response.getEntity());
+        }
     }
 
 }