Rewrite of redirect integration test cases
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 @@
 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.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.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 @@
         }
     }
 
-    static class BasicRedirectService extends AbstractSimpleServerExchangeHandler {
-
-        private final int statuscode;
-
-        public BasicRedirectService(final int statuscode) {
-            super();
-            this.statuscode = statuscode;
+    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);
         }
-
-        @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);
-            }
-        }
-
     }
 
     @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 @@
 
     @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() {
+            public AsyncServerExchangeHandler decorate(final AsyncServerExchangeHandler exchangeHandler) {
+                return new RedirectingAsyncDecorator(
+                        exchangeHandler,
+                        new RedirectResolver() {
 
-                    @Override
-                    protected SimpleHttpResponse handle(
-                            final SimpleHttpRequest request, final HttpCoreContext context) throws HttpException {
-                        return new SimpleHttpResponse(HttpStatus.SC_MOVED_TEMPORARILY);
-                    }
+                            @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 @@
 
     @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 @@
 
     @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 @@
 
     @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 @@
 
     @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 @@
         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 @@
 
     @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 @@
 
     @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 @@
         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)) {
+                address2 = secondServer.start(H2Config.DEFAULT);
+            } else {
+                address2 = secondServer.start(Http1Config.DEFAULT);
+            }
+            final HttpHost redirectTarget = new HttpHost(scheme.name(), "localhost", address2.getPort());
+
+            final HttpHost target = start(new Decorator<AsyncServerExchangeHandler>() {
+
+                @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;
+                                }
+
+                            });
                 }
 
             });
 
-            if (version.greaterEquals(HttpVersion.HTTP_2)) {
-                secondServer.start(H2Config.DEFAULT);
-            } else {
-                secondServer.start(Http1Config.DEFAULT);
-            }
-            final Future<ListenerEndpoint> endpointFuture = secondServer.listen(new InetSocketAddress(0));
-            final ListenerEndpoint endpoint2 = endpointFuture.get();
+            final 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 InetSocketAddress address2 = (InetSocketAddress) endpoint2.getAddress();
-            final HttpHost initialTarget = new HttpHost(scheme.name(), "localhost", address2.getPort());
+            final HttpRequest request = context.getRequest();
 
-            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());
-            }
+            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));
         }
     }
 
-    private static class RomeRedirectService extends AbstractSimpleServerExchangeHandler {
-
-        @Override
-        protected SimpleHttpResponse handle(
-                final SimpleHttpRequest request, final HttpCoreContext context) throws HttpException {
-            try {
-                final URI requestURI = request.getUri();
-                final String path = requestURI.getPath();
-                if (path.equals("/rome")) {
-                    final SimpleHttpResponse response = new SimpleHttpResponse(HttpStatus.SC_OK);
-                    response.setBody("Successful redirect", ContentType.TEXT_PLAIN);
-                    return response;
-                } else {
-                    final SimpleHttpResponse response = new SimpleHttpResponse(HttpStatus.SC_MOVED_TEMPORARILY);
-                    response.addHeader(new BasicHeader("Location", "/rome"));
-                    return response;
-                }
-            } catch (final URISyntaxException ex) {
-                throw new ProtocolException(ex.getMessage(), ex);
-            }
-        }
-
-    }
-
-    @Test
-    public void testRepeatRequest() throws Exception {
-        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
-
-            @Override
-            public AsyncServerExchangeHandler get() {
-                return new RomeRedirectService();
-            }
-
-        });
-        final HttpHost target = start();
-
-        final HttpClientContext context = HttpClientContext.create();
-
-        final Future<SimpleHttpResponse> future1 = httpclient.execute(
-                SimpleHttpRequests.get(target, "/rome"), context, null);
-        final HttpResponse response1 = future1.get();
-        Assert.assertNotNull(response1);
-
-        final Future<SimpleHttpResponse> future2 = httpclient.execute(
-                SimpleHttpRequests.get(target, "/rome"), context, null);
-        final HttpResponse response2 = future2.get();
-        Assert.assertNotNull(response2);
-
-        final HttpRequest request = context.getRequest();
-
-        Assert.assertEquals(HttpStatus.SC_OK, response2.getCode());
-        Assert.assertEquals("/rome", request.getRequestUri());
-        Assert.assertEquals(target, new HttpHost(request.getScheme(), request.getAuthority()));
-    }
-
-    @Test
-    public void testRepeatRequestRedirect() throws Exception {
-        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
-
-            @Override
-            public AsyncServerExchangeHandler get() {
-                return new RomeRedirectService();
-            }
-
-        });
-        final HttpHost target = start();
-
-        final HttpClientContext context = HttpClientContext.create();
-
-        final Future<SimpleHttpResponse> future1 = httpclient.execute(
-                SimpleHttpRequests.get(target, "/lille"), context, null);
-        final HttpResponse response1 = future1.get();
-        Assert.assertNotNull(response1);
-
-        final Future<SimpleHttpResponse> future2 = httpclient.execute(
-                SimpleHttpRequests.get(target, "/lille"), context, null);
-        final HttpResponse response2 = future2.get();
-        Assert.assertNotNull(response2);
-
-        final HttpRequest request = context.getRequest();
-
-        Assert.assertEquals(HttpStatus.SC_OK, response2.getCode());
-        Assert.assertEquals("/rome", request.getRequestUri());
-        Assert.assertEquals(target, new HttpHost(request.getScheme(), request.getAuthority()));
-    }
-
-    @Test
-    public void testDifferentRequestSameRedirect() throws Exception {
-        server.register("*", new Supplier<AsyncServerExchangeHandler>() {
-
-            @Override
-            public AsyncServerExchangeHandler get() {
-                return new RomeRedirectService();
-            }
-
-        });
-        final HttpHost target = start();
-
-        final HttpClientContext context = HttpClientContext.create();
-
-        final Future<SimpleHttpResponse> future1 = httpclient.execute(
-                SimpleHttpRequests.get(target, "/alian"), context, null);
-        final HttpResponse response1 = future1.get();
-        Assert.assertNotNull(response1);
-
-        final Future<SimpleHttpResponse> future2 = httpclient.execute(
-                SimpleHttpRequests.get(target, "/lille"), context, null);
-        final HttpResponse response2 = future2.get();
-        Assert.assertNotNull(response2);
-
-
-        final HttpRequest request = context.getRequest();
-
-        Assert.assertEquals(HttpStatus.SC_OK, response2.getCode());
-        Assert.assertEquals("/rome", request.getRequestUri());
-        Assert.assertEquals(target, new HttpHost(request.getScheme(), request.getAuthority()));
-    }
-
 }
diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/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.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 @@
         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 @@
 
     @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>() {
-
-            @Override
-            public AsyncServerExchangeHandler get() {
-                return new NoKeepAliveRedirectService(HttpStatus.SC_MOVED_TEMPORARILY);
-            }
-
-        });
-
         final List<Header> defaultHeaders = new ArrayList<>(1);
         defaultHeaders.add(new BasicHeader(HttpHeaders.USER_AGENT, "my-test-client"));
         clientBuilder.setDefaultHeaders(defaultHeaders);
 
-        final HttpHost target = start();
+        final HttpHost target = start(new Decorator<AsyncServerExchangeHandler>() {
+
+            @Override
+            public AsyncServerExchangeHandler decorate(final AsyncServerExchangeHandler exchangeHandler) {
+                return new RedirectingAsyncDecorator(
+                        exchangeHandler,
+                        new OldPathRedirectResolver("/oldlocation", "/random", HttpStatus.SC_MOVED_PERMANENTLY,
+                                Redirect.ConnControl.CLOSE));
+            }
+
+        });
 
         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.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.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,282 +71,201 @@
  */
 public class TestRedirects extends LocalServerTestBase {
 
-    private static class BasicRedirectService implements HttpRequestHandler {
+    @Test
+    public void testBasicRedirect300() throws Exception {
+        final HttpHost target = start(null, new Decorator<HttpServerRequestHandler>() {
 
-        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);
+            @Override
+            public HttpServerRequestHandler decorate(final HttpServerRequestHandler requestHandler) {
+                return new RedirectingDecorator(
+                        requestHandler,
+                        new OldPathRedirectResolver("/oldlocation", "/random", HttpStatus.SC_MULTIPLE_CHOICES));
             }
-        }
 
-    }
+        });
 
-    private static class CircularRedirectService implements HttpRequestHandler {
+        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();
 
-        public CircularRedirectService() {
-            super();
-        }
+            Assert.assertEquals(HttpStatus.SC_MULTIPLE_CHOICES, response.getCode());
+            Assert.assertEquals(URIUtils.create(target, "/oldlocation/100"), reqWrapper.getUri());
 
-        @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);
-            }
-        }
-    }
+            final RedirectLocations redirects = context.getRedirectLocations();
+            Assert.assertNotNull(redirects);
+            Assert.assertEquals(0, redirects.size());
 
-    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);
-            }
-        }
-    }
-
-    private static class RomeRedirectService implements HttpRequestHandler {
-
-        public RomeRedirectService() {
-            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("/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);
-            }
-        }
-    }
-
-    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;
-        }
-
-        @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);
-            }
+            EntityUtils.consume(response.getEntity());
         }
     }
 
     @Test
-    public void testBasicRedirect300() throws Exception {
-        this.server.registerHandler("*", new BasicRedirectService(HttpStatus.SC_MULTIPLE_CHOICES));
+    public void testBasicRedirect300NoKeepAlive() throws Exception {
+        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_MULTIPLE_CHOICES,
+                                Redirect.ConnControl.CLOSE));
+            }
+
+        });
 
         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/");
