You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hc.apache.org by ol...@apache.org on 2016/10/31 17:33:33 UTC

svn commit: r1767339 [6/14] - in /httpcomponents/httpcore/trunk: ./ httpcore5-ab/src/main/java/org/apache/hc/core5/http/benchmark/ httpcore5-ab/src/test/java/org/apache/hc/core5/http/benchmark/ httpcore5-h2/src/main/java/org/apache/hc/core5/http2/boots...

Modified: httpcomponents/httpcore/trunk/httpcore5/src/examples/org/apache/hc/core5/http/examples/NHttpClient.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/trunk/httpcore5/src/examples/org/apache/hc/core5/http/examples/NHttpClient.java?rev=1767339&r1=1767338&r2=1767339&view=diff
==============================================================================
--- httpcomponents/httpcore/trunk/httpcore5/src/examples/org/apache/hc/core5/http/examples/NHttpClient.java (original)
+++ httpcomponents/httpcore/trunk/httpcore5/src/examples/org/apache/hc/core5/http/examples/NHttpClient.java Mon Oct 31 17:33:27 2016
@@ -26,133 +26,86 @@
  */
 package org.apache.hc.core5.http.examples;
 
-import java.io.IOException;
-import java.io.InterruptedIOException;
+import java.net.URI;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
 
 import org.apache.hc.core5.concurrent.FutureCallback;
-import org.apache.hc.core5.http.ClassicHttpRequest;
-import org.apache.hc.core5.http.ClassicHttpResponse;
 import org.apache.hc.core5.http.HttpHost;
-import org.apache.hc.core5.http.config.ConnectionConfig;
-import org.apache.hc.core5.http.impl.nio.BasicAsyncRequestProducer;
-import org.apache.hc.core5.http.impl.nio.BasicAsyncResponseConsumer;
-import org.apache.hc.core5.http.impl.nio.DefaultHttpClientIOEventHandlerFactory;
-import org.apache.hc.core5.http.impl.nio.HttpAsyncRequestExecutor;
-import org.apache.hc.core5.http.impl.nio.HttpAsyncRequester;
-import org.apache.hc.core5.http.message.BasicClassicHttpRequest;
-import org.apache.hc.core5.http.pool.nio.BasicNIOConnPool;
+import org.apache.hc.core5.http.HttpResponse;
+import org.apache.hc.core5.http.Message;
+import org.apache.hc.core5.http.impl.nio.bootstrap.ClientEndpoint;
+import org.apache.hc.core5.http.impl.nio.bootstrap.HttpAsyncRequester;
+import org.apache.hc.core5.http.impl.nio.bootstrap.RequesterBootstrap;
+import org.apache.hc.core5.http.nio.BasicRequestProducer;
+import org.apache.hc.core5.http.nio.BasicResponseConsumer;
+import org.apache.hc.core5.http.nio.command.ShutdownType;
+import org.apache.hc.core5.http.nio.entity.StringAsyncEntityConsumer;
 import org.apache.hc.core5.http.protocol.HttpCoreContext;
