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 12:43:58 UTC

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

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 cbca2ef  Rewrite of redirect integration test cases
     new 5939ce0  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   (cbca2ef)
            \
             N -- N -- N   refs/heads/development (5939ce0)

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:
 .../async/AbstractHttpAsyncRedirectsTest.java      | 75 ++++++++++++++++
 .../testing/async/TestHttp1AsyncRedirects.java     | 99 +++++++---------------
 2 files changed, 105 insertions(+), 69 deletions(-)


[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 5939ce0ba4ccdc47190ed6e09420cf916d8d9e9c
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      | 638 ++++++-----------
 .../testing/async/TestHttp1AsyncRedirects.java     |  99 +--
 .../hc/client5/testing/sync/TestRedirects.java     | 772 ++++++++++-----------
 8 files changed, 1014 insertions(+), 892 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..1857229 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
@@ -29,8 +29,6 @@ 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 +43,11 @@ 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.OldPathRedirectResolver;
 import org.apache.hc.client5.testing.SSLTestContexts;
+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,14 +60,14 @@ 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.reactive.ReactiveServerExchangeHandler;
 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.testing.reactive.ReactiveRandomProcessor;
 import org.apache.hc.core5.util.TimeValue;
 import org.junit.Assert;
 import org.junit.Test;
@@ -88,132 +90,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 +125,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 +266,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 +297,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 +350,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 +378,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 +400,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 +519,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 +554,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 +576,84 @@ 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>() {
+            secondServer.register("/random/*", new Supplier<AsyncServerExchangeHandler>() {
 
                 @Override
                 public AsyncServerExchangeHandler get() {
-                    return new CrossSiteRedirectService(redirectTarget);
+                    if (isReactive()) {
+                        return new ReactiveServerExchangeHandler(new ReactiveRandomProcessor());
+                    } else {
+                        return new AsyncRandomHandler();
+                    }
                 }
 
             });
-
+            final InetSocketAddress address2;
             if (version.greaterEquals(HttpVersion.HTTP_2)) {
-                secondServer.start(H2Config.DEFAULT);
+                address2 = secondServer.start(H2Config.DEFAULT);
             } else {
-                secondServer.start(Http1Config.DEFAULT);
+                address2 = 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 HttpHost redirectTarget = 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));
-        }
-    }
+            final HttpHost target = start(new Decorator<AsyncServerExchangeHandler>() {
 
-    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;
+                @Override
+                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")) {
+                                        final URI location = new URIBuilder(requestUri)
+                                                .setHttpHost(redirectTarget)
+                                                .setPath("/random/100")
+                                                .build();
+                                        return new Redirect(HttpStatus.SC_MOVED_PERMANENTLY, location.toString());
+                                    }
+                                    return null;
+                                }
+
+                            });
                 }
-            } 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 HttpClientContext context = HttpClientContext.create();
+            final Future<SimpleHttpResponse> future = httpclient.execute(
+                    SimpleHttpRequests.get(target, "/oldlocation"), context, null);
+            final HttpResponse response = future.get();
+            Assert.assertNotNull(response);
 
+            final HttpRequest request = context.getRequest();
 
-        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()));
+            Assert.assertEquals(HttpStatus.SC_OK, response.getCode());
+            Assert.assertEquals("/random/100", request.getRequestUri());
+            Assert.assertEquals(redirectTarget, new HttpHost(request.getScheme(), request.getAuthority()));
+        } finally {
+            server.shutdown(TimeValue.ofSeconds(5));
+        }
     }
 
 }
diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp1AsyncRedirects.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp1AsyncRedirects.java
index 0e22390..ee72da9 100644
--- a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp1AsyncRedirects.java
+++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestHttp1AsyncRedirects.java
@@ -26,15 +26,12 @@
  */
 package org.apache.hc.client5.testing.async;
 
-import java.net.URI;
-import java.net.URISyntaxException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
 import java.util.concurrent.Future;
 
-import org.apache.hc.client5.http.async.methods.SimpleHttpRequest;
 import org.apache.hc.client5.http.async.methods.SimpleHttpRequests;
 import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
 import org.apache.hc.client5.http.config.RequestConfig;
@@ -44,23 +41,20 @@ import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManager;
 import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBuilder;
 import org.apache.hc.client5.http.protocol.HttpClientContext;
 import org.apache.hc.client5.http.ssl.DefaultClientTlsStrategy;
+import org.apache.hc.client5.testing.OldPathRedirectResolver;
 import org.apache.hc.client5.testing.SSLTestContexts;
-import org.apache.hc.core5.function.Supplier;
-import org.apache.hc.core5.http.ContentType;
+import org.apache.hc.client5.testing.redirect.Redirect;
+import org.apache.hc.core5.function.Decorator;
 import org.apache.hc.core5.http.Header;
-import org.apache.hc.core5.http.HttpException;
 import org.apache.hc.core5.http.HttpHeaders;
 import org.apache.hc.core5.http.HttpHost;
 import org.apache.hc.core5.http.HttpRequest;
 import org.apache.hc.core5.http.HttpResponse;
 import org.apache.hc.core5.http.HttpStatus;
 import org.apache.hc.core5.http.HttpVersion;