+            Assert.assertEquals(HttpStatus.SC_MULTIPLE_CHOICES, response.getCode());
+            Assert.assertEquals(URIUtils.create(target, "/oldlocation/100"), reqWrapper.getUri());
 
-        final ClassicHttpResponse response = this.httpclient.execute(target, httpget, context);
-        EntityUtils.consume(response.getEntity());
+            final RedirectLocations redirects = context.getRedirectLocations();
+            Assert.assertNotNull(redirects);
+            Assert.assertEquals(0, redirects.size());
 
-        final HttpRequest reqWrapper = context.getRequest();
-
-        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 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();
+
+            Assert.assertEquals(HttpStatus.SC_MOVED_TEMPORARILY, response.getCode());
+            Assert.assertEquals("/oldlocation/100", reqWrapper.getRequestUri());
+
+            EntityUtils.consume(response.getEntity());
+        }
+    }
+
+    @Test
+    public void testBasicRedirect303() throws Exception {
+        final HttpHost target = start(null, new Decorator<HttpServerRequestHandler>() {
+
+            @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/123");
+
+        try (final ClassicHttpResponse response = this.httpclient.execute(target, httpget, context)) {
+            final HttpRequest reqWrapper = context.getRequest();
+
+            Assert.assertEquals(HttpStatus.SC_OK, response.getCode());
+            Assert.assertEquals(URIUtils.create(target, "/random/123"), reqWrapper.getUri());
+
+            EntityUtils.consume(response.getEntity());
+        }
+    }
+
+    @Test
+    public void testBasicRedirect304() throws Exception {
+        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");
             }
 
         });
@@ -350,104 +274,103 @@
 
         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_MOVED_TEMPORARILY, response.getCode());
-        Assert.assertEquals("/oldlocation/", reqWrapper.getRequestUri());
-    }
+            final RedirectLocations redirects = context.getRedirectLocations();
+            Assert.assertNotNull(redirects);
+            Assert.assertEquals(0, redirects.size());
 
-    @Test
-    public void testBasicRedirect303() throws Exception {
-        this.server.registerHandler("*", new BasicRedirectService(HttpStatus.SC_SEE_OTHER));
-
-        final HttpHost target = start();
-
-        final HttpClientContext context = HttpClientContext.create();
-
-        final HttpGet httpget = new HttpGet("/oldlocation/");
-
-        final ClassicHttpResponse response = this.httpclient.execute(target, httpget, context);
-        EntityUtils.consume(response.getEntity());
-
-        final HttpRequest reqWrapper = context.getRequest();
-
-        Assert.assertEquals(HttpStatus.SC_OK, response.getCode());
-        Assert.assertEquals(URIUtils.create(target, "/newlocation/"), reqWrapper.getUri());
-    }
-
-    @Test
-    public void testBasicRedirect304() throws Exception {
-        this.server.registerHandler("*", new BasicRedirectService(HttpStatus.SC_NOT_MODIFIED));
-
-        final HttpHost target = start();
-
-        final HttpClientContext context = HttpClientContext.create();
-
-        final HttpGet httpget = new HttpGet("/oldlocation/");
-
-        final ClassicHttpResponse response = this.httpclient.execute(target, httpget, context);
-        EntityUtils.consume(response.getEntity());
-
-        final HttpRequest reqWrapper = context.getRequest();
-
-        Assert.assertEquals(HttpStatus.SC_NOT_MODIFIED, response.getCode());
-        Assert.assertEquals(URIUtils.create(target, "/oldlocation/"), reqWrapper.getUri());
+            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");
 
-        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_USE_PROXY, response.getCode());
+            Assert.assertEquals(URIUtils.create(target, "/oldlocation/stuff"), reqWrapper.getUri());
 
-        Assert.assertEquals(HttpStatus.SC_USE_PROXY, 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 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 @@
         }
     }
 
-    @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 @@
     }
 
     @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();
 
-        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());
 
-        Assert.assertEquals(HttpStatus.SC_OK, response.getCode());
-        Assert.assertEquals(URIUtils.create(target, "/newlocation/"), reqWrapper.getUri());
-        Assert.assertEquals("GET", reqWrapper.getMethod());
+            EntityUtils.consume(response.getEntity());
+        }
+
     }
 
     @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");
 
-        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, "/test/relativelocation"), reqWrapper.getUri());
+            EntityUtils.consume(response.getEntity());
+        }
+
     }
 
-    @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 @@
         }
     }
 
-    @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 @@
 
     @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 @@
 
         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 HttpGet httpget = new HttpGet("/oldlocation/100");
 
+        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();
+            final Header header = reqWrapper.getFirstHeader(HttpHeaders.USER_AGENT);
+            Assert.assertEquals("my-test-client", header.getValue());
 
-        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());
+            EntityUtils.consume(response.getEntity());
+        }
     }
 
 }