-import org.apache.hc.core5.http.protocol.HttpProcessor;
-import org.apache.hc.core5.http.protocol.HttpProcessorBuilder;
-import org.apache.hc.core5.http.protocol.RequestConnControl;
-import org.apache.hc.core5.http.protocol.RequestContent;
-import org.apache.hc.core5.http.protocol.RequestExpectContinue;
-import org.apache.hc.core5.http.protocol.RequestTargetHost;
-import org.apache.hc.core5.http.protocol.RequestUserAgent;
-import org.apache.hc.core5.reactor.ConnectingIOReactor;
-import org.apache.hc.core5.reactor.DefaultConnectingIOReactor;
-import org.apache.hc.core5.reactor.IOEventHandlerFactory;
-import org.apache.hc.core5.reactor.IOReactorConfig;
 
 /**
- * Minimal asynchronous HTTP/1.1 client.
- * <p>
- * Please note that this example represents a minimal HTTP client implementation.
- * It does not support HTTPS as is.
- * You either need to provide BasicNIOConnPool with a connection factory
- * that supports SSL or use a more complex HttpAsyncClient.
+ * Asynchronous HTTP/1.1 request executor.
  */
 public class NHttpClient {
 
     public static void main(String[] args) throws Exception {
-        // Create HTTP protocol processing chain
-        HttpProcessor httpproc = HttpProcessorBuilder.create()
-                // Use standard client-side protocol interceptors
-                .add(new RequestContent())
-                .add(new RequestTargetHost())
-                .add(new RequestConnControl())
-                .add(new RequestUserAgent("Test/1.1"))
-                .add(new RequestExpectContinue()).build();
-        // Create client-side HTTP protocol handler
-        HttpAsyncRequestExecutor protocolHandler = new HttpAsyncRequestExecutor();
-        // Create client-side I/O event handler factory
-        final IOEventHandlerFactory eventHandlerFactory = new DefaultHttpClientIOEventHandlerFactory(
-                protocolHandler,
-                ConnectionConfig.DEFAULT);
-        // Create client-side I/O reactor
-        final ConnectingIOReactor ioReactor = new DefaultConnectingIOReactor(
-                eventHandlerFactory,
-                IOReactorConfig.DEFAULT);
-        // Create HTTP connection pool
-        BasicNIOConnPool pool = new BasicNIOConnPool(ioReactor);
-        // Limit total number of connections to just two
-        pool.setDefaultMaxPerRoute(2);
-        pool.setMaxTotal(2);
-        // Run the I/O reactor in a separate thread
-        Thread t = new Thread(new Runnable() {
 
+        // Create and start requester
+        final HttpAsyncRequester requester = RequesterBootstrap.bootstrap().create();
+        Runtime.getRuntime().addShutdownHook(new Thread() {
             @Override
             public void run() {
-                try {
-                    // Ready to go!
-                    ioReactor.execute();
-                } catch (InterruptedIOException ex) {
-                    System.err.println("Interrupted");
-                } catch (IOException e) {
-                    System.err.println("I/O error: " + e.getMessage());
-                }
-                System.out.println("Shutdown");
+                System.out.println("HTTP requester shutting down");
+                requester.shutdown(3, TimeUnit.SECONDS);
             }
-
         });
-        // Start the client thread
-        t.start();
-        // Create HTTP requester
-        HttpAsyncRequester requester = new HttpAsyncRequester(httpproc);
+        requester.start();
+
         // Execute HTTP GETs to the following hosts and
         HttpHost[] targets = new HttpHost[] {
                 new HttpHost("www.apache.org", 80, "http"),
-                new HttpHost("www.verisign.com", 443, "https"),
-                new HttpHost("www.google.com", 80, "http")
+                new HttpHost("hc.apache.org", 80, "http")
         };
+
         final CountDownLatch latch = new CountDownLatch(targets.length);
         for (final HttpHost target: targets) {
-            ClassicHttpRequest request = new BasicClassicHttpRequest("GET", "/");
+            final Future<ClientEndpoint> future = requester.connect(target, 5, TimeUnit.SECONDS);
+            final ClientEndpoint clientEndpoint = future.get();
             HttpCoreContext coreContext = HttpCoreContext.create();
-            requester.execute(
-                    new BasicAsyncRequestProducer(target, request),
-                    new BasicAsyncResponseConsumer(),
-                    pool,
-                    coreContext,
-                    // Handle HTTP response from a callback
-                    new FutureCallback<ClassicHttpResponse>() {
-
-                @Override
-                public void completed(final ClassicHttpResponse response) {
-                    latch.countDown();
-                    System.out.println(target + "->" + response.getCode());
-                }
-
-                @Override
-                public void failed(final Exception ex) {
-                    latch.countDown();
-                    System.out.println(target + "->" + ex);
-                }
-
-                @Override
-                public void cancelled() {
-                    latch.countDown();
-                    System.out.println(target + " cancelled");
-                }
+            clientEndpoint.execute(
+                    new BasicRequestProducer("GET", URI.create("/")),
+                    new BasicResponseConsumer<>(new StringAsyncEntityConsumer()),
+                    coreContext, new FutureCallback<Message<HttpResponse, String>>() {
+
+                        @Override
+                        public void completed(final Message<HttpResponse, String> message) {
+                            latch.countDown();
+                            clientEndpoint.shutdown(ShutdownType.IMMEDIATE);
+                            HttpResponse response = message.getHead();
+                            System.out.println(target + "->" + response.getCode());
+                        }
+
+                        @Override
+                        public void failed(final Exception ex) {
+                            latch.countDown();
+                            clientEndpoint.shutdown(ShutdownType.IMMEDIATE);
+                            System.out.println(target + "->" + ex);
+                        }
+
+                        @Override
+                        public void cancelled() {
+                            latch.countDown();
+                            clientEndpoint.shutdown(ShutdownType.IMMEDIATE);
+                            System.out.println(target + " cancelled");
+                        }
 
-            });
+                    });
         }
+
         latch.await();
         System.out.println("Shutting down I/O reactor");
-        ioReactor.shutdown(3, TimeUnit.SECONDS);
-        System.out.println("Done");
+        requester.initiateShutdown();
     }
 
 }

Modified: httpcomponents/httpcore/trunk/httpcore5/src/examples/org/apache/hc/core5/http/examples/NHttpFileServer.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/trunk/httpcore5/src/examples/org/apache/hc/core5/http/examples/NHttpFileServer.java?rev=1767339&r1=1767338&r2=1767339&view=diff
==============================================================================
--- httpcomponents/httpcore/trunk/httpcore5/src/examples/org/apache/hc/core5/http/examples/NHttpFileServer.java (original)
+++ httpcomponents/httpcore/trunk/httpcore5/src/examples/org/apache/hc/core5/http/examples/NHttpFileServer.java Mon Oct 31 17:33:27 2016
@@ -28,38 +28,40 @@ package org.apache.hc.core5.http.example
 
 import java.io.File;
 import java.io.IOException;
-import java.net.URL;
-import java.net.URLDecoder;
+import java.net.InetSocketAddress;
+import java.net.SocketTimeoutException;
+import java.net.URI;
+import java.net.URISyntaxException;
 import java.util.Locale;
 import java.util.concurrent.TimeUnit;
 
-import javax.net.ssl.SSLContext;
-
-import org.apache.hc.core5.http.ClassicHttpRequest;
-import org.apache.hc.core5.http.ClassicHttpResponse;
+import org.apache.hc.core5.http.ConnectionClosedException;
 import org.apache.hc.core5.http.ExceptionListener;
 import org.apache.hc.core5.http.HttpConnection;
 import org.apache.hc.core5.http.HttpException;
+import org.apache.hc.core5.http.HttpRequest;
 import org.apache.hc.core5.http.HttpStatus;
-import org.apache.hc.core5.http.MethodNotSupportedException;
-import org.apache.hc.core5.http.bootstrap.nio.HttpServer;
-import org.apache.hc.core5.http.bootstrap.nio.ServerBootstrap;
-import org.apache.hc.core5.http.entity.ContentType;
-import org.apache.hc.core5.http.impl.nio.BasicAsyncRequestConsumer;
-import org.apache.hc.core5.http.impl.nio.BasicAsyncResponseProducer;
-import org.apache.hc.core5.http.nio.HttpAsyncExchange;
-import org.apache.hc.core5.http.nio.HttpAsyncRequestConsumer;
-import org.apache.hc.core5.http.nio.HttpAsyncRequestHandler;
-import org.apache.hc.core5.http.nio.entity.NFileEntity;
-import org.apache.hc.core5.http.nio.entity.NStringEntity;
+import org.apache.hc.core5.http.Message;
+import org.apache.hc.core5.http.ProtocolException;
+import org.apache.hc.core5.http.impl.nio.ConnectionListener;
+import org.apache.hc.core5.http.impl.nio.bootstrap.HttpAsyncServer;
+import org.apache.hc.core5.http.impl.nio.bootstrap.ServerBootstrap;
+import org.apache.hc.core5.http.io.entity.ContentType;
+import org.apache.hc.core5.http.nio.AsyncRequestConsumer;
+import org.apache.hc.core5.http.nio.BasicRequestConsumer;
+import org.apache.hc.core5.http.nio.BasicResponseProducer;
+import org.apache.hc.core5.http.nio.entity.FileEntityProducer;
+import org.apache.hc.core5.http.nio.entity.NoopEntityConsumer;
+import org.apache.hc.core5.http.nio.support.RequestConsumerSupplier;
+import org.apache.hc.core5.http.nio.support.ResponseHandler;
+import org.apache.hc.core5.http.nio.support.ResponseTrigger;
 import org.apache.hc.core5.http.protocol.HttpContext;
 import org.apache.hc.core5.http.protocol.HttpCoreContext;
 import org.apache.hc.core5.reactor.IOReactorConfig;
-import org.apache.hc.core5.ssl.SSLContexts;
+import org.apache.hc.core5.reactor.ListenerEndpoint;
 
 /**
- * Embedded HTTP/1.1 file server based on a non-blocking I/O model and capable of direct channel
- * (zero copy) data transfer.
+ * Asynchronous embedded HTTP/1.1 file server.
  */
 public class NHttpFileServer {
 
@@ -69,120 +71,133 @@ public class NHttpFileServer {
             System.exit(1);
         }
         // Document root directory
-        File docRoot = new File(args[0]);
+        final File docRoot = new File(args[0]);
         int port = 8080;
         if (args.length >= 2) {
             port = Integer.parseInt(args[1]);
         }
 
-        SSLContext sslcontext = null;
-        if (port == 8443) {
-            // Initialize SSL context
-            URL url = NHttpFileServer.class.getResource("/my.keystore");
-            if (url == null) {
-                System.out.println("Keystore not found");
-                System.exit(1);
-            }
-            sslcontext = SSLContexts.custom()
-                    .loadKeyMaterial(url, "secret".toCharArray(), "secret".toCharArray())
-                    .build();
-        }
-
         IOReactorConfig config = IOReactorConfig.custom()
                 .setSoTimeout(15000)
                 .setTcpNoDelay(true)
                 .build();
 
-        final HttpServer server = ServerBootstrap.bootstrap()
-                .setListenerPort(port)
-                .setServerInfo("Test/1.1")
+        final HttpAsyncServer server = ServerBootstrap.bootstrap()
                 .setIOReactorConfig(config)
-                .setSslContext(sslcontext)
-                .setExceptionListener(ExceptionListener.STD_ERR)
-                .registerHandler("*", new HttpFileHandler(docRoot))
-                .create();
+                .setExceptionListener(new ExceptionListener() {
 
-        server.start();
-        server.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);
+                    @Override
+                    public void onError(final Exception ex) {
+                        if (ex instanceof ConnectionClosedException) {
+                            return;
+                        }
+                        if (ex instanceof SocketTimeoutException) {
+                            System.out.println("Timeout");
+                        } else if (ex instanceof IOException) {
+                            System.out.println("I/O error: " + ex.getMessage());
+                        } else {
+                            ex.printStackTrace();
+                        }
+                    }
+                })
+                .setConnectionListener(new ConnectionListener() {
+
+                    @Override
+                    public void onConnect(final HttpConnection connection) {
+                        System.out.println(connection + " connected");
+                    }
+
+                    @Override
+                    public void onError(final HttpConnection connection, final Exception ex) {
+                        System.err.println(connection + " error: " + ex.getMessage());
+                    }
+
+                    @Override
+                    public void onDisconnect(final HttpConnection connection) {
+                        System.out.println(connection + " disconnected");
+                    }
+
+                })
+                .register("*", new RequestConsumerSupplier<Message<HttpRequest, Void>>() {
+
+                    @Override
+                    public AsyncRequestConsumer<Message<HttpRequest, Void>> get(
+                            final HttpRequest request,
+                            final HttpContext context) throws HttpException {
+                        return new BasicRequestConsumer<>(new NoopEntityConsumer());
+                    }
+
+                }, new ResponseHandler<Message<HttpRequest, Void>>() {
+
+                    @Override
+                    public void handle(
+                            final Message<HttpRequest, Void> message,
+                            final ResponseTrigger responseTrigger,
+                            final HttpContext context) throws HttpException, IOException {
+                        HttpRequest request = message.getHead();
+                        URI requestUri;
+                        try {
+                            requestUri = request.getUri();
+                        } catch (URISyntaxException ex) {
+                            throw new ProtocolException(ex.getMessage(), ex);
+                        }
+                        String path = requestUri.getPath();
+                        final File file = new File(docRoot, path);
+                        if (!file.exists()) {
+
+                            System.out.println("File " + file.getPath() + " not found");
+                            responseTrigger.submitResponse(new BasicResponseProducer(
+                                    HttpStatus.SC_NOT_FOUND,
+                                    "<html><body><h1>File" + file.getPath() +
+                                            " not found</h1></body></html>",
+                                    ContentType.TEXT_HTML));
+
+                        } else if (!file.canRead() || file.isDirectory()) {
+
+                            System.out.println("Cannot read file " + file.getPath());
+                            responseTrigger.submitResponse(new BasicResponseProducer(
+                                    HttpStatus.SC_FORBIDDEN,
+                                    "<html><body><h1>Access denied</h1></body></html>",
+                                    ContentType.TEXT_HTML));
+
+                        } else {
+
+                            final ContentType contentType;
+                            final String filename = file.getName().toLowerCase(Locale.ROOT);
+                            if (filename.endsWith(".txt")) {
+                                contentType = ContentType.TEXT_PLAIN;
+                            } else if (filename.endsWith(".html") || filename.endsWith(".htm")) {
+                                contentType = ContentType.TEXT_HTML;
+                            } else if (filename.endsWith(".xml")) {
+                                contentType = ContentType.TEXT_XML;
+                            } else {
+                                contentType = ContentType.DEFAULT_BINARY;
+                            }
+
+                            final HttpConnection connection = (HttpConnection) context.getAttribute(HttpCoreContext.HTTP_CONNECTION);
+
+                            System.out.println(connection + " serving file " + file.getPath());
+                            responseTrigger.submitResponse(new BasicResponseProducer(
+                                    HttpStatus.SC_OK, new FileEntityProducer(file, contentType)));
+                        }
+                    }
+
+                })
+                .create();
 
         Runtime.getRuntime().addShutdownHook(new Thread() {
             @Override
             public void run() {
+                System.out.println("HTTP server shutting down");
                 server.shutdown(5, TimeUnit.SECONDS);
             }
         });
 
-    }
-
-    static class HttpFileHandler implements HttpAsyncRequestHandler<ClassicHttpRequest> {
-
-        private final File docRoot;
-
-        public HttpFileHandler(final File docRoot) {
-            super();
-            this.docRoot = docRoot;
-        }
-
-        @Override
-        public HttpAsyncRequestConsumer<ClassicHttpRequest> processRequest(
-                final ClassicHttpRequest request,
-                final HttpContext context) {
-            // Buffer request content in memory for simplicity
-            return new BasicAsyncRequestConsumer();
-        }
-
-        @Override
-        public void handle(
-                final ClassicHttpRequest request,
-                final HttpAsyncExchange httpexchange,
-                final HttpContext context) throws HttpException, IOException {
-            ClassicHttpResponse response = httpexchange.getResponse();
-            handleInternal(request, response, context);
-            httpexchange.submitResponse(new BasicAsyncResponseProducer(response));
-        }
-
-        private void handleInternal(
-                final ClassicHttpRequest request,
-                final ClassicHttpResponse response,
-                final HttpContext context) throws HttpException, IOException {
-
-            String method = request.getMethod().toUpperCase(Locale.ENGLISH);
-            if (!method.equals("GET") && !method.equals("HEAD") && !method.equals("POST")) {
-                throw new MethodNotSupportedException(method + " method not supported");
-            }
-
-            String path = request.getPath();
-            final File file = new File(this.docRoot, URLDecoder.decode(path, "UTF-8"));
-            if (!file.exists()) {
-
-                response.setCode(HttpStatus.SC_NOT_FOUND);
-                NStringEntity entity = new NStringEntity(
-                        "<html><body><h1>File" + file.getPath() +
-                        " not found</h1></body></html>",
-                        ContentType.create("text/html", "UTF-8"));
-                response.setEntity(entity);
-                System.out.println("File " + file.getPath() + " not found");
-
-            } else if (!file.canRead() || file.isDirectory()) {
-
-                response.setCode(HttpStatus.SC_FORBIDDEN);
-                NStringEntity entity = new NStringEntity(
-                        "<html><body><h1>Access denied</h1></body></html>",
-                        ContentType.create("text/html", "UTF-8"));
-                response.setEntity(entity);
-                System.out.println("Cannot read file " + file.getPath());
-
-            } else {
-
-                HttpCoreContext coreContext = HttpCoreContext.adapt(context);
-                HttpConnection conn = coreContext.getConnection(HttpConnection.class);
-                response.setCode(HttpStatus.SC_OK);
-                NFileEntity body = new NFileEntity(file, ContentType.create("text/html"));
-                response.setEntity(body);
-                System.out.println(conn + ": serving file " + file.getPath());
-            }
-        }
-
+        server.start();
+        ListenerEndpoint listenerEndpoint = server.listen(new InetSocketAddress(port));
+        listenerEndpoint.waitFor();
+        System.out.print("Listening on " + listenerEndpoint.getAddress());
+        server.awaitShutdown(Long.MAX_VALUE, TimeUnit.DAYS);
     }
 
 }