-import org.apache.hc.core5.http.ProtocolException;
 import org.apache.hc.core5.http.URIScheme;
 import org.apache.hc.core5.http.message.BasicHeader;
 import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler;
-import org.apache.hc.core5.http.protocol.HttpCoreContext;
-import org.apache.hc.core5.net.URIBuilder;
 import org.junit.Assert;
 import org.junit.Rule;
 import org.junit.Test;
@@ -129,54 +123,19 @@ public class TestHttp1AsyncRedirects extends AbstractHttpAsyncRedirectsTest<Clos
         return clientBuilder.build();
     }
 
-    static class NoKeepAliveRedirectService extends AbstractSimpleServerExchangeHandler {
-
-        private final int statuscode;
-
-        public NoKeepAliveRedirectService(final int statuscode) {
-            super();
-            this.statuscode = statuscode;
-        }
-
-        @Override
-        protected SimpleHttpResponse handle(
-                final SimpleHttpRequest request, final HttpCoreContext context) throws HttpException {
-            try {
-                final URI requestURI = request.getUri();
-                final String path = requestURI.getPath();
-                if (path.equals("/oldlocation/")) {
-                    final SimpleHttpResponse response = new SimpleHttpResponse(statuscode);
-                    response.addHeader(new BasicHeader("Location",
-                            new URIBuilder(requestURI).setPath("/newlocation/").build()));
-                    response.addHeader(new BasicHeader("Connection", "close"));
-                    return response;
-                } else if (path.equals("/newlocation/")) {
-                    final SimpleHttpResponse response = new SimpleHttpResponse(HttpStatus.SC_OK);
-                    response.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);
-            }
-        }
-
-    }
-
-    @Override
     @Test
-    public void testBasicRedirect300() throws Exception {
-        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
+    public void testBasicRedirect300NoKeepAlive() throws Exception {
+        final HttpHost target = start(new Decorator<AsyncServerExchangeHandler>() {
 
             @Override
-            public AsyncServerExchangeHandler get() {
-                return new NoKeepAliveRedirectService(HttpStatus.SC_MULTIPLE_CHOICES);
+            public AsyncServerExchangeHandler decorate(final AsyncServerExchangeHandler exchangeHandler) {
+                return new RedirectingAsyncDecorator(
+                        exchangeHandler,
+                        new OldPathRedirectResolver("/oldlocation", "/random", HttpStatus.SC_MULTIPLE_CHOICES,
+                                Redirect.ConnControl.CLOSE));
             }
 
         });
-        final HttpHost target = start();
-
         final HttpClientContext context = HttpClientContext.create();
         final Future<SimpleHttpResponse> future = httpclient.execute(
                 SimpleHttpRequests.get(target, "/oldlocation/"), context, null);
@@ -191,57 +150,59 @@ public class TestHttp1AsyncRedirects extends AbstractHttpAsyncRedirectsTest<Clos
 
     @Test
     public void testBasicRedirect301NoKeepAlive() throws Exception {
-        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
+        final HttpHost target = start(new Decorator<AsyncServerExchangeHandler>() {
 
             @Override
-            public AsyncServerExchangeHandler get() {
-                return new NoKeepAliveRedirectService(HttpStatus.SC_MOVED_PERMANENTLY);
+            public AsyncServerExchangeHandler decorate(final AsyncServerExchangeHandler exchangeHandler) {
+                return new RedirectingAsyncDecorator(
+                        exchangeHandler,
+                        new OldPathRedirectResolver("/oldlocation", "/random", HttpStatus.SC_MOVED_PERMANENTLY,
+                                Redirect.ConnControl.CLOSE));
             }
 
         });
-
-        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 testDefaultHeadersRedirect() throws Exception {
-        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
+        final List<Header> defaultHeaders = new ArrayList<>(1);
+        defaultHeaders.add(new BasicHeader(HttpHeaders.USER_AGENT, "my-test-client"));
+        clientBuilder.setDefaultHeaders(defaultHeaders);
+
+        final HttpHost target = start(new Decorator<AsyncServerExchangeHandler>() {
 
             @Override
-            public AsyncServerExchangeHandler get() {
-                return new NoKeepAliveRedirectService(HttpStatus.SC_MOVED_TEMPORARILY);
+            public AsyncServerExchangeHandler decorate(final AsyncServerExchangeHandler exchangeHandler) {
+                return new RedirectingAsyncDecorator(
+                        exchangeHandler,
+                        new OldPathRedirectResolver("/oldlocation", "/random", HttpStatus.SC_MOVED_PERMANENTLY,
+                                Redirect.ConnControl.CLOSE));
             }
 
         });
 
-        final List<Header> defaultHeaders = new ArrayList<>(1);
-        defaultHeaders.add(new BasicHeader(HttpHeaders.USER_AGENT, "my-test-client"));
-        clientBuilder.setDefaultHeaders(defaultHeaders);
-
-        final HttpHost target = start();
-
         final HttpClientContext context = HttpClientContext.create();
 
         final Future<SimpleHttpResponse> future = httpclient.execute(
-                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());
 
         final Header header = request.getFirstHeader(HttpHeaders.USER_AGENT);
         Assert.assertEquals("my-test-client", header.getValue());
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());
+        }
     }
 
 }