Modified: httpcomponents/httpcore/trunk/httpcore5/src/examples/org/apache/hc/core5/http/examples/NHttpReverseProxy.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/trunk/httpcore5/src/examples/org/apache/hc/core5/http/examples/NHttpReverseProxy.java?rev=1767339&r1=1767338&r2=1767339&view=diff
==============================================================================
--- httpcomponents/httpcore/trunk/httpcore5/src/examples/org/apache/hc/core5/http/examples/NHttpReverseProxy.java (original)
+++ httpcomponents/httpcore/trunk/httpcore5/src/examples/org/apache/hc/core5/http/examples/NHttpReverseProxy.java Mon Oct 31 17:33:27 2016
@@ -29,834 +29,657 @@ package org.apache.hc.core5.http.example
 import java.io.IOException;
 import java.io.InterruptedIOException;
 import java.net.InetSocketAddress;
-import java.net.URI;
 import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicReference;
 
-import org.apache.hc.core5.http.ClassicHttpRequest;
-import org.apache.hc.core5.http.ClassicHttpResponse;
-import org.apache.hc.core5.http.ConnectionReuseStrategy;
+import org.apache.hc.core5.concurrent.FutureCallback;
+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.HttpConnection;
 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.config.ConnectionConfig;
-import org.apache.hc.core5.http.entity.ContentType;
-import org.apache.hc.core5.http.impl.DefaultConnectionReuseStrategy;
-import org.apache.hc.core5.http.impl.nio.BasicAsyncResponseProducer;
-import org.apache.hc.core5.http.impl.nio.DefaultHttpClientIOEventHandlerFactory;
-import org.apache.hc.core5.http.impl.nio.DefaultHttpServerIOEventHandlerFactory;
-import org.apache.hc.core5.http.impl.nio.HttpAsyncRequestExecutor;
-import org.apache.hc.core5.http.impl.nio.HttpAsyncRequester;
-import org.apache.hc.core5.http.impl.nio.HttpAsyncService;
-import org.apache.hc.core5.http.impl.nio.UriHttpAsyncRequestHandlerMapper;
-import org.apache.hc.core5.http.message.BasicClassicHttpRequest;
-import org.apache.hc.core5.http.message.BasicClassicHttpResponse;
-import org.apache.hc.core5.http.nio.ContentDecoder;
-import org.apache.hc.core5.http.nio.ContentEncoder;
-import org.apache.hc.core5.http.nio.HttpAsyncExchange;
-import org.apache.hc.core5.http.nio.HttpAsyncRequestConsumer;
-import org.apache.hc.core5.http.nio.HttpAsyncRequestHandler;
-import org.apache.hc.core5.http.nio.HttpAsyncRequestHandlerMapper;
-import org.apache.hc.core5.http.nio.HttpAsyncRequestProducer;
-import org.apache.hc.core5.http.nio.HttpAsyncResponseConsumer;
-import org.apache.hc.core5.http.nio.HttpAsyncResponseProducer;
-import org.apache.hc.core5.http.nio.IOControl;
-import org.apache.hc.core5.http.nio.NHttpClientConnection;
-import org.apache.hc.core5.http.nio.NHttpConnection;
-import org.apache.hc.core5.http.nio.NHttpServerConnection;
-import org.apache.hc.core5.http.nio.entity.NStringEntity;
-import org.apache.hc.core5.http.pool.nio.BasicNIOConnPool;
-import org.apache.hc.core5.http.pool.nio.BasicNIOPoolEntry;
+import org.apache.hc.core5.http.impl.BasicEntityDetails;
+import org.apache.hc.core5.http.impl.LazyEntityDetails;
+import org.apache.hc.core5.http.impl.nio.ConnectionListener;
+import org.apache.hc.core5.http.impl.nio.ExpandableBuffer;
+import org.apache.hc.core5.http.impl.nio.Http1StreamListener;
+import org.apache.hc.core5.http.impl.nio.bootstrap.ClientEndpoint;
+import org.apache.hc.core5.http.impl.nio.bootstrap.ClientEndpointImpl;
+import org.apache.hc.core5.http.impl.nio.bootstrap.HttpAsyncRequester;
+import org.apache.hc.core5.http.impl.nio.bootstrap.HttpAsyncServer;
+import org.apache.hc.core5.http.impl.nio.bootstrap.RequesterBootstrap;
+import org.apache.hc.core5.http.impl.nio.bootstrap.ServerBootstrap;
+import org.apache.hc.core5.http.impl.nio.pool.BasicNIOConnPool;
+import org.apache.hc.core5.http.impl.nio.pool.BasicNIOPoolEntry;
+import org.apache.hc.core5.http.io.entity.ContentType;
+import org.apache.hc.core5.http.message.BasicHttpRequest;
+import org.apache.hc.core5.http.message.BasicHttpResponse;
+import org.apache.hc.core5.http.nio.AsyncClientExchangeHandler;
+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.ExpectationChannel;
+import org.apache.hc.core5.http.nio.RequestChannel;
+import org.apache.hc.core5.http.nio.ResponseChannel;
+import org.apache.hc.core5.http.nio.Supplier;
 import org.apache.hc.core5.http.protocol.HttpContext;
-import org.apache.hc.core5.http.protocol.HttpCoreContext;
-import org.apache.hc.core5.http.protocol.HttpProcessor;
-import org.apache.hc.core5.http.protocol.DefaultHttpProcessor;
-import org.apache.hc.core5.http.protocol.RequestConnControl;
-import org.apache.hc.core5.http.protocol.RequestContent;
-import org.apache.hc.core5.http.protocol.RequestExpectContinue;
-import org.apache.hc.core5.http.protocol.RequestTargetHost;
-import org.apache.hc.core5.http.protocol.RequestUserAgent;
-import org.apache.hc.core5.http.protocol.ResponseConnControl;
-import org.apache.hc.core5.http.protocol.ResponseContent;
-import org.apache.hc.core5.http.protocol.ResponseDate;
-import org.apache.hc.core5.http.protocol.ResponseServer;
 import org.apache.hc.core5.pool.PoolStats;
-import org.apache.hc.core5.reactor.ConnectingIOReactor;
-import org.apache.hc.core5.reactor.DefaultConnectingIOReactor;
-import org.apache.hc.core5.reactor.DefaultListeningIOReactor;
+import org.apache.hc.core5.reactor.ConnectionInitiator;
 import org.apache.hc.core5.reactor.IOReactorConfig;
-import org.apache.hc.core5.reactor.ListeningIOReactor;
+import org.apache.hc.core5.reactor.IOSession;
+import org.apache.hc.core5.util.HeapByteBufferAllocator;
 
 /**
- * Asynchronous, fully streaming HTTP/1.1 reverse proxy.
+ * Asynchronous embedded  HTTP/1.1 reverse proxy with full content streaming.
  */
 public class NHttpReverseProxy {
 
     public static void main(String[] args) throws Exception {
         if (args.length < 1) {
-            System.out.println("Usage: NHttpReverseProxy <hostname> [port]");
+            System.out.println("Usage: NHttpReverseProxy <hostname> [listener port]");
             System.exit(1);
         }
-        URI uri = new URI(args[0]);
+        // Target host
+        final HttpHost targetHost = HttpHost.create(args[0]);
         int port = 8080;
         if (args.length > 1) {
             port = Integer.parseInt(args[1]);
         }
 
-        // Target host
-        HttpHost targetHost = new HttpHost(
-                uri.getHost(),
-                uri.getPort() > 0 ? uri.getPort() : 80,
-                uri.getScheme() != null ? uri.getScheme() : "http");
-
         System.out.println("Reverse proxy to " + targetHost);
 
         IOReactorConfig config = IOReactorConfig.custom()
-            .setIoThreadCount(1)
             .setSoTimeout(3000)
             .setConnectTimeout(3000)
             .build();
 
-        // Set up HTTP protocol processor for outgoing connections
-        HttpProcessor outhttpproc;
-        outhttpproc = new DefaultHttpProcessor(
-                new RequestContent(),
-                new RequestTargetHost(),
-                new RequestConnControl(),
-                new RequestUserAgent("Test/1.1"),
-                new RequestExpectContinue());
-
-        ProxyClientProtocolHandler clientProtocolHandler = new ProxyClientProtocolHandler();
-        final ConnectingIOReactor connectingIOReactor = new DefaultConnectingIOReactor(
-                new DefaultHttpClientIOEventHandlerFactory(clientProtocolHandler, ConnectionConfig.DEFAULT));
-        HttpAsyncRequester executor = new HttpAsyncRequester(outhttpproc);
-
-        ProxyConnPool connPool = new ProxyConnPool(connectingIOReactor, 0);
-        connPool.setMaxTotal(100);
-        connPool.setDefaultMaxPerRoute(20);
-
+        final HttpAsyncRequester requester = RequesterBootstrap.bootstrap()
+                .setIOReactorConfig(config)
+                .setConnectionListener(new ConnectionListener() {
+
+                    @Override
+                    public void onConnect(final HttpConnection connection) {
+                        System.out.println("[proxy->origin] connection open " + connection);
+                    }
 
-        // Set up HTTP protocol processor for incoming connections
-        HttpProcessor inhttpproc = new DefaultHttpProcessor(
-                new ResponseDate(),
-                new ResponseServer("Test/1.1"),
-                new ResponseContent(),
-                new ResponseConnControl());
-
-        UriHttpAsyncRequestHandlerMapper handlerRegistry = new UriHttpAsyncRequestHandlerMapper();
-        handlerRegistry.register("*", new ProxyRequestHandler(targetHost, executor, connPool));
-
-        ProxyServiceHandler serverProtocolHandler = new ProxyServiceHandler(
-                inhttpproc,
-                new ProxyIncomingConnectionReuseStrategy(),
-                handlerRegistry);
-        final ListeningIOReactor listeningIOReactor = new DefaultListeningIOReactor(
-                new DefaultHttpServerIOEventHandlerFactory(serverProtocolHandler, ConnectionConfig.DEFAULT));
+                    @Override
+                    public void onDisconnect(final HttpConnection connection) {
+                        System.out.println("[proxy->origin] connection closed " + connection);
+                    }
 
-        Thread t = new Thread(new Runnable() {
+                    @Override
+                    public void onError(final HttpConnection connection, final Exception ex) {
+                    }
 
-            @Override
-            public void run() {
-                try {
-                    connectingIOReactor.execute();
-                } catch (InterruptedIOException ex) {
-                    System.err.println("Interrupted");
-                } catch (IOException ex) {
-                    ex.printStackTrace();
-                } finally {
-                    listeningIOReactor.shutdown(3, TimeUnit.SECONDS);
-                }
-            }
+                })
+                .setStreamListener(new Http1StreamListener() {
 
-        });
-        t.start();
-        try {
-            listeningIOReactor.listen(new InetSocketAddress(port));
-            listeningIOReactor.execute();
-        } catch (InterruptedIOException ex) {
-            System.err.println("Interrupted");
-        } catch (IOException ex) {
-            ex.printStackTrace();
-        } finally {
-            connectingIOReactor.shutdown(3, TimeUnit.SECONDS);
-        }
-    }
+                    @Override
+                    public void onRequestHead(final HttpConnection connection, HttpRequest request) {
+                    }
 
-    static class ProxyHttpExchange {
+                    @Override
+                    public void onResponseHead(final HttpConnection connection, HttpResponse response) {
+                    }
 
-        private final ByteBuffer inBuffer;
-        private final ByteBuffer outBuffer;
+                    @Override
+                    public void onExchangeComplete(final HttpConnection connection, final boolean keepAlive) {
+                        System.out.println("[proxy<-origin] connection " + connection +
+                                (keepAlive ? " kept alive" : " cannot be kept alive"));
+                    }
 
-        private volatile String id;
-        private volatile HttpHost target;
-        private volatile HttpAsyncExchange responseTrigger;
-        private volatile IOControl originIOControl;
-        private volatile IOControl clientIOControl;
-        private volatile ClassicHttpRequest request;
-        private volatile boolean requestReceived;
-        private volatile ClassicHttpResponse response;
-        private volatile boolean responseReceived;
-        private volatile Exception ex;
+                })
+                .create();
 
-        public ProxyHttpExchange() {
-            super();
-            this.inBuffer = ByteBuffer.allocateDirect(10240);
-            this.outBuffer = ByteBuffer.allocateDirect(10240);
-        }
+        final ProxyConnPool connPool = new ProxyConnPool(requester, 0);
+        connPool.setMaxTotal(100);
+        connPool.setDefaultMaxPerRoute(20);
 
-        public ByteBuffer getInBuffer() {
-            return this.inBuffer;
-        }
+        final HttpAsyncServer server = ServerBootstrap.bootstrap()
+                .setIOReactorConfig(config)
+                .setConnectionListener(new ConnectionListener() {
+
+                    @Override
+                    public void onConnect(final HttpConnection connection) {
+                        System.out.println("[client->proxy] connection open " + connection);
+                    }
 
-        public ByteBuffer getOutBuffer() {
-            return this.outBuffer;
-        }
+                    @Override
+                    public void onDisconnect(final HttpConnection connection) {
+                        System.out.println("[client->proxy] connection closed " + connection);
+                    }
 
-        public String getId() {
-            return this.id;
-        }
+                    @Override
+                    public void onError(final HttpConnection connection, final Exception ex) {
+                    }
 
-        public void setId(final String id) {
-            this.id = id;
-        }
+                })
+                .setStreamListener(new Http1StreamListener() {
 
-        public HttpHost getTarget() {
-            return this.target;
-        }
+                    @Override
+                    public void onRequestHead(final HttpConnection connection, HttpRequest request) {
+                    }
 
-        public void setTarget(final HttpHost target) {
-            this.target = target;
-        }
+                    @Override
+                    public void onResponseHead(final HttpConnection connection, HttpResponse response) {
+                    }
 
-        public ClassicHttpRequest getRequest() {
-            return this.request;
-        }
+                    @Override
+                    public void onExchangeComplete(final HttpConnection connection, final boolean keepAlive) {
+                        System.out.println("[client<-proxy] connection " + connection +
+                                (keepAlive ? " kept alive" : " cannot be kept alive"));
+                    }
 
-        public void setRequest(final ClassicHttpRequest request) {
-            this.request = request;
-        }
+                })
+                .register("*", new Supplier<AsyncServerExchangeHandler>() {
 
-        public ClassicHttpResponse getResponse() {
-            return this.response;
-        }
+                    @Override
+                    public AsyncServerExchangeHandler get() {
+                        return new IncomingExchangeHandler(targetHost, connPool);
+                    }
 
-        public void setResponse(final ClassicHttpResponse response) {
-            this.response = response;
-        }
+                })
+                .create();
 
-        public HttpAsyncExchange getResponseTrigger() {
-            return this.responseTrigger;
-        }
+        Runtime.getRuntime().addShutdownHook(new Thread() {
+            @Override
+            public void run() {
+                System.out.println("Reverse proxy shutting down");
+                server.shutdown(5, TimeUnit.SECONDS);
+                requester.shutdown(5, TimeUnit.SECONDS);
+            }
+        });
 
-        public void setResponseTrigger(final HttpAsyncExchange responseTrigger) {
-            this.responseTrigger = responseTrigger;
-        }
+        requester.start();
+        server.start();
+        server.listen(new InetSocketAddress(port));
 
-        public IOControl getClientIOControl() {
-            return this.clientIOControl;
-        }
+        server.awaitShutdown(Integer.MAX_VALUE, TimeUnit.DAYS);
+    }
 
-        public void setClientIOControl(final IOControl clientIOControl) {
-            this.clientIOControl = clientIOControl;
-        }
+    private static class ProxyBuffer extends ExpandableBuffer {
 
-        public IOControl getOriginIOControl() {
-            return this.originIOControl;
+        ProxyBuffer(int buffersize) {
+            super(buffersize, HeapByteBufferAllocator.INSTANCE);
         }
 
-        public void setOriginIOControl(final IOControl originIOControl) {
-            this.originIOControl = originIOControl;
+        void put(final ByteBuffer src) {
+            setInputMode();
+            int requiredCapacity = buffer().position() + src.remaining();
+            ensureCapacity(requiredCapacity);
+            buffer().put(src);
         }
 
-        public boolean isRequestReceived() {
-            return this.requestReceived;
+        int write(final DataStreamChannel channel) throws IOException {
+            setOutputMode();
+            if (buffer().hasRemaining()) {
+                return channel.write(buffer());
+            } else {
+                return 0;
+            }
         }
 
-        public void setRequestReceived() {
-            this.requestReceived = true;
-        }
+    }
 
-        public boolean isResponseReceived() {
-            return this.responseReceived;
-        }
+    private static final AtomicLong COUNT = new AtomicLong(0);
 
-        public void setResponseReceived() {
-            this.responseReceived = true;
-        }
+    private static class ProxyExchangeState {
 
-        public Exception getException() {
-            return this.ex;
-        }
+        final String id;
 
-        public void setException(final Exception ex) {
-            this.ex = ex;
-        }
+        ProxyBuffer inBuf;
+        ProxyBuffer outBuf;
+        HttpRequest request;
+        HttpResponse response;
+        boolean inputEnd;
+        boolean outputEnd;
+        ResponseChannel responseMessageChannel;
+        CapacityChannel requestCapacityChannel;
+        CapacityChannel responseCapacityChannel;
+        DataStreamChannel requestDataChannel;
+        DataStreamChannel responseDataChannel;
 
-        public void reset() {
-            this.inBuffer.clear();
-            this.outBuffer.clear();
-            this.target = null;
-            this.id = null;
-            this.responseTrigger = null;
-            this.clientIOControl = null;
-            this.originIOControl = null;
-            this.request = null;
-            this.requestReceived = false;
-            this.response = null;
-            this.responseReceived = false;
-            this.ex = null;
+        ProxyExchangeState() {
+            this.id = String.format("%08X", COUNT.getAndIncrement());
         }
 
     }
 
-    static class ProxyRequestHandler implements HttpAsyncRequestHandler<ProxyHttpExchange> {
-
-        private final HttpHost target;
-        private final HttpAsyncRequester executor;
-        private final BasicNIOConnPool connPool;
-        private final AtomicLong counter;
-
-        public ProxyRequestHandler(
-                final HttpHost target,
-                final HttpAsyncRequester executor,
-                final BasicNIOConnPool connPool) {
-            super();
-            this.target = target;
-            this.executor = executor;
-            this.connPool = connPool;
-            this.counter = new AtomicLong(1);
-        }
-
-        @Override
-        public HttpAsyncRequestConsumer<ProxyHttpExchange> processRequest(
-                final ClassicHttpRequest request,
-                final HttpContext context) {
-            ProxyHttpExchange httpExchange = (ProxyHttpExchange) context.getAttribute("http-exchange");
-            if (httpExchange == null) {
-                httpExchange = new ProxyHttpExchange();
-                context.setAttribute("http-exchange", httpExchange);
-            }
-            synchronized (httpExchange) {
-                httpExchange.reset();
-                String id = String.format("%08X", this.counter.getAndIncrement());
-                httpExchange.setId(id);
-                httpExchange.setTarget(this.target);
-                return new ProxyRequestConsumer(httpExchange, this.executor, this.connPool);
-            }
-        }
-
-        @Override
-        public void handle(
-                final ProxyHttpExchange httpExchange,
-                final HttpAsyncExchange responseTrigger,
-                final HttpContext context) throws HttpException, IOException {
-            synchronized (httpExchange) {
-                Exception ex = httpExchange.getException();
-                if (ex != null) {
-                    System.out.println("[client<-proxy] " + httpExchange.getId() + " " + ex);
-                    int status = HttpStatus.SC_INTERNAL_SERVER_ERROR;
-                    ClassicHttpResponse response = new BasicClassicHttpResponse(status);
-                    String message = ex.getMessage();
-                    if (message == null) {
-                        message = "Unexpected error";
-                    }
-                    response.setEntity(new NStringEntity(message, ContentType.DEFAULT_TEXT));
-                    responseTrigger.submitResponse(new BasicAsyncResponseProducer(response));
-                    System.out.println("[client<-proxy] " + httpExchange.getId() + " error response triggered");
-                }
-                ClassicHttpResponse response = httpExchange.getResponse();
-                if (response != null) {
-                    responseTrigger.submitResponse(new ProxyResponseProducer(httpExchange));
-                    System.out.println("[client<-proxy] " + httpExchange.getId() + " response triggered");
-                }
-                // No response yet.
-                httpExchange.setResponseTrigger(responseTrigger);
-            }
-        }
+    private static final int INIT_BUFFER_SIZE = 4096;
 
-    }
+    private static class IncomingExchangeHandler implements AsyncServerExchangeHandler {
 
-    static class ProxyRequestConsumer implements HttpAsyncRequestConsumer<ProxyHttpExchange> {
+        private final HttpHost targetHost;
+        private final ProxyConnPool connPool;
+        private final AtomicBoolean consistent;
+        private final AtomicReference<BasicNIOPoolEntry> poolEntryRef;
+        private final ProxyExchangeState exchangeState;
 
-        private final ProxyHttpExchange httpExchange;
-        private final HttpAsyncRequester executor;
-        private final BasicNIOConnPool connPool;
-
-        private volatile boolean completed;
-
-        public ProxyRequestConsumer(
-                final ProxyHttpExchange httpExchange,
-                final HttpAsyncRequester executor,
-                final BasicNIOConnPool connPool) {
+        IncomingExchangeHandler(final HttpHost targetHost, final ProxyConnPool connPool) {
             super();
-            this.httpExchange = httpExchange;
-            this.executor = executor;
+            this.targetHost = targetHost;
             this.connPool = connPool;
+            this.consistent = new AtomicBoolean(true);
+            this.poolEntryRef = new AtomicReference<>(null);
+            this.exchangeState = new ProxyExchangeState();
         }
 
         @Override
-        public void close() throws IOException {
+        public void setContext(final HttpContext context) {
         }
 
         @Override
-        public void requestReceived(final ClassicHttpRequest request) {
-            synchronized (this.httpExchange) {
-                System.out.println("[client->proxy] " + this.httpExchange.getId() + " " + request.getMethod() + " " + request.getPath());
-                this.httpExchange.setRequest(request);
-                this.executor.execute(
-                        new ProxyRequestProducer(this.httpExchange),
-                        new ProxyResponseConsumer(this.httpExchange),
-                        this.connPool);
-            }
+        public void verify(
+                final HttpRequest request,
+                final EntityDetails entityDetails,
+                final ExpectationChannel expectationChannel) throws HttpException, IOException {
+            expectationChannel.sendContinue();
         }
 
         @Override
-        public void consumeContent(
-                final ContentDecoder decoder, final IOControl ioctrl) throws IOException {
-            synchronized (this.httpExchange) {
-                this.httpExchange.setClientIOControl(ioctrl);
-                // Receive data from the client
-                ByteBuffer buf = this.httpExchange.getInBuffer();
-                int n = decoder.read(buf);
-                System.out.println("[client->proxy] " + this.httpExchange.getId() + " " + n + " bytes read");
-                if (decoder.isCompleted()) {
-                    System.out.println("[client->proxy] " + this.httpExchange.getId() + " content fully read");
-                }
-                // If the buffer is full, suspend client input until there is free
-                // space in the buffer
-                if (!buf.hasRemaining()) {
-                    ioctrl.suspendInput();
-                    System.out.println("[client->proxy] " + this.httpExchange.getId() + " suspend client input");
-                }
-                // If there is some content in the input buffer make sure origin
-                // output is active
-                if (buf.position() > 0) {
-                    if (this.httpExchange.getOriginIOControl() != null) {
-                        this.httpExchange.getOriginIOControl().requestOutput();
-                        System.out.println("[client->proxy] " + this.httpExchange.getId() + " request origin output");
-                    }
-                }
-            }
-        }
+        public void handleRequest(
+                final HttpRequest incomingRequest,
+                final EntityDetails entityDetails,
+                final ResponseChannel responseChannel) throws HttpException, IOException {
 
-        @Override
-        public void requestCompleted(final HttpContext context) {
-            synchronized (this.httpExchange) {
-                this.completed = true;;
-                System.out.println("[client->proxy] " + this.httpExchange.getId() + " request completed");
-                this.httpExchange.setRequestReceived();
-                if (this.httpExchange.getOriginIOControl() != null) {
-                    this.httpExchange.getOriginIOControl().requestOutput();
-                    System.out.println("[client->proxy] " + this.httpExchange.getId() + " request origin output");
-                }
+            synchronized (exchangeState) {
+                System.out.println("[client->proxy] " + exchangeState.id + " " +
+                        incomingRequest.getMethod() + " " + incomingRequest.getPath());
+                exchangeState.request = incomingRequest;
+                exchangeState.inputEnd = entityDetails == null;
+                exchangeState.responseMessageChannel = responseChannel;
             }
-        }
 
-        @Override
-        public Exception getException() {
-            return null;
-        }
+            System.out.println("[proxy->origin] " + exchangeState.id + " request connection to " + targetHost);
 
-        @Override
-        public ProxyHttpExchange getResult() {
-            return this.httpExchange;
-        }
+            connPool.lease(targetHost, null, 10, TimeUnit.SECONDS, new FutureCallback<BasicNIOPoolEntry>() {
 
-        @Override
-        public boolean isDone() {
-            return this.completed;
-        }
+                @Override
+                public void completed(final BasicNIOPoolEntry poolEntry) {
+                    poolEntryRef.set(poolEntry);
+                    IOSession iosession = poolEntry.getConnection();
+                    System.out.println("[proxy->origin] " + exchangeState.id + " connection leased: " + iosession.getHandler());
+                    ClientEndpoint clientEndpoint = new ClientEndpointImpl(iosession);
+                    clientEndpoint.execute(new OutgoingExchangeHandler(exchangeState), null);
+                }
 
-        @Override
-        public void failed(final Exception ex) {
-            System.out.println("[client->proxy] " + ex.toString());
-        }
+                @Override
+                public void failed(final Exception cause) {
+                    HttpResponse outgoingResponse = new BasicHttpResponse(HttpStatus.SC_SERVICE_UNAVAILABLE);
+                    outgoingResponse.addHeader(HttpHeaders.CONNECTION, HeaderElements.CLOSE);
+                    exchangeState.response = outgoingResponse;
 
-    }
+                    ByteBuffer msg = StandardCharsets.US_ASCII.encode(CharBuffer.wrap(cause.getMessage()));
+                    exchangeState.outBuf = new ProxyBuffer(1024);
+                    exchangeState.outBuf.put(msg);
+                    exchangeState.outputEnd = true;
 
-    static class ProxyRequestProducer implements HttpAsyncRequestProducer {
+                    System.out.println("[client<-proxy] " + exchangeState.id + " status " + outgoingResponse.getCode());
 
-        private final ProxyHttpExchange httpExchange;
+                    try {
+                        EntityDetails entityDetails = new BasicEntityDetails(msg.remaining(), ContentType.TEXT_PLAIN);
+                        responseChannel.sendResponse(outgoingResponse, entityDetails);
+                    } catch (HttpException | IOException ignore) {
+                    }
+                }
 
-        public ProxyRequestProducer(final ProxyHttpExchange httpExchange) {
-            super();
-            this.httpExchange = httpExchange;
-        }
+                @Override
+                public void cancelled() {
+                    failed(new InterruptedIOException());
+                }
 
-        @Override
-        public void close() throws IOException {
-        }
+            });
 
-        @Override
-        public HttpHost getTarget() {
-            synchronized (this.httpExchange) {
-                return this.httpExchange.getTarget();
-            }
         }
 
         @Override
-        public ClassicHttpRequest generateRequest() {
-            synchronized (this.httpExchange) {
-                ClassicHttpRequest request = this.httpExchange.getRequest();
-                System.out.println("[proxy->origin] " + this.httpExchange.getId() + " " + request.getMethod() + " " + request.getPath());
-                // Rewrite request!!!!
-                BasicClassicHttpRequest newREquest = new BasicClassicHttpRequest(request.getMethod(), request.getPath());
-                newREquest.setEntity(request.getEntity());
-                return newREquest;
+        public void updateCapacity(final CapacityChannel capacityChannel) throws IOException {
+            synchronized (exchangeState) {
+                exchangeState.requestCapacityChannel = capacityChannel;
+                int capacity = exchangeState.inBuf != null ? exchangeState.inBuf.capacity() : INIT_BUFFER_SIZE;
+                if (capacity > 0) {
+                    System.out.println("[client<-proxy] " + exchangeState.id + " input capacity: " + capacity);
+                    capacityChannel.update(capacity);
+                }
             }
         }
 
         @Override
-        public void produceContent(
-                final ContentEncoder encoder, final IOControl ioctrl) throws IOException {
-            synchronized (this.httpExchange) {
-                this.httpExchange.setOriginIOControl(ioctrl);
-                // Send data to the origin server
-                ByteBuffer buf = this.httpExchange.getInBuffer();
-                buf.flip();
-                int n = encoder.write(buf);
-                buf.compact();
-                System.out.println("[proxy->origin] " + this.httpExchange.getId() + " " + n + " bytes written");
-                // If there is space in the buffer and the message has not been
-                // transferred, make sure the client is sending more data
-                if (buf.hasRemaining() && !this.httpExchange.isRequestReceived()) {
-                    if (this.httpExchange.getClientIOControl() != null) {
-                        this.httpExchange.getClientIOControl().requestInput();
-                        System.out.println("[proxy->origin] " + this.httpExchange.getId() + " request client input");
+        public int consume(final ByteBuffer src) throws IOException {
+            synchronized (exchangeState) {
+                System.out.println("[client->proxy] " + exchangeState.id + " " + src.remaining() + " bytes received");
+                DataStreamChannel dataChannel = exchangeState.requestDataChannel;
+                if (dataChannel != null && exchangeState.inBuf != null) {
+                    if (exchangeState.inBuf.hasData()) {
+                        int bytesWritten = exchangeState.inBuf.write(dataChannel);
+                        System.out.println("[proxy->origin] " + exchangeState.id + " " + bytesWritten + " bytes sent");
+                    }
+                    if (!exchangeState.inBuf.hasData()) {
+                        int bytesWritten = dataChannel.write(src);
+                        System.out.println("[proxy->origin] " + exchangeState.id + " " + bytesWritten + " bytes sent");
                     }
                 }
-                if (buf.position() == 0) {
-                    if (this.httpExchange.isRequestReceived()) {
-                        encoder.complete();
-                        System.out.println("[proxy->origin] " + this.httpExchange.getId() + " content fully written");
-                    } else {
-                        // Input buffer is empty. Wait until the client fills up
-                        // the buffer
-                        ioctrl.suspendOutput();
-                        System.out.println("[proxy->origin] " + this.httpExchange.getId() + " suspend origin output");
+                if (src.hasRemaining()) {
+                    if (exchangeState.inBuf == null) {
+                        exchangeState.inBuf = new ProxyBuffer(INIT_BUFFER_SIZE);
                     }
+                    exchangeState.inBuf.put(src);
+                }
+                int capacity = exchangeState.inBuf != null ? exchangeState.inBuf.capacity() : INIT_BUFFER_SIZE;
+                System.out.println("[client<-proxy] " + exchangeState.id + " input capacity: " + capacity);
+                if (dataChannel != null) {
+                    dataChannel.requestOutput();
                 }
+                return capacity;
             }
         }
 
         @Override
-        public void requestCompleted(final HttpContext context) {
-            synchronized (this.httpExchange) {
-                System.out.println("[proxy->origin] " + this.httpExchange.getId() + " request completed");
+        public void streamEnd(final List<Header> trailers) throws HttpException, IOException {
+            synchronized (exchangeState) {
+                System.out.println("[client->proxy] " + exchangeState.id + " end of input");
+                exchangeState.inputEnd = true;
+                DataStreamChannel dataChannel = exchangeState.requestDataChannel;
+                if (dataChannel != null && (exchangeState.inBuf == null || !exchangeState.inBuf.hasData())) {
+                    System.out.println("[proxy->origin] " + exchangeState.id + " end of output");
+                    dataChannel.endStream();
+                }
             }
         }
 
         @Override
-        public boolean isRepeatable() {
-            return false;
-        }
-
-        @Override
-        public void resetRequest() {
+        public int available() {
+            synchronized (exchangeState) {
+                int available = exchangeState.outBuf != null ? exchangeState.outBuf.length() : 0;
+                System.out.println("[client<-proxy] " + exchangeState.id + " output available: " + available);
+                return available;
+            }
         }
 
         @Override
-        public void failed(final Exception ex) {
-            System.out.println("[proxy->origin] " + ex.toString());
-        }
-
-    }
-
-    static class ProxyResponseConsumer implements HttpAsyncResponseConsumer<ProxyHttpExchange> {
-
-        private final ProxyHttpExchange httpExchange;
+        public void produce(final DataStreamChannel channel) throws IOException {
+            synchronized (exchangeState) {
+                System.out.println("[client<-proxy] " + exchangeState.id + " produce output");
+                exchangeState.responseDataChannel = channel;
 
-        private volatile boolean completed;
-
-        public ProxyResponseConsumer(final ProxyHttpExchange httpExchange) {
-            super();
-            this.httpExchange = httpExchange;
+                if (exchangeState.outBuf != null) {
+                    if (exchangeState.outBuf.hasData()) {
+                        int bytesWritten = exchangeState.outBuf.write(channel);
+                        System.out.println("[client<-proxy] " + exchangeState.id + " " + bytesWritten + " bytes sent");
+                    }
+                    if (exchangeState.outputEnd && !exchangeState.outBuf.hasData()) {
+                        channel.endStream();
+                        System.out.println("[client<-proxy] " + exchangeState.id + " end of output");
+                    }
+                    if (!exchangeState.outputEnd) {
+                        CapacityChannel capacityChannel = exchangeState.responseCapacityChannel;
+                        if (capacityChannel != null) {
+                            int capacity = exchangeState.outBuf.capacity();
+                            if (capacity > 0) {
+                                System.out.println("[proxy->origin] " + exchangeState.id + " input capacity: " + capacity);
+                                capacityChannel.update(capacity);
+                            }
+                        }
+                    }
+                }
+            }
         }
 
         @Override
-        public void close() throws IOException {
+        public void failed(final Exception cause) {
+            System.out.println("[client<-proxy] " + exchangeState.id + " error: " + cause.getMessage());
+            cause.printStackTrace(System.out);
+            consistent.set(false);
+            releaseResources();
         }
 
         @Override
-        public void responseReceived(final ClassicHttpResponse response) {
-            synchronized (this.httpExchange) {
-                System.out.println("[proxy<-origin] " + this.httpExchange.getId() + " " + response.getCode());
-                this.httpExchange.setResponse(response);
-                HttpAsyncExchange responseTrigger = this.httpExchange.getResponseTrigger();
-                if (responseTrigger != null && !responseTrigger.isCompleted()) {
-                    System.out.println("[client<-proxy] " + this.httpExchange.getId() + " response triggered");
-                    responseTrigger.submitResponse(new ProxyResponseProducer(this.httpExchange));
-                }
+        public void releaseResources() {
+            BasicNIOPoolEntry poolEntry = poolEntryRef.getAndSet(null);
+            if (poolEntry != null) {
+                System.out.println("[proxy->origin] " + exchangeState.id + " releasing connection");
+                connPool.release(poolEntry, consistent.get());
+            }
+            synchronized (exchangeState) {
+                exchangeState.responseMessageChannel = null;
+                exchangeState.responseDataChannel = null;
+                exchangeState.requestCapacityChannel = null;
             }
         }
 
+    }
+
+    private final static Set<String> HOP_BY_HOP = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
+            HttpHeaders.CONTENT_LENGTH.toLowerCase(Locale.ROOT),
+            HttpHeaders.TRANSFER_ENCODING.toLowerCase(Locale.ROOT),
+            HttpHeaders.CONNECTION.toLowerCase(Locale.ROOT),
+            "Keep-Alive".toLowerCase(Locale.ROOT),
+            "Proxy-Authenticate".toLowerCase(Locale.ROOT),
+            HttpHeaders.TE.toLowerCase(Locale.ROOT),
+            HttpHeaders.TRAILER.toLowerCase(Locale.ROOT),
+            HttpHeaders.UPGRADE.toLowerCase(Locale.ROOT))));
+
+    private static class OutgoingExchangeHandler implements AsyncClientExchangeHandler {
+
+        private final ProxyExchangeState exchangeState;
+
+        OutgoingExchangeHandler(final ProxyExchangeState exchangeState) {
+            this.exchangeState = exchangeState;
+        }
+
         @Override
-        public void consumeContent(
-                final ContentDecoder decoder, final IOControl ioctrl) throws IOException {
-            synchronized (this.httpExchange) {
-                this.httpExchange.setOriginIOControl(ioctrl);
-                // Receive data from the origin
-                ByteBuffer buf = this.httpExchange.getOutBuffer();
-                int n = decoder.read(buf);
-                System.out.println("[proxy<-origin] " + this.httpExchange.getId() + " " + n + " bytes read");
-                if (decoder.isCompleted()) {
-                    System.out.println("[proxy<-origin] " + this.httpExchange.getId() + " content fully read");
-                }
-                // If the buffer is full, suspend origin input until there is free
-                // space in the buffer
-                if (!buf.hasRemaining()) {
-                    ioctrl.suspendInput();
-                    System.out.println("[proxy<-origin] " + this.httpExchange.getId() + " suspend origin input");
-                }
-                // If there is some content in the input buffer make sure client
-                // output is active
-                if (buf.position() > 0) {
-                    if (this.httpExchange.getClientIOControl() != null) {
-                        this.httpExchange.getClientIOControl().requestOutput();
-                        System.out.println("[proxy<-origin] " + this.httpExchange.getId() + " request client output");
+        public void produceRequest(
+                final RequestChannel channel) throws HttpException, IOException {
+            synchronized (exchangeState) {
+                HttpRequest incomingRequest = exchangeState.request;
+                HttpRequest outgoingRequest = new BasicHttpRequest(incomingRequest.getMethod(), incomingRequest.getPath());
+                for (Iterator<Header> it = incomingRequest.headerIterator(); it.hasNext(); ) {
+                    Header header = it.next();
+                    if (!HOP_BY_HOP.contains(header.getName().toLowerCase(Locale.ROOT))) {
+                        outgoingRequest.addHeader(header);
                     }
                 }
+
+                System.out.println("[proxy->origin] " + exchangeState.id + " " +
+                        outgoingRequest.getMethod() + " " + outgoingRequest.getPath());
+
+                channel.sendRequest(
+                        outgoingRequest,
+                        !exchangeState.inputEnd ? new LazyEntityDetails(outgoingRequest) : null);
             }
         }
 
         @Override
-        public void responseCompleted(final HttpContext context) {
-            synchronized (this.httpExchange) {
-                if (this.completed) {
-                    return;
-                }
-                this.completed = true;
-                System.out.println("[proxy<-origin] " + this.httpExchange.getId() + " response completed");
-                this.httpExchange.setResponseReceived();
-                if (this.httpExchange.getClientIOControl() != null) {
-                    this.httpExchange.getClientIOControl().requestOutput();
-                    System.out.println("[proxy<-origin] " + this.httpExchange.getId() + " request client output");
-                }
+        public int available() {
+            synchronized (exchangeState) {
+                int available = exchangeState.inBuf != null ? exchangeState.inBuf.length() : 0;
+                System.out.println("[proxy->origin] " + exchangeState.id + " output available: " + available);
+                return available;
             }
         }
 
         @Override
-        public void failed(final Exception ex) {
-            synchronized (this.httpExchange) {
-                if (this.completed) {
-                    return;
-                }
-                this.completed = true;
-                this.httpExchange.setException(ex);
-                HttpAsyncExchange responseTrigger = this.httpExchange.getResponseTrigger();
-                if (responseTrigger != null && !responseTrigger.isCompleted()) {
-                    System.out.println("[client<-proxy] " + this.httpExchange.getId() + " " + ex);
-                    int status = HttpStatus.SC_INTERNAL_SERVER_ERROR;
-                    ClassicHttpResponse response = new BasicClassicHttpResponse(status);
-                    String message = ex.getMessage();
-                    if (message == null) {
-                        message = "Unexpected error";
+        public void produce(final DataStreamChannel channel) throws IOException {
+            synchronized (exchangeState) {
+                System.out.println("[proxy->origin] " + exchangeState.id + " produce output");
+                exchangeState.requestDataChannel = channel;
+                if (exchangeState.inBuf != null) {
+                    if (exchangeState.inBuf.hasData()) {
+                        int bytesWritten = exchangeState.inBuf.write(channel);
+                        System.out.println("[proxy->origin] " + exchangeState.id + " " + bytesWritten + " bytes sent");
+                    }
+                    if (exchangeState.inputEnd && !exchangeState.inBuf.hasData()) {
+                        channel.endStream();
+                        System.out.println("[proxy->origin] " + exchangeState.id + " end of output");
+                    }
+                    if (!exchangeState.inputEnd) {
+                        CapacityChannel capacityChannel = exchangeState.requestCapacityChannel;
+                        if (capacityChannel != null) {
+                            int capacity = exchangeState.inBuf.capacity();
+                            if (capacity > 0) {
+                                System.out.println("[client<-proxy] " + exchangeState.id + " input capacity: " + capacity);
+                                capacityChannel.update(capacity);
+                            }
+                        }
                     }
-                    response.setEntity(new NStringEntity(message, ContentType.DEFAULT_TEXT));
-                    responseTrigger.submitResponse(new BasicAsyncResponseProducer(response));
                 }
             }
         }
 
         @Override
-        public boolean cancel() {
-            synchronized (this.httpExchange) {
-                if (this.completed) {
-                    return false;
+        public void consumeResponse(
+                final HttpResponse incomingResponse,
+                final EntityDetails entityDetails) throws HttpException, IOException {
+            synchronized (exchangeState) {
+                System.out.println("[proxy<-origin] " + exchangeState.id + " status " + incomingResponse.getCode());
+
+                HttpResponse outgoingResponse = new BasicHttpResponse(incomingResponse.getCode());
+                for (Iterator<Header> it = incomingResponse.headerIterator(); it.hasNext(); ) {
+                    Header header = it.next();
+                    if (!HOP_BY_HOP.contains(header.getName().toLowerCase(Locale.ROOT))) {
+                        outgoingResponse.addHeader(header);
+                    }
                 }
-                failed(new InterruptedIOException("Cancelled"));
-                return true;
-            }
-        }
 
-        @Override
-        public ProxyHttpExchange getResult() {
-            return this.httpExchange;
-        }
+                exchangeState.response = outgoingResponse;
+                exchangeState.outputEnd = entityDetails == null;
 
-        @Override
-        public Exception getException() {
-            return null;
-        }
+                ResponseChannel responseChannel = exchangeState.responseMessageChannel;
+                responseChannel.sendResponse(
+                        outgoingResponse,
+                        !exchangeState.outputEnd ?  new LazyEntityDetails(outgoingResponse) : null);
 
-        @Override
-        public boolean isDone() {
-            return this.completed;
-        }
-
-    }
-
-    static class ProxyResponseProducer implements HttpAsyncResponseProducer {
-
-        private final ProxyHttpExchange httpExchange;
-
-        public ProxyResponseProducer(final ProxyHttpExchange httpExchange) {
-            super();
-            this.httpExchange = httpExchange;
-        }
-
-        @Override
-        public void close() throws IOException {
-            this.httpExchange.reset();
+                System.out.println("[client<-proxy] " + exchangeState.id + " status " + outgoingResponse.getCode());
+            }
         }
 
         @Override
-        public ClassicHttpResponse generateResponse() {
-            synchronized (this.httpExchange) {
-                ClassicHttpResponse response = this.httpExchange.getResponse();
-                System.out.println("[client<-proxy] " + this.httpExchange.getId() + " " + response.getCode());
-                // Rewrite response!!!!
-                BasicClassicHttpResponse r = new BasicClassicHttpResponse(response.getCode());
-                r.setEntity(response.getEntity());
-                return r;
+        public void updateCapacity(final CapacityChannel capacityChannel) throws IOException {
+            synchronized (exchangeState) {
+                exchangeState.responseCapacityChannel = capacityChannel;
+                int capacity = exchangeState.outBuf != null ? exchangeState.outBuf.capacity() : INIT_BUFFER_SIZE;
+                if (capacity > 0) {
+                    System.out.println("[proxy->origin] " + exchangeState.id + " input capacity: " + capacity);
+                    capacityChannel.update(capacity);
+                }
             }
         }
 
         @Override
-        public void produceContent(
-                final ContentEncoder encoder, final IOControl ioctrl) throws IOException {
-            synchronized (this.httpExchange) {
-                this.httpExchange.setClientIOControl(ioctrl);
-                // Send data to the client
-                ByteBuffer buf = this.httpExchange.getOutBuffer();
-                buf.flip();
-                int n = encoder.write(buf);
-                buf.compact();
-                System.out.println("[client<-proxy] " + this.httpExchange.getId() + " " + n + " bytes written");
-                // If there is space in the buffer and the message has not been
-                // transferred, make sure the origin is sending more data
-                if (buf.hasRemaining() && !this.httpExchange.isResponseReceived()) {
-                    if (this.httpExchange.getOriginIOControl() != null) {
-                        this.httpExchange.getOriginIOControl().requestInput();
-                        System.out.println("[client<-proxy] " + this.httpExchange.getId() + " request origin input");
+        public int consume(final ByteBuffer src) throws IOException {
+            synchronized (exchangeState) {
+                System.out.println("[proxy<-origin] " + exchangeState.id + " " + src.remaining() + " bytes received");
+                DataStreamChannel dataChannel = exchangeState.responseDataChannel;
+                if (dataChannel != null && exchangeState.outBuf != null) {
+                    if (exchangeState.outBuf.hasData()) {
+                        int bytesWritten = exchangeState.outBuf.write(dataChannel);
+                        System.out.println("[client<-proxy] " + exchangeState.id + " " + bytesWritten + " bytes sent");
+                    }
+                    if (!exchangeState.outBuf.hasData()) {
+                        int bytesWritten = dataChannel.write(src);
+                        System.out.println("[client<-proxy] " + exchangeState.id + " " + bytesWritten + " bytes sent");
                     }
                 }
-                if (buf.position() == 0) {
-                    if (this.httpExchange.isResponseReceived()) {
-                        encoder.complete();
-                        System.out.println("[client<-proxy] " + this.httpExchange.getId() + " content fully written");
-                    } else {
-                        // Input buffer is empty. Wait until the origin fills up
-                        // the buffer
-                        ioctrl.suspendOutput();
-                        System.out.println("[client<-proxy] " + this.httpExchange.getId() + " suspend client output");
+                if (src.hasRemaining()) {
+                    if (exchangeState.outBuf == null) {
+                        exchangeState.outBuf = new ProxyBuffer(INIT_BUFFER_SIZE);
                     }
+                    exchangeState.outBuf.put(src);
                 }
+                int capacity = exchangeState.outBuf != null ? exchangeState.outBuf.capacity() : INIT_BUFFER_SIZE;
+                System.out.println("[proxy->origin] " + exchangeState.id + " input capacity: " + capacity);
+                if (dataChannel != null) {
+                    dataChannel.requestOutput();
+                }
+                return capacity;
             }
         }
 
         @Override
-        public void responseCompleted(final HttpContext context) {
-            synchronized (this.httpExchange) {
-                System.out.println("[client<-proxy] " + this.httpExchange.getId() + " response completed");
+        public void streamEnd(final List<Header> trailers) throws HttpException, IOException {
+            synchronized (exchangeState) {
+                System.out.println("[proxy<-origin] " + exchangeState.id + " end of input");
+                exchangeState.outputEnd = true;
+                DataStreamChannel dataChannel = exchangeState.responseDataChannel;
+                if (dataChannel != null && (exchangeState.outBuf == null || !exchangeState.outBuf.hasData())) {
+                    System.out.println("[client<-proxy] " + exchangeState.id + " end of output");
+                    dataChannel.endStream();
+                }
             }
         }
 
         @Override
-        public void failed(final Exception ex) {
-            System.out.println("[client<-proxy] " + ex.toString());
+        public void cancel() {
+            releaseResources();
         }
 
-    }
-
-    static class ProxyIncomingConnectionReuseStrategy extends DefaultConnectionReuseStrategy {
-
         @Override
-        public boolean keepAlive(final HttpRequest request, final HttpResponse response, final HttpContext context) {
-            NHttpConnection conn = (NHttpConnection) context.getAttribute(
-                    HttpCoreContext.HTTP_CONNECTION);
-            boolean keepAlive = super.keepAlive(request, response, context);
-            if (keepAlive) {
-                System.out.println("[client->proxy] connection kept alive " + conn);
-            }
-            return keepAlive;
-        }
+        public void failed(final Exception cause) {
+            System.out.println("[client<-proxy] " + exchangeState.id + " error: " + cause.getMessage());
+            cause.printStackTrace(System.out);
+            synchronized (exchangeState) {
+                if (exchangeState.response == null) {
+                    int status = cause instanceof IOException ? HttpStatus.SC_SERVICE_UNAVAILABLE : HttpStatus.SC_INTERNAL_SERVER_ERROR;
+                    HttpResponse outgoingResponse = new BasicHttpResponse(status);
+                    outgoingResponse.addHeader(HttpHeaders.CONNECTION, HeaderElements.CLOSE);
+                    exchangeState.response = outgoingResponse;
 
-    };
+                    ByteBuffer msg = StandardCharsets.US_ASCII.encode(CharBuffer.wrap(cause.getMessage()));
+                    exchangeState.outBuf = new ProxyBuffer(1024);
+                    exchangeState.outBuf.put(msg);
+                    exchangeState.outputEnd = true;
 
-    static class ProxyOutgoingConnectionReuseStrategy extends DefaultConnectionReuseStrategy {
+                    System.out.println("[client<-proxy] " + exchangeState.id + " status " + outgoingResponse.getCode());
 
-        @Override
-        public boolean keepAlive(final HttpRequest request, final HttpResponse response, final HttpContext context) {
-            NHttpConnection conn = (NHttpConnection) context.getAttribute(
-                    HttpCoreContext.HTTP_CONNECTION);
-            boolean keepAlive = super.keepAlive(request, response, context);
-            if (keepAlive) {
-                System.out.println("[proxy->origin] connection kept alive " + conn);
+                    try {
+                        EntityDetails entityDetails = new BasicEntityDetails(msg.remaining(), ContentType.TEXT_PLAIN);
+                        exchangeState.responseMessageChannel.sendResponse(outgoingResponse, entityDetails);
+                    } catch (HttpException | IOException ignore) {
+                    }
+                } else {
+                    exchangeState.outputEnd = true;
+                }
+                releaseResources();
             }
-            return keepAlive;
-        }
-
-    };
-
-    static class ProxyServiceHandler extends HttpAsyncService {
-
-        public ProxyServiceHandler(
-                final HttpProcessor httpProcessor,
-                final ConnectionReuseStrategy reuseStrategy,
-                final HttpAsyncRequestHandlerMapper handlerResolver) {
-            super(httpProcessor, reuseStrategy, null, handlerResolver, null);
-        }
-
-        @Override
-        protected void log(final Exception ex) {
-            ex.printStackTrace();
-        }
-
-        @Override
-        public void connected(final NHttpServerConnection conn) {
-            System.out.println("[client->proxy] connection open " + conn);
-            super.connected(conn);
         }
 
         @Override
-        public void closed(final NHttpServerConnection conn) {
-            System.out.println("[client->proxy] connection closed " + conn);
-            super.closed(conn);
-        }
-
-    }
-
-    static class ProxyClientProtocolHandler extends HttpAsyncRequestExecutor {
-
-        public ProxyClientProtocolHandler() {
-            super(HttpAsyncRequestExecutor.DEFAULT_WAIT_FOR_CONTINUE, new ProxyOutgoingConnectionReuseStrategy(), null);
-        }
-
-        @Override
-        protected void log(final Exception ex) {
-            ex.printStackTrace();
-        }
-
-        @Override
-        public void connected(final NHttpClientConnection conn,
-                final Object attachment) throws IOException, HttpException {
-            System.out.println("[proxy->origin] connection open " + conn);
-            super.connected(conn, attachment);
-        }
-
-        @Override
-        public void closed(final NHttpClientConnection conn) {
-            System.out.println("[proxy->origin] connection closed " + conn);
-            super.closed(conn);
+        public void releaseResources() {
+            synchronized (exchangeState) {
+                exchangeState.requestDataChannel = null;
+                exchangeState.responseCapacityChannel = null;
+            }
         }
 
     }
 
-    static class ProxyConnPool extends BasicNIOConnPool {
+    private static class ProxyConnPool extends BasicNIOConnPool {
 
-        public ProxyConnPool(
-                final ConnectingIOReactor ioreactor,
+        ProxyConnPool(
+                final ConnectionInitiator connectionInitiator,
                 final int connectTimeout) {
-            super(ioreactor, connectTimeout);
+            super(connectionInitiator, connectTimeout);
         }
 
         @Override
         public void release(final BasicNIOPoolEntry entry, boolean reusable) {
-            System.out.println("[proxy->origin] connection released " + entry.getConnection());
             super.release(entry, reusable);
             StringBuilder buf = new StringBuilder();
             PoolStats totals = getTotalStats();

Modified: httpcomponents/httpcore/trunk/httpcore5/src/examples/org/apache/hc/core5/http/examples/PrintVersionInfo.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/trunk/httpcore5/src/examples/org/apache/hc/core5/http/examples/PrintVersionInfo.java?rev=1767339&r1=1767338&r2=1767339&view=diff
==============================================================================
--- httpcomponents/httpcore/trunk/httpcore5/src/examples/org/apache/hc/core5/http/examples/PrintVersionInfo.java (original)
+++ httpcomponents/httpcore/trunk/httpcore5/src/examples/org/apache/hc/core5/http/examples/PrintVersionInfo.java Mon Oct 31 17:33:27 2016
@@ -41,7 +41,6 @@ public class PrintVersionInfo {
     /** A default list of module packages. */
     private final static String[] MODULE_LIST = {
         "org.apache.http",              // HttpCore
-        "org.apache.http.nio",          // HttpCore NIO
         "org.apache.http.client",       // HttpClient
     };
 

Copied: httpcomponents/httpcore/trunk/httpcore5/src/main/java/org/apache/hc/core5/http/MisdirectedRequestException.java (from r1765384, httpcomponents/httpcore/trunk/httpcore5/src/test/java/org/apache/hc/core5/http/OoopsieRuntimeException.java)
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/trunk/httpcore5/src/main/java/org/apache/hc/core5/http/MisdirectedRequestException.java?p2=httpcomponents/httpcore/trunk/httpcore5/src/main/java/org/apache/hc/core5/http/MisdirectedRequestException.java&p1=httpcomponents/httpcore/trunk/httpcore5/src/test/java/org/apache/hc/core5/http/OoopsieRuntimeException.java&r1=1765384&r2=1767339&rev=1767339&view=diff
==============================================================================
--- httpcomponents/httpcore/trunk/httpcore5/src/test/java/org/apache/hc/core5/http/OoopsieRuntimeException.java (original)
+++ httpcomponents/httpcore/trunk/httpcore5/src/main/java/org/apache/hc/core5/http/MisdirectedRequestException.java Mon Oct 31 17:33:27 2016
@@ -27,12 +27,27 @@
 
 package org.apache.hc.core5.http;
 
-public class OoopsieRuntimeException extends RuntimeException {
+/**
+ * Signals a misdirected request (the server is not authoritative to handle the request).
+ *
+ * @since 5.0
+ */
+public class MisdirectedRequestException extends ProtocolException {
 
-    private static final long serialVersionUID = 662807254163212266L;
+    /**
+     * Creates an exception without a detail message.
+     */
+    public MisdirectedRequestException() {
+        super();
+    }
 
-    public OoopsieRuntimeException() {
-        super("Ooopsie!!!");
+    /**
+     * Creates an exception with the specified detail message.
+     *
+     * @param message The exception detail message
+     */
+    public MisdirectedRequestException(final String message) {
+        super(message);
     }
 
 }

Propchange: httpcomponents/httpcore/trunk/httpcore5/src/main/java/org/apache/hc/core5/http/MisdirectedRequestException.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: httpcomponents/httpcore/trunk/httpcore5/src/main/java/org/apache/hc/core5/http/MisdirectedRequestException.java
------------------------------------------------------------------------------
    svn:keywords = Date Revision

Propchange: httpcomponents/httpcore/trunk/httpcore5/src/main/java/org/apache/hc/core5/http/MisdirectedRequestException.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain