8202423: Small HTTP Client refresh
authorchegar
Wed, 02 May 2018 02:36:17 -0700
changeset 49944 4690a2871b44
parent 49943 8e1ed2a15845
child 49945 9425445633cf
8202423: Small HTTP Client refresh Reviewed-by: chegar, dfuchs, michaelm, prappo Contributed-by: Chris Hegarty <chris.hegarty@oracle.com>, Daniel Fuchs <daniel.fuchs@oracle.com>, Michael McMahon <michael.x.mcmahon@oracle.com>, Pavel Rappo <pavel.rappo@oracle.com>
src/java.net.http/share/classes/jdk/internal/net/http/AsyncSSLConnection.java
src/java.net.http/share/classes/jdk/internal/net/http/AsyncSSLTunnelConnection.java
src/java.net.http/share/classes/jdk/internal/net/http/Http1AsyncReceiver.java
src/java.net.http/share/classes/jdk/internal/net/http/Http1Exchange.java
src/java.net.http/share/classes/jdk/internal/net/http/Http1Request.java
src/java.net.http/share/classes/jdk/internal/net/http/Http1Response.java
src/java.net.http/share/classes/jdk/internal/net/http/Http2ClientImpl.java
src/java.net.http/share/classes/jdk/internal/net/http/Http2Connection.java
src/java.net.http/share/classes/jdk/internal/net/http/HttpClientImpl.java
src/java.net.http/share/classes/jdk/internal/net/http/PlainHttpConnection.java
src/java.net.http/share/classes/jdk/internal/net/http/SocketTube.java
src/java.net.http/share/classes/jdk/internal/net/http/Stream.java
src/java.net.http/share/classes/jdk/internal/net/http/common/BufferSupplier.java
src/java.net.http/share/classes/jdk/internal/net/http/common/ByteBufferPool.java
src/java.net.http/share/classes/jdk/internal/net/http/common/ByteBufferReference.java
src/java.net.http/share/classes/jdk/internal/net/http/common/DebugLogger.java
src/java.net.http/share/classes/jdk/internal/net/http/common/FlowTube.java
src/java.net.http/share/classes/jdk/internal/net/http/common/SSLFlowDelegate.java
src/java.net.http/share/classes/jdk/internal/net/http/common/SSLTube.java
src/java.net.http/share/classes/jdk/internal/net/http/common/SubscriberWrapper.java
src/java.net.http/share/classes/jdk/internal/net/http/common/Utils.java
src/java.net.http/share/classes/jdk/internal/net/http/hpack/Huffman.java
src/java.net.http/share/classes/jdk/internal/net/http/hpack/NaiveHuffman.java
src/java.net.http/share/classes/jdk/internal/net/http/hpack/QuickHuffman.java
src/java.net.http/share/classes/jdk/internal/net/http/hpack/SimpleHeaderTable.java
src/java.net.http/share/classes/jdk/internal/net/http/hpack/StringReader.java
src/java.net.http/share/classes/jdk/internal/net/http/hpack/StringWriter.java
test/jdk/java/net/httpclient/BodyProcessorInputStreamTest.java
test/jdk/java/net/httpclient/DependentPromiseActionsTest.java
test/jdk/java/net/httpclient/EscapedOctetsInURI.java
test/jdk/java/net/httpclient/HttpInputStreamTest.java
test/jdk/java/net/httpclient/NonAsciiCharsInURI.java
test/jdk/java/net/httpclient/ProxyServer.java
test/jdk/java/net/httpclient/RetryWithCookie.java
test/jdk/java/net/httpclient/SmallTimeout.java
test/jdk/java/net/httpclient/TimeoutOrdering.java
test/jdk/java/net/httpclient/http2/java.net.http/jdk/internal/net/http/hpack/CircularBufferTest.java
test/jdk/java/net/httpclient/http2/java.net.http/jdk/internal/net/http/hpack/DecoderTest.java
test/jdk/java/net/httpclient/http2/java.net.http/jdk/internal/net/http/hpack/HuffmanTest.java
test/jdk/java/net/httpclient/http2/server/Http2TestServer.java
test/jdk/java/net/httpclient/websocket/DummyWebSocketServer.java
test/jdk/java/net/httpclient/websocket/PendingBinaryPingClose.java
test/jdk/java/net/httpclient/websocket/PendingBinaryPongClose.java
test/jdk/java/net/httpclient/websocket/PendingPingBinaryClose.java
test/jdk/java/net/httpclient/websocket/PendingPingTextClose.java
test/jdk/java/net/httpclient/websocket/PendingPongBinaryClose.java
test/jdk/java/net/httpclient/websocket/PendingPongTextClose.java
test/jdk/java/net/httpclient/websocket/PendingTextPingClose.java
test/jdk/java/net/httpclient/websocket/PendingTextPongClose.java
test/jdk/java/net/httpclient/websocket/SendTest.java
test/jdk/java/net/httpclient/websocket/WSHandshakeExceptionTest.java
test/jdk/java/net/httpclient/websocket/WebSocketExtendedTest.java
test/jdk/java/net/httpclient/websocket/WebSocketTest.java
--- a/src/java.net.http/share/classes/jdk/internal/net/http/AsyncSSLConnection.java	Wed May 02 10:47:16 2018 +0200
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/AsyncSSLConnection.java	Wed May 02 02:36:17 2018 -0700
@@ -61,6 +61,7 @@
                     // create the SSLTube wrapping the SocketTube, with the given engine
                     flow = new SSLTube(engine,
                                        client().theExecutor(),
+                                       client().getSSLBufferSupplier()::recycle,
                                        plainConnection.getConnectionFlow());
                     return null; } );
     }
--- a/src/java.net.http/share/classes/jdk/internal/net/http/AsyncSSLTunnelConnection.java	Wed May 02 10:47:16 2018 +0200
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/AsyncSSLTunnelConnection.java	Wed May 02 02:36:17 2018 -0700
@@ -65,6 +65,7 @@
                     // create the SSLTube wrapping the SocketTube, with the given engine
                     flow = new SSLTube(engine,
                                        client().theExecutor(),
+                                       client().getSSLBufferSupplier()::recycle,
                                        plainConnection.getConnectionFlow());
                     return null;} );
     }
--- a/src/java.net.http/share/classes/jdk/internal/net/http/Http1AsyncReceiver.java	Wed May 02 10:47:16 2018 +0200
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/Http1AsyncReceiver.java	Wed May 02 02:36:17 2018 -0700
@@ -356,6 +356,7 @@
                 // be left over in the stream.
                 try {
                     setRetryOnError(false);
+                    pending.close(null);
                     onReadError(new IOException("subscription cancelled"));
                     unsubscribe(pending);
                 } finally {
--- a/src/java.net.http/share/classes/jdk/internal/net/http/Http1Exchange.java	Wed May 02 10:47:16 2018 +0200
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/Http1Exchange.java	Wed May 02 02:36:17 2018 -0700
@@ -259,8 +259,19 @@
         if (!connection.connected()) {
             if (debug.on()) debug.log("initiating connect async");
             connectCF = connection.connectAsync();
+            Throwable cancelled;
             synchronized (lock) {
-                operations.add(connectCF);
+                if ((cancelled = failed) == null) {
+                    operations.add(connectCF);
+                }
+            }
+            if (cancelled != null) {
+                if (client.isSelectorThread()) {
+                    executor.execute(() ->
+                        connectCF.completeExceptionally(cancelled));
+                } else {
+                    connectCF.completeExceptionally(cancelled);
+                }
             }
         } else {
             connectCF = new MinimalFuture<>();
@@ -403,6 +414,9 @@
             if ((error = failed) == null) {
                 failed = error = cause;
             }
+            if (debug.on()) {
+                debug.log(request.uri() + ": " + error);
+            }
             if (requestAction != null && requestAction.finished()
                     && response != null && response.finished()) {
                 return;
@@ -447,7 +461,7 @@
                 exec.execute(() -> {
                     if (cf.completeExceptionally(x)) {
                         if (debug.on())
-                            debug.log("completed cf with %s", (Object) x);
+                            debug.log("%s: completed cf with %s", request.uri(), x);
                     }
                 });
             }
--- a/src/java.net.http/share/classes/jdk/internal/net/http/Http1Request.java	Wed May 02 10:47:16 2018 +0200
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/Http1Request.java	Wed May 02 02:36:17 2018 -0700
@@ -200,9 +200,9 @@
             query = "";
         }
         if (query.equals("")) {
-            return path;
+            return Utils.encode(path);
         } else {
-            return path + "?" + query;
+            return Utils.encode(path + "?" + query);
         }
     }
 
--- a/src/java.net.http/share/classes/jdk/internal/net/http/Http1Response.java	Wed May 02 10:47:16 2018 +0200
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/Http1Response.java	Wed May 02 02:36:17 2018 -0700
@@ -375,12 +375,17 @@
                     (t) -> {
                         try {
                             if (t != null) {
-                                subscriber.onError(t);
-                                connection.close();
-                                cf.completeExceptionally(t);
+                                try {
+                                    subscriber.onError(t);
+                                } finally {
+                                    cf.completeExceptionally(t);
+                                }
                             }
                         } finally {
                             bodyReader.onComplete(t);
+                            if (t != null) {
+                                connection.close();
+                            }
                         }
                     }));
                 CompletableFuture<State> bodyReaderCF = bodyReader.completion();
--- a/src/java.net.http/share/classes/jdk/internal/net/http/Http2ClientImpl.java	Wed May 02 10:47:16 2018 +0200
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/Http2ClientImpl.java	Wed May 02 02:36:17 2018 -0700
@@ -95,10 +95,10 @@
         synchronized (this) {
             Http2Connection connection = connections.get(key);
             if (connection != null) {
-                if (connection.closed) {
+                if (connection.closed || !connection.reserveStream(true)) {
                     if (debug.on())
-                        debug.log("removing found closed connection: %s", connection);
-                    connections.remove(key);
+                        debug.log("removing found closed or closing connection: %s", connection);
+                    deleteConnection(connection);
                 } else {
                     // fast path if connection already exists
                     if (debug.on())
@@ -138,9 +138,9 @@
      */
     boolean offerConnection(Http2Connection c) {
         if (debug.on()) debug.log("offering to the connection pool: %s", c);
-        if (c.closed) {
+        if (c.closed || c.finalStream()) {
             if (debug.on())
-                debug.log("skipping offered closed connection: %s", c);
+                debug.log("skipping offered closed or closing connection: %s", c);
             return false;
         }
 
@@ -148,7 +148,7 @@
         synchronized(this) {
             Http2Connection c1 = connections.putIfAbsent(key, c);
             if (c1 != null) {
-                c.setSingleStream(true);
+                c.setFinalStream();
                 if (debug.on())
                     debug.log("existing entry in connection pool for %s", key);
                 return false;
@@ -163,9 +163,12 @@
         if (debug.on())
             debug.log("removing from the connection pool: %s", c);
         synchronized (this) {
-            connections.remove(c.key());
-            if (debug.on())
-                debug.log("removed from the connection pool: %s", c);
+            Http2Connection c1 = connections.get(c.key());
+            if (c1 != null && c1.equals(c)) {
+                connections.remove(c.key());
+                if (debug.on())
+                    debug.log("removed from the connection pool: %s", c);
+            }
         }
     }
 
--- a/src/java.net.http/share/classes/jdk/internal/net/http/Http2Connection.java	Wed May 02 10:47:16 2018 +0200
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/Http2Connection.java	Wed May 02 02:36:17 2018 -0700
@@ -121,33 +121,66 @@
             Utils.getHpackLogger(this::dbgString, Utils.DEBUG_HPACK);
     static final ByteBuffer EMPTY_TRIGGER = ByteBuffer.allocate(0);
 
-    private boolean singleStream; // used only for stream 1, then closed
+    static private final int MAX_CLIENT_STREAM_ID = Integer.MAX_VALUE; // 2147483647
+    static private final int MAX_SERVER_STREAM_ID = Integer.MAX_VALUE - 1; // 2147483646
+
+    /**
+     * Flag set when no more streams to be opened on this connection.
+     * Two cases where it is used.
+     *
+     * 1. Two connections to the same server were opened concurrently, in which
+     *    case one of them will be put in the cache, and the second will expire
+     *    when all its opened streams (which usually should be a single client
+     *    stream + possibly some additional push-promise server streams) complete.
+     * 2. A cached connection reaches its maximum number of streams (~ 2^31-1)
+     *    either server / or client allocated, in which case it will be taken
+     *    out of the cache - allowing a new connection to replace it. It will
+     *    expire when all its still open streams (which could be many) eventually
+     *    complete.
+     */
+    private boolean finalStream;
 
     /*
-     *  ByteBuffer pooling strategy for HTTP/2 protocol:
+     * ByteBuffer pooling strategy for HTTP/2 protocol.
      *
      * In general there are 4 points where ByteBuffers are used:
-     *  - incoming/outgoing frames from/to ByteBuffers plus incoming/outgoing encrypted data
-     *    in case of SSL connection.
+     *  - incoming/outgoing frames from/to ByteBuffers plus incoming/outgoing
+     *    encrypted data in case of SSL connection.
      *
      * 1. Outgoing frames encoded to ByteBuffers.
-     *    Outgoing ByteBuffers are created with requited size and frequently small (except DataFrames, etc)
-     *    At this place no pools at all. All outgoing buffers should be collected by GC.
+     *
+     *  Outgoing ByteBuffers are created with required size and frequently
+     *  small (except DataFrames, etc). At this place no pools at all. All
+     *  outgoing buffers should eventually be collected by GC.
      *
      * 2. Incoming ByteBuffers (decoded to frames).
-     *    Here, total elimination of BB pool is not a good idea.
-     *    We don't know how many bytes we will receive through network.
-     * So here we allocate buffer of reasonable size. The following life of the BB:
-     * - If all frames decoded from the BB are other than DataFrame and HeaderFrame (and HeaderFrame subclasses)
-     *     BB is returned to pool,
-     * - If we decoded DataFrame from the BB. In that case DataFrame refers to subbuffer obtained by slice() method.
-     *     Such BB is never returned to pool and will be GCed.
-     * - If we decoded HeadersFrame from the BB. Then header decoding is performed inside processFrame method and
-     *     the buffer could be release to pool.
+     *
+     *  Here, total elimination of BB pool is not a good idea.
+     *  We don't know how many bytes we will receive through network.
      *
-     * 3. SLL encrypted buffers. Here another pool was introduced and all net buffers are to/from the pool,
-     *    because of we can't predict size encrypted packets.
+     *  A possible future improvement ( currently not implemented ):
+     *  Allocate buffers of reasonable size. The following life of the BB:
+     *   - If all frames decoded from the BB are other than DataFrame and
+     *     HeaderFrame (and HeaderFrame subclasses) BB is returned to pool,
+     *   - If a DataFrame is decoded from the BB. In that case DataFrame refers
+     *     to sub-buffer obtained by slice(). Such a BB is never returned to the
+     *     pool and will eventually be GC'ed.
+     *   - If a HeadersFrame is decoded from the BB. Then header decoding is
+     *     performed inside processFrame method and the buffer could be release
+     *     back to pool.
      *
+     * 3. SSL encrypted buffers ( received ).
+     *
+     *  The current implementation recycles encrypted buffers read from the
+     *  channel. The pool of buffers has a maximum size of 3, SocketTube.MAX_BUFFERS,
+     *  direct buffers which are shared by all connections on a given client.
+     *  The pool is used by all SSL connections - whether HTTP/1.1 or HTTP/2,
+     *  but only for SSL encrypted buffers that circulate between the SocketTube
+     *  Publisher and the SSLFlowDelegate Reader. Limiting the pool to this
+     *  particular segment allows the use of direct buffers, thus avoiding any
+     *  additional copy in the NIO socket channel implementation. See
+     *  HttpClientImpl.SSLDirectBufferSupplier, SocketTube.SSLDirectBufferSource,
+     *  and SSLTube.recycler.
      */
 
 
@@ -220,6 +253,12 @@
     private final Map<Integer,Stream<?>> streams = new ConcurrentHashMap<>();
     private int nextstreamid;
     private int nextPushStream = 2;
+    // actual stream ids are not allocated until the Headers frame is ready
+    // to be sent. The following two fields are updated as soon as a stream
+    // is created and assigned to a connection. They are checked before
+    // assigning a stream to a connection.
+    private int lastReservedClientStreamid = 1;
+    private int lastReservedServerStreamid = 0;
     private final Encoder hpackOut;
     private final Decoder hpackIn;
     final SettingsFrame clientSettings;
@@ -365,6 +404,29 @@
         return client2.client();
     }
 
+    // call these before assigning a request/stream to a connection
+    // if false returned then a new Http2Connection is required
+    // if true, the the stream may be assigned to this connection
+    synchronized boolean reserveStream(boolean clientInitiated) {
+        if (finalStream) {
+            return false;
+        }
+        if (clientInitiated && (lastReservedClientStreamid + 2) >= MAX_CLIENT_STREAM_ID) {
+            setFinalStream();
+            client2.deleteConnection(this);
+            return false;
+        } else if (!clientInitiated && (lastReservedServerStreamid + 2) >= MAX_SERVER_STREAM_ID) {
+            setFinalStream();
+            client2.deleteConnection(this);
+            return false;
+        }
+        if (clientInitiated)
+            lastReservedClientStreamid+=2;
+        else
+            lastReservedServerStreamid+=2;
+        return true;
+    }
+
     /**
      * Throws an IOException if h2 was not negotiated
      */
@@ -414,12 +476,16 @@
                 .thenCompose(checkAlpnCF);
     }
 
-    synchronized boolean singleStream() {
-        return singleStream;
+    synchronized boolean finalStream() {
+        return finalStream;
     }
 
-    synchronized void setSingleStream(boolean use) {
-        singleStream = use;
+    /**
+     * Mark this connection so no more streams created on it and it will close when
+     * all are complete.
+     */
+    synchronized void setFinalStream() {
+        finalStream = true;
     }
 
     static String keyFor(HttpConnection connection) {
@@ -693,6 +759,9 @@
         if (promisedStreamid != nextPushStream) {
             resetStream(promisedStreamid, ResetFrame.PROTOCOL_ERROR);
             return;
+        } else if (!reserveStream(false)) {
+            resetStream(promisedStreamid, ResetFrame.REFUSED_STREAM);
+            return;
         } else {
             nextPushStream += 2;
         }
@@ -752,7 +821,7 @@
             // corresponding entry in the window controller.
             windowController.removeStream(streamid);
         }
-        if (singleStream() && streams.isEmpty()) {
+        if (finalStream() && streams.isEmpty()) {
             // should be only 1 stream, but there might be more if server push
             close();
         }
--- a/src/java.net.http/share/classes/jdk/internal/net/http/HttpClientImpl.java	Wed May 02 10:47:16 2018 +0200
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/HttpClientImpl.java	Wed May 02 02:36:17 2018 -0700
@@ -28,12 +28,12 @@
 import javax.net.ssl.SSLContext;
 import javax.net.ssl.SSLParameters;
 import java.io.IOException;
-import java.lang.System.Logger.Level;
 import java.lang.ref.Reference;
 import java.lang.ref.WeakReference;
 import java.net.Authenticator;
 import java.net.CookieHandler;
 import java.net.ProxySelector;
+import java.nio.ByteBuffer;
 import java.nio.channels.CancelledKeyException;
 import java.nio.channels.ClosedChannelException;
 import java.nio.channels.SelectableChannel;
@@ -47,7 +47,6 @@
 import java.time.Instant;
 import java.time.temporal.ChronoUnit;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedList;
@@ -57,6 +56,7 @@
 import java.util.Set;
 import java.util.TreeSet;
 import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
@@ -70,6 +70,7 @@
 import java.net.http.HttpResponse.BodyHandler;
 import java.net.http.HttpResponse.PushPromiseHandler;
 import java.net.http.WebSocket;
+import jdk.internal.net.http.common.BufferSupplier;
 import jdk.internal.net.http.common.Log;
 import jdk.internal.net.http.common.Logger;
 import jdk.internal.net.http.common.Pair;
@@ -138,6 +139,13 @@
     private final long id;
     private final String dbgTag;
 
+    // The SSL DirectBuffer Supplier provides the ability to recycle
+    // buffers used between the socket reader and the SSLEngine, or
+    // more precisely between the SocketTube publisher and the
+    // SSLFlowDelegate reader.
+    private final SSLDirectBufferSupplier sslBufferSupplier
+            = new SSLDirectBufferSupplier(this);
+
     // This reference is used to keep track of the facade HttpClient
     // that was returned to the application code.
     // It makes it possible to know when the application no longer
@@ -1160,7 +1168,90 @@
     // used for the connection window
     int getReceiveBufferSize() {
         return Utils.getIntegerNetProperty(
-                "jdk.httpclient.receiveBufferSize", 2 * 1024 * 1024
+                "jdk.httpclient.receiveBufferSize",
+                0 // only set the size if > 0
         );
     }
+
+    // Optimization for reading SSL encrypted data
+    // --------------------------------------------
+
+    // Returns a BufferSupplier that can be used for reading
+    // encrypted bytes of the channel. These buffers can then
+    // be recycled by the SSLFlowDelegate::Reader after their
+    // content has been copied in the SSLFlowDelegate::Reader
+    // readBuf.
+    // Because allocating, reading, copying, and recycling
+    // all happen in the SelectorManager thread,
+    // then this BufferSupplier can be shared between all
+    // the SSL connections managed by this client.
+    BufferSupplier getSSLBufferSupplier() {
+        return sslBufferSupplier;
+    }
+
+    // An implementation of BufferSupplier that manages a pool of
+    // maximum 3 direct byte buffers (SocketTube.MAX_BUFFERS) that
+    // are used for reading encrypted bytes off the channel before
+    // copying and subsequent unwrapping.
+    private static final class SSLDirectBufferSupplier implements BufferSupplier {
+        private static final int POOL_SIZE = SocketTube.MAX_BUFFERS;
+        private final ByteBuffer[] pool = new ByteBuffer[POOL_SIZE];
+        private final HttpClientImpl client;
+        private final Logger debug;
+        private int tail, count; // no need for volatile: only accessed in SM thread.
+
+        SSLDirectBufferSupplier(HttpClientImpl client) {
+            this.client = Objects.requireNonNull(client);
+            this.debug = client.debug;
+        }
+
+        // Gets a buffer from the pool, or allocates a new one if needed.
+        @Override
+        public ByteBuffer get() {
+            assert client.isSelectorThread();
+            assert tail <= POOL_SIZE : "allocate tail is " + tail;
+            ByteBuffer buf;
+            if (tail == 0) {
+                if (debug.on()) {
+                    // should not appear more than SocketTube.MAX_BUFFERS
+                    debug.log("ByteBuffer.allocateDirect(%d)", Utils.BUFSIZE);
+                }
+                assert count++ < POOL_SIZE : "trying to allocate more than "
+                            + POOL_SIZE + " buffers";
+                buf = ByteBuffer.allocateDirect(Utils.BUFSIZE);
+            } else {
+                assert tail > 0 : "non positive tail value: " + tail;
+                tail--;
+                buf = pool[tail];
+                pool[tail] = null;
+            }
+            assert buf.isDirect();
+            assert buf.position() == 0;
+            assert buf.hasRemaining();
+            assert buf.limit() == Utils.BUFSIZE;
+            assert tail < POOL_SIZE;
+            assert tail >= 0;
+            return buf;
+        }
+
+        // Returns the given buffer to the pool.
+        @Override
+        public void recycle(ByteBuffer buffer) {
+            assert client.isSelectorThread();
+            assert buffer.isDirect();
+            assert !buffer.hasRemaining();
+            assert tail < POOL_SIZE : "recycle tail is " + tail;
+            assert tail >= 0;
+            buffer.position(0);
+            buffer.limit(buffer.capacity());
+            // don't fail if assertions are off. we have asserted above.
+            if (tail < POOL_SIZE) {
+                pool[tail] = buffer;
+                tail++;
+            }
+            assert tail <= POOL_SIZE;
+            assert tail > 0;
+        }
+    }
+
 }
--- a/src/java.net.http/share/classes/jdk/internal/net/http/PlainHttpConnection.java	Wed May 02 10:47:16 2018 +0200
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/PlainHttpConnection.java	Wed May 02 02:36:17 2018 -0700
@@ -145,31 +145,39 @@
         try {
             this.chan = SocketChannel.open();
             chan.configureBlocking(false);
-            int bufsize = client.getReceiveBufferSize();
-            if (!trySetReceiveBufferSize(bufsize)) {
-                trySetReceiveBufferSize(256*1024);
+            trySetReceiveBufferSize(client.getReceiveBufferSize());
+            if (debug.on()) {
+                int bufsize = getInitialBufferSize();
+                debug.log("Initial receive buffer size is: %d", bufsize);
             }
             chan.setOption(StandardSocketOptions.TCP_NODELAY, true);
-            // wrap the connected channel in a Tube for async reading and writing
+            // wrap the channel in a Tube for async reading and writing
             tube = new SocketTube(client(), chan, Utils::getBuffer);
         } catch (IOException e) {
             throw new InternalError(e);
         }
     }
 
-    private boolean trySetReceiveBufferSize(int bufsize) {
+    private int getInitialBufferSize() {
         try {
-            chan.setOption(StandardSocketOptions.SO_RCVBUF, bufsize);
+            return chan.getOption(StandardSocketOptions.SO_RCVBUF);
+        } catch(IOException x) {
             if (debug.on())
-                debug.log("Receive buffer size is %s",
-                          chan.getOption(StandardSocketOptions.SO_RCVBUF));
-            return true;
+                debug.log("Failed to get initial receive buffer size on %s", chan);
+        }
+        return 0;
+    }
+
+    private void trySetReceiveBufferSize(int bufsize) {
+        try {
+            if (bufsize > 0) {
+                chan.setOption(StandardSocketOptions.SO_RCVBUF, bufsize);
+            }
         } catch(IOException x) {
             if (debug.on())
                 debug.log("Failed to set receive buffer size to %d on %s",
                           bufsize, chan);
         }
-        return false;
     }
 
     @Override
--- a/src/java.net.http/share/classes/jdk/internal/net/http/SocketTube.java	Wed May 02 10:47:16 2018 +0200
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/SocketTube.java	Wed May 02 02:36:17 2018 -0700
@@ -26,7 +26,6 @@
 package jdk.internal.net.http;
 
 import java.io.IOException;
-import java.lang.System.Logger.Level;
 import java.nio.ByteBuffer;
 import java.util.List;
 import java.util.Objects;
@@ -39,6 +38,7 @@
 import java.util.ArrayList;
 import java.util.function.Consumer;
 import java.util.function.Supplier;
+import jdk.internal.net.http.common.BufferSupplier;
 import jdk.internal.net.http.common.Demand;
 import jdk.internal.net.http.common.FlowTube;
 import jdk.internal.net.http.common.Logger;
@@ -59,7 +59,7 @@
 
     private final HttpClientImpl client;
     private final SocketChannel channel;
-    private final Supplier<ByteBuffer> buffersSource;
+    private final SliceBufferSource sliceBuffersSource;
     private final Object lock = new Object();
     private final AtomicReference<Throwable> errorRef = new AtomicReference<>();
     private final InternalReadPublisher readPublisher;
@@ -67,10 +67,11 @@
     private final long id = IDS.incrementAndGet();
 
     public SocketTube(HttpClientImpl client, SocketChannel channel,
-                      Supplier<ByteBuffer> buffersSource) {
+                      Supplier<ByteBuffer> buffersFactory) {
         this.client = client;
         this.channel = channel;
-        this.buffersSource = buffersSource;
+        this.sliceBuffersSource = new SliceBufferSource(buffersFactory);
+
         this.readPublisher = new InternalReadPublisher();
         this.writeSubscriber = new InternalWriteSubscriber();
     }
@@ -564,6 +565,7 @@
             final InternalReadSubscription impl;
             final TubeSubscriber  subscriber;
             final AtomicReference<Throwable> errorRef = new AtomicReference<>();
+            final BufferSource bufferSource;
             volatile boolean subscribed;
             volatile boolean cancelled;
             volatile boolean completed;
@@ -571,6 +573,9 @@
             public ReadSubscription(InternalReadSubscription impl,
                                     TubeSubscriber subscriber) {
                 this.impl = impl;
+                this.bufferSource = subscriber.supportsRecycling()
+                        ? new SSLDirectBufferSource(client)
+                        : SocketTube.this.sliceBuffersSource;
                 this.subscriber = subscriber;
             }
 
@@ -779,7 +784,7 @@
                         if (demand.tryDecrement()) {
                             // we have demand.
                             try {
-                                List<ByteBuffer> bytes = readAvailable();
+                                List<ByteBuffer> bytes = readAvailable(current.bufferSource);
                                 if (bytes == EOF) {
                                     if (!completed) {
                                         if (debug.on()) debug.log("got read EOF");
@@ -906,6 +911,180 @@
     }
 
     // ===================================================================== //
+    //                       Buffer Management                               //
+    // ===================================================================== //
+
+    // This interface is used by readAvailable(BufferSource);
+    public interface BufferSource {
+        /**
+         * Returns a buffer to read data from the socket.
+         *
+         * @implNote
+         * Different implementation can have different strategies, as to
+         * which kind of buffer to return, or whether to return the same
+         * buffer. The only constraints are that:
+         *   a. the buffer returned must not be null
+         *   b. the buffer position indicates where to start reading
+         *   c. the buffer limit indicates where to stop reading.
+         *   d. the buffer is 'free' - that is - it is not used
+         *      or retained by anybody else
+         *
+         * @return A buffer to read data from the socket.
+         */
+        ByteBuffer getBuffer();
+
+        /**
+         * Appends the read-data in {@code buffer} to the list of buffer to
+         * be sent downstream to the subscriber. May return a new
+         * list, or append to the given list.
+         *
+         * @implNote
+         * Different implementation can have different strategies, but
+         * must obviously be consistent with the implementation of the
+         * getBuffer() method. For instance, an implementation could
+         * decide to add the buffer to the list and return a new buffer
+         * next time getBuffer() is called, or could decide to add a buffer
+         * slice to the list and return the same buffer (if remaining
+         * space is available) next time getBuffer() is called.
+         *
+         * @param list    The list before adding the data. Can be null.
+         * @param buffer  The buffer containing the data to add to the list.
+         * @param start   The start position at which data were read.
+         *                The current buffer position indicates the end.
+         * @return A possibly new list where a buffer containing the
+         *         data read from the socket has been added.
+         */
+        List<ByteBuffer> append(List<ByteBuffer> list, ByteBuffer buffer, int start);
+
+        /**
+         * Returns the given unused {@code buffer}, previously obtained from
+         * {@code getBuffer}.
+         *
+         * @implNote This method can be used, if necessary, to return
+         *  the unused buffer to the pull.
+         *
+         * @param buffer The unused buffer.
+         */
+        default void returnUnused(ByteBuffer buffer) { }
+    }
+
+    // An implementation of BufferSource used for unencrypted data.
+    // This buffer source uses heap buffers and avoids wasting memory
+    // by forwarding read-only buffer slices downstream.
+    // Buffers allocated through this source are simply GC'ed when
+    // they are no longer referenced.
+    private static final class SliceBufferSource implements BufferSource {
+        private final Supplier<ByteBuffer> factory;
+        private volatile ByteBuffer current;
+
+        public SliceBufferSource() {
+            this(Utils::getBuffer);
+        }
+        public SliceBufferSource(Supplier<ByteBuffer> factory) {
+            this.factory = Objects.requireNonNull(factory);
+        }
+
+        // Reuses the same buffer if some space remains available.
+        // Otherwise, returns a new heap buffer.
+        @Override
+        public final ByteBuffer getBuffer() {
+            ByteBuffer buf = current;
+            buf = (buf == null || !buf.hasRemaining())
+                    ? (current = factory.get()) : buf;
+            assert buf.hasRemaining();
+            return buf;
+        }
+
+        // Adds a read-only slice to the list, potentially returning a
+        // new list with that slice at the end.
+        @Override
+        public final List<ByteBuffer> append(List <ByteBuffer> list, ByteBuffer buf, int start) {
+            // creates a slice to add to the list
+            int limit = buf.limit();
+            buf.limit(buf.position());
+            buf.position(start);
+            ByteBuffer slice = buf.slice();
+
+            // restore buffer state to what it was before creating the slice
+            buf.position(buf.limit());
+            buf.limit(limit);
+
+            // add the buffer to the list
+            return SocketTube.listOf(list, slice.asReadOnlyBuffer());
+        }
+    }
+
+
+    // An implementation of BufferSource used for encrypted data.
+    // This buffer source uses direct byte buffers that will be
+    // recycled by the SocketTube subscriber.
+    //
+    private static final class SSLDirectBufferSource implements BufferSource {
+        private final BufferSupplier factory;
+        private final HttpClientImpl client;
+        private ByteBuffer current;
+
+        public SSLDirectBufferSource(HttpClientImpl client) {
+            this.client = Objects.requireNonNull(client);
+            this.factory = Objects.requireNonNull(client.getSSLBufferSupplier());
+        }
+
+        // Obtains a 'free' byte buffer from the pool, or returns
+        // the same buffer if nothing was read at the previous cycle.
+        // The subscriber will be responsible for recycling this
+        // buffer into the pool (see SSLFlowDelegate.Reader)
+        @Override
+        public final ByteBuffer getBuffer() {
+            assert client.isSelectorThread();
+            ByteBuffer buf = current;
+            if (buf == null) {
+                buf = current = factory.get();
+            }
+            assert buf.hasRemaining();
+            assert buf.position() == 0;
+            return buf;
+        }
+
+        // Adds the buffer to the list. The buffer will be later returned to the
+        // pool by the subscriber (see SSLFlowDelegate.Reader).
+        // The next buffer returned by getBuffer() will be obtained from the
+        // pool. It might be the same buffer or another one.
+        // Because socket tube can read up to MAX_BUFFERS = 3 buffers, and because
+        // recycling will happen in the flow before onNext returns, then the
+        // pool can not grow larger than MAX_BUFFERS = 3 buffers, even though
+        // it's shared by all SSL connections opened on that client.
+        @Override
+        public final List<ByteBuffer> append(List <ByteBuffer> list, ByteBuffer buf, int start) {
+            assert client.isSelectorThread();
+            assert buf.isDirect();
+            assert start == 0;
+            assert current == buf;
+            current = null;
+            buf.limit(buf.position());
+            buf.position(start);
+            // add the buffer to the list
+            return SocketTube.listOf(list, buf);
+        }
+
+        @Override
+        public void returnUnused(ByteBuffer buffer) {
+            // if current is null, then the buffer will have been added to the
+            // list, through append. Otherwise, current is not null, and needs
+            // to be returned to prevent the buffer supplier pool from growing
+            // to more than MAX_BUFFERS.
+            assert buffer == current;
+            ByteBuffer buf = current;
+            if (buf != null) {
+                assert buf.position() == 0;
+                current = null;
+                // the supplier assert if buf has remaining
+                buf.limit(buf.position());
+                factory.recycle(buf);
+            }
+        }
+    }
+
+    // ===================================================================== //
     //                   Socket Channel Read/Write                           //
     // ===================================================================== //
     static final int MAX_BUFFERS = 3;
@@ -918,11 +1097,8 @@
     // is inserted into the returned buffer list, and if the current buffer
     // has remaining space, that space will be used to read more data when
     // the channel becomes readable again.
-    private volatile ByteBuffer current;
-    private List<ByteBuffer> readAvailable() throws IOException {
-        ByteBuffer buf = current;
-        buf = (buf == null || !buf.hasRemaining())
-                ? (current = buffersSource.get()) : buf;
+    private List<ByteBuffer> readAvailable(BufferSource buffersSource) throws IOException {
+        ByteBuffer buf = buffersSource.getBuffer();
         assert buf.hasRemaining();
 
         int read;
@@ -936,6 +1112,9 @@
                 }
             } catch (IOException x) {
                 if (buf.position() == pos && list == null) {
+                    // make sure that the buffer source will recycle
+                    // 'buf' if needed
+                    buffersSource.returnUnused(buf);
                     // no bytes have been read, just throw...
                     throw x;
                 } else {
@@ -951,6 +1130,7 @@
                 // returned if read == -1. If some data has already been read,
                 // then it must be returned. -1 will be returned next time
                 // the caller attempts to read something.
+                buffersSource.returnUnused(buf);
                 if (list == null) {
                     // nothing read - list was null - return EOF or NOTHING
                     list = read == -1 ? EOF : NOTHING;
@@ -960,39 +1140,27 @@
 
             // check whether this buffer has still some free space available.
             // if so, we will keep it for the next round.
-            final boolean hasRemaining = buf.hasRemaining();
+            list = buffersSource.append(list, buf, pos);
 
-            // creates a slice to add to the list
-            int limit = buf.limit();
-            buf.limit(buf.position());
-            buf.position(pos);
-            ByteBuffer slice = buf.slice();
-
-            // restore buffer state to what it was before creating the slice
-            buf.position(buf.limit());
-            buf.limit(limit);
-
-            // add the buffer to the list
-            list = addToList(list, slice.asReadOnlyBuffer());
             if (read <= 0 || list.size() == MAX_BUFFERS) {
                 break;
             }
 
-            buf = hasRemaining ? buf : (current = buffersSource.get());
+            buf = buffersSource.getBuffer();
             pos = buf.position();
             assert buf.hasRemaining();
         }
         return list;
     }
 
-    private <T> List<T> addToList(List<T> list, T item) {
+    private static <T> List<T> listOf(List<T> list, T item) {
         int size = list == null ? 0 : list.size();
         switch (size) {
             case 0: return List.of(item);
             case 1: return List.of(list.get(0), item);
             case 2: return List.of(list.get(0), list.get(1), item);
             default: // slow path if MAX_BUFFERS > 3
-                ArrayList<T> res = new ArrayList<>(list);
+                List<T> res = list instanceof ArrayList ? list : new ArrayList<>(list);
                 res.add(item);
                 return res;
         }
--- a/src/java.net.http/share/classes/jdk/internal/net/http/Stream.java	Wed May 02 10:47:16 2018 +0200
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/Stream.java	Wed May 02 02:36:17 2018 -0700
@@ -614,7 +614,7 @@
         if (query != null) {
             path += "?" + query;
         }
-        hdrs.setHeader(":path", path);
+        hdrs.setHeader(":path", Utils.encode(path));
     }
 
     HttpHeadersImpl getRequestPseudoHeaders() {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/common/BufferSupplier.java	Wed May 02 02:36:17 2018 -0700
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.internal.net.http.common;
+
+import java.nio.ByteBuffer;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.function.Supplier;
+
+/**
+ *  This interface allows to recycle buffers used for SSL decryption.
+ *  Buffers that are used for reading SSL encrypted data are typically
+ *  very short lived, as it is necessary to aggregate their content
+ *  before calling SSLEngine::unwrap.
+ *  Because both reading and copying happen in the SelectorManager
+ *  thread, then it makes it possible to pool these buffers and
+ *  recycle them for the next socket read, instead of simply
+ *  letting them be GC'ed. That also makes it possible to use
+ *  direct byte buffers, and avoid another layer of copying by
+ *  the SocketChannel implementation.
+ *
+ *  The HttpClientImpl has an implementation of this interface
+ *  that allows to reuse the same 3 direct buffers for reading
+ *  off SSL encrypted data from the socket.
+ *  The BufferSupplier::get method is called by SocketTube
+ *  (see SocketTube.SSLDirectBufferSource) and BufferSupplier::recycle
+ *  is called by SSLFlowDelegate.Reader.
+ **/
+public interface BufferSupplier extends Supplier<ByteBuffer> {
+    /**
+     * Returns a buffer to read encrypted data off the socket.
+     * @return a buffer to read encrypted data off the socket.
+     */
+    ByteBuffer get();
+
+    /**
+     * Returns a buffer to the pool.
+     *
+     * @param buffer This must be a buffer previously obtained
+     *               by calling BufferSupplier::get. The caller must
+     *               not touch the buffer after returning it to
+     *               the pool.
+     */
+    void recycle(ByteBuffer buffer);
+}
+
--- a/src/java.net.http/share/classes/jdk/internal/net/http/common/ByteBufferPool.java	Wed May 02 10:47:16 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,60 +0,0 @@
-/*
- * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-package jdk.internal.net.http.common;
-
-import java.nio.ByteBuffer;
-import java.util.concurrent.ConcurrentLinkedQueue;
-
-/**
- * The class provides reuse of ByteBuffers.
- * It is supposed that all requested buffers have the same size for a long period of time.
- * That is why there is no any logic splitting buffers into different buckets (by size). It's unnecessary.
- *
- * At the same moment it is allowed to change requested buffers size (all smaller buffers will be discarded).
- * It may be needed for example, if after rehandshaking netPacketBufferSize was changed.
- */
-public class ByteBufferPool {
-
-    private final java.util.Queue<ByteBuffer> pool = new ConcurrentLinkedQueue<>();
-
-    public ByteBufferPool() {
-    }
-
-    public ByteBufferReference get(int size) {
-        ByteBuffer buffer;
-        while ((buffer = pool.poll()) != null) {
-            if (buffer.capacity() >= size) {
-                return ByteBufferReference.of(buffer, this);
-            }
-        }
-        return ByteBufferReference.of(ByteBuffer.allocate(size), this);
-    }
-
-    public void release(ByteBuffer buffer) {
-        buffer.clear();
-        pool.offer(buffer);
-    }
-
-}
--- a/src/java.net.http/share/classes/jdk/internal/net/http/common/ByteBufferReference.java	Wed May 02 10:47:16 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,88 +0,0 @@
-/*
- * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-package jdk.internal.net.http.common;
-
-import java.nio.ByteBuffer;
-import java.util.Objects;
-import java.util.function.Supplier;
-
-public class ByteBufferReference  implements Supplier<ByteBuffer> {
-
-    private ByteBuffer buffer;
-    private final ByteBufferPool pool;
-
-    public static ByteBufferReference of(ByteBuffer buffer) {
-        return of(buffer, null);
-    }
-
-    public static ByteBufferReference of(ByteBuffer buffer, ByteBufferPool pool) {
-        Objects.requireNonNull(buffer);
-        return new ByteBufferReference(buffer, pool);
-    }
-
-    public static ByteBuffer[] toBuffers(ByteBufferReference... refs) {
-        ByteBuffer[] bufs = new ByteBuffer[refs.length];
-        for (int i = 0; i < refs.length; i++) {
-            bufs[i] = refs[i].get();
-        }
-        return bufs;
-    }
-
-    public static ByteBufferReference[] toReferences(ByteBuffer... buffers) {
-        ByteBufferReference[] refs = new ByteBufferReference[buffers.length];
-        for (int i = 0; i < buffers.length; i++) {
-            refs[i] = of(buffers[i]);
-        }
-        return refs;
-    }
-
-
-    public static void clear(ByteBufferReference[] refs) {
-        for(ByteBufferReference ref : refs) {
-            ref.clear();
-        }
-    }
-
-    private ByteBufferReference(ByteBuffer buffer, ByteBufferPool pool) {
-        this.buffer = buffer;
-        this.pool = pool;
-    }
-
-    @Override
-    public ByteBuffer get() {
-        ByteBuffer buf = this.buffer;
-        assert buf!=null : "getting ByteBuffer after clearance";
-        return buf;
-    }
-
-    public void clear() {
-        ByteBuffer buf = this.buffer;
-        assert buf!=null : "double ByteBuffer clearance";
-        this.buffer = null;
-        if (pool != null) {
-            pool.release(buf);
-        }
-    }
-}
--- a/src/java.net.http/share/classes/jdk/internal/net/http/common/DebugLogger.java	Wed May 02 10:47:16 2018 +0200
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/common/DebugLogger.java	Wed May 02 02:36:17 2018 -0700
@@ -52,6 +52,12 @@
     final static System.Logger HTTP = System.getLogger(HTTP_NAME);
     final static System.Logger WS = System.getLogger(WS_NAME);
     final static System.Logger HPACK = System.getLogger(HPACK_NAME);
+    private static final DebugLogger NO_HTTP_LOGGER =
+            new DebugLogger(HTTP, "HTTP"::toString, Level.OFF, Level.OFF);
+    private static final DebugLogger NO_WS_LOGGER =
+            new DebugLogger(HTTP, "WS"::toString, Level.OFF, Level.OFF);
+    private static final DebugLogger NO_HPACK_LOGGER =
+            new DebugLogger(HTTP, "HPACK"::toString, Level.OFF, Level.OFF);
     final static long START_NANOS = System.nanoTime();
 
     private final Supplier<String> dbgTag;
@@ -112,6 +118,16 @@
     }
 
     private boolean isEnabled(Level level) {
+        return levelEnabledFor(level, outLevel, errLevel, logger);
+    }
+
+    @Override
+    public final boolean on() {
+        return debugOn;
+    }
+
+    static boolean levelEnabledFor(Level level, Level outLevel, Level errLevel,
+                                   System.Logger logger) {
         if (level == Level.OFF) return false;
         int severity = level.getSeverity();
         return severity >= errLevel.getSeverity()
@@ -120,11 +136,6 @@
     }
 
     @Override
-    public final boolean on() {
-        return debugOn;
-    }
-
-    @Override
     public boolean isLoggable(Level level) {
         // fast path, we assume these guys never change.
         // support only static configuration.
@@ -251,18 +262,33 @@
     public static DebugLogger createHttpLogger(Supplier<String> dbgTag,
                                                Level outLevel,
                                                Level errLevel) {
-        return new DebugLogger(HTTP, dbgTag, outLevel, errLevel);
+        if (levelEnabledFor(Level.DEBUG, outLevel, errLevel, HTTP)) {
+            return new DebugLogger(HTTP, dbgTag, outLevel, errLevel);
+        } else {
+            // return a shared instance if debug logging is not enabled.
+            return NO_HTTP_LOGGER;
+        }
     }
 
     public static DebugLogger createWebSocketLogger(Supplier<String> dbgTag,
                                                     Level outLevel,
                                                     Level errLevel) {
-        return new DebugLogger(WS, dbgTag, outLevel, errLevel);
+        if (levelEnabledFor(Level.DEBUG, outLevel, errLevel, WS)) {
+            return new DebugLogger(WS, dbgTag, outLevel, errLevel);
+        } else {
+            // return a shared instance if logging is not enabled.
+            return NO_WS_LOGGER;
+        }
     }
 
     public static DebugLogger createHpackLogger(Supplier<String> dbgTag,
                                                 Level outLevel,
                                                 Level errLevel) {
-        return new DebugLogger(HPACK, dbgTag, outLevel, errLevel);
+        if (levelEnabledFor(Level.DEBUG, outLevel, errLevel, HPACK)) {
+            return new DebugLogger(HPACK, dbgTag, outLevel, errLevel);
+        } else {
+            // return a shared instance if logging is not enabled.
+            return NO_HPACK_LOGGER;
+        }
     }
 }
--- a/src/java.net.http/share/classes/jdk/internal/net/http/common/FlowTube.java	Wed May 02 10:47:16 2018 +0200
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/common/FlowTube.java	Wed May 02 02:36:17 2018 -0700
@@ -63,6 +63,8 @@
          */
         default void dropSubscription() { }
 
+        default boolean supportsRecycling() { return false; }
+
     }
 
     /**
--- a/src/java.net.http/share/classes/jdk/internal/net/http/common/SSLFlowDelegate.java	Wed May 02 10:47:16 2018 +0200
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/common/SSLFlowDelegate.java	Wed May 02 02:36:17 2018 -0700
@@ -33,7 +33,6 @@
 import javax.net.ssl.SSLEngineResult.Status;
 import javax.net.ssl.SSLException;
 import java.io.IOException;
-import java.lang.System.Logger.Level;
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -46,6 +45,7 @@
 import java.util.concurrent.Flow;
 import java.util.concurrent.Flow.Subscriber;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
 
 /**
  * Implements SSL using two SubscriberWrappers.
@@ -97,6 +97,7 @@
     volatile boolean close_notify_received;
     final CompletableFuture<Void> readerCF;
     final CompletableFuture<Void> writerCF;
+    final Consumer<ByteBuffer> recycler;
     static AtomicInteger scount = new AtomicInteger(1);
     final int id;
 
@@ -110,8 +111,23 @@
                            Subscriber<? super List<ByteBuffer>> downReader,
                            Subscriber<? super List<ByteBuffer>> downWriter)
     {
+        this(engine, exec, null, downReader, downWriter);
+    }
+
+    /**
+     * Creates an SSLFlowDelegate fed from two Flow.Subscribers. Each
+     * Flow.Subscriber requires an associated {@link CompletableFuture}
+     * for errors that need to be signaled from downstream to upstream.
+     */
+    public SSLFlowDelegate(SSLEngine engine,
+            Executor exec,
+            Consumer<ByteBuffer> recycler,
+            Subscriber<? super List<ByteBuffer>> downReader,
+            Subscriber<? super List<ByteBuffer>> downWriter)
+        {
         this.id = scount.getAndIncrement();
         this.tubeName = String.valueOf(downWriter);
+        this.recycler = recycler;
         this.reader = new Reader();
         this.writer = new Writer();
         this.engine = engine;
@@ -181,9 +197,11 @@
         sb.append("SSL: id ").append(id);
         sb.append(" HS state: " + states(handshakeState));
         sb.append(" Engine state: " + engine.getHandshakeStatus().toString());
-        sb.append(" LL : ");
-        for (String s: stateList) {
-            sb.append(s).append(" ");
+        if (stateList != null) {
+            sb.append(" LL : ");
+            for (String s : stateList) {
+                sb.append(s).append(" ");
+            }
         }
         sb.append("\r\n");
         sb.append("Reader:: ").append(reader.toString());
@@ -213,15 +231,20 @@
      * Upstream subscription strategy is to try and keep no more than
      * TARGET_BUFSIZE bytes in readBuf
      */
-    class Reader extends SubscriberWrapper {
+    final class Reader extends SubscriberWrapper implements FlowTube.TubeSubscriber {
+        // Maximum record size is 16k.
+        // Because SocketTube can feeds us up to 3 16K buffers,
+        // then setting this size to 16K means that the readBuf
+        // can store up to 64K-1 (16K-1 + 3*16K)
+        static final int TARGET_BUFSIZE = 16 * 1024;
+
         final SequentialScheduler scheduler;
-        static final int TARGET_BUFSIZE = 16 * 1024;
         volatile ByteBuffer readBuf;
         volatile boolean completing;
         final Object readBufferLock = new Object();
         final Logger debugr = Utils.getDebugLogger(this::dbgString, Utils.DEBUG);
 
-        class ReaderDownstreamPusher implements Runnable {
+        private final class ReaderDownstreamPusher implements Runnable {
             @Override public void run() { processData(); }
         }
 
@@ -233,6 +256,11 @@
             readBuf.limit(0); // keep in read mode
         }
 
+        @Override
+        public boolean supportsRecycling() {
+            return recycler != null;
+        }
+
         protected SchedulingAction enterScheduling() {
             return enterReadScheduling();
         }
@@ -250,7 +278,7 @@
                 debugr.log("Adding %d bytes to read buffer",
                            Utils.remaining(buffers));
             addToReadBuf(buffers, complete);
-            scheduler.runOrSchedule();
+            scheduler.runOrSchedule(exec);
         }
 
         @Override
@@ -270,6 +298,9 @@
         @Override
         protected long upstreamWindowUpdate(long currentWindow, long downstreamQsize) {
             if (readBuf.remaining() > TARGET_BUFSIZE) {
+                if (debugr.on())
+                    debugr.log("readBuf has more than TARGET_BUFSIZE: %d",
+                               readBuf.remaining());
                 return 0;
             } else {
                 return super.upstreamWindowUpdate(currentWindow, downstreamQsize);
@@ -285,6 +316,11 @@
                         reallocReadBuf();
                     readBuf.put(buf);
                     readBuf.flip();
+                    // should be safe to call inside lock
+                    // since the only implementation
+                    // offers the buffer to an unbounded queue.
+                    // WARNING: do not touch buf after this point!
+                    if (recycler != null) recycler.accept(buf);
                 }
                 if (complete) {
                     this.completing = complete;
@@ -293,7 +329,7 @@
         }
 
         void schedule() {
-            scheduler.runOrSchedule();
+            scheduler.runOrSchedule(exec);
         }
 
         void stop() {
@@ -303,8 +339,13 @@
 
         AtomicInteger count = new AtomicInteger(0);
 
+        // minimum number of bytes required to call unwrap.
+        // Usually this is 0, unless there was a buffer underflow.
+        // In this case we need to wait for more bytes than what
+        // we had before calling unwrap() again.
+        volatile int minBytesRequired;
         // work function where it all happens
-        void processData() {
+        final void processData() {
             try {
                 if (debugr.on())
                     debugr.log("processData:"
@@ -313,15 +354,23 @@
                            + ", engine handshake status:" + engine.getHandshakeStatus());
                 int len;
                 boolean complete = false;
-                while ((len = readBuf.remaining()) > 0) {
+                while (readBuf.remaining() > (len = minBytesRequired)) {
                     boolean handshaking = false;
                     try {
                         EngineResult result;
                         synchronized (readBufferLock) {
                             complete = this.completing;
+                            if (debugr.on()) debugr.log("Unwrapping: %s", readBuf.remaining());
+                            // Unless there is a BUFFER_UNDERFLOW, we should try to
+                            // unwrap any number of bytes. Set minBytesRequired to 0:
+                            // we only need to do that if minBytesRequired is not already 0.
+                            len = len > 0 ? minBytesRequired = 0 : len;
                             result = unwrapBuffer(readBuf);
-                            if (debugr.on())
-                                debugr.log("Unwrapped: %s", result.result);
+                            len = readBuf.remaining();
+                            if (debugr.on()) {
+                                debugr.log("Unwrapped: result: %s", result.result);
+                                debugr.log("Unwrapped: consumed: %s", result.bytesConsumed());
+                            }
                         }
                         if (result.bytesProduced() > 0) {
                             if (debugr.on())
@@ -332,12 +381,19 @@
                         if (result.status() == Status.BUFFER_UNDERFLOW) {
                             if (debugr.on()) debugr.log("BUFFER_UNDERFLOW");
                             // not enough data in the read buffer...
-                            requestMore();
+                            // no need to try to unwrap again unless we get more bytes
+                            // than minBytesRequired = len in the read buffer.
+                            minBytesRequired = len;
                             synchronized (readBufferLock) {
-                                // check if we have received some data
+                                // more bytes could already have been added...
+                                assert readBuf.remaining() >= len;
+                                // check if we have received some data, and if so
+                                // we can just re-spin the loop
                                 if (readBuf.remaining() > len) continue;
-                                return;
                             }
+                            // request more data and return.
+                            requestMore();
+                            return;
                         }
                         if (complete && result.status() == Status.CLOSED) {
                             if (debugr.on()) debugr.log("Closed: completing");
@@ -352,8 +408,10 @@
                             handshaking = true;
                         } else {
                             if ((handshakeState.getAndSet(NOT_HANDSHAKING)& ~DOING_TASKS) == HANDSHAKING) {
+                                handshaking = false;
+                                applicationBufferSize = engine.getSession().getApplicationBufferSize();
+                                packetBufferSize = engine.getSession().getPacketBufferSize();
                                 setALPN();
-                                handshaking = false;
                                 resumeActivity();
                             }
                         }
@@ -391,7 +449,8 @@
                     case BUFFER_OVERFLOW:
                         // may happen only if app size buffer was changed.
                         // get it again if app buffer size changed
-                        int appSize = engine.getSession().getApplicationBufferSize();
+                        int appSize = applicationBufferSize =
+                                engine.getSession().getApplicationBufferSize();
                         ByteBuffer b = ByteBuffer.allocate(appSize + dst.position());
                         dst.flip();
                         b.put(dst);
@@ -489,7 +548,7 @@
 
         @Override
         protected void incoming(List<ByteBuffer> buffers, boolean complete) {
-            assert complete ? buffers ==  Utils.EMPTY_BB_LIST : true;
+            assert complete ? buffers == Utils.EMPTY_BB_LIST : true;
             assert buffers != Utils.EMPTY_BB_LIST ? complete == false : true;
             if (complete) {
                 if (debugw.on()) debugw.log("adding SENTINEL");
@@ -549,6 +608,15 @@
             }
         }
 
+        void triggerWrite() {
+            synchronized (writeList) {
+                if (writeList.isEmpty()) {
+                    writeList.add(HS_TRIGGER);
+                }
+            }
+            scheduler.runOrSchedule();
+        }
+
         private void processData() {
             boolean completing = isCompleting();
 
@@ -586,6 +654,8 @@
                         handshaking = true;
                     } else {
                         if ((handshakeState.getAndSet(NOT_HANDSHAKING) & ~DOING_TASKS) == HANDSHAKING) {
+                            applicationBufferSize = engine.getSession().getApplicationBufferSize();
+                            packetBufferSize = engine.getSession().getPacketBufferSize();
                             setALPN();
                             resumeActivity();
                         }
@@ -630,8 +700,9 @@
                         // Shouldn't happen. We allocated buffer with packet size
                         // get it again if net buffer size was changed
                         if (debugw.on()) debugw.log("BUFFER_OVERFLOW");
-                        int appSize = engine.getSession().getApplicationBufferSize();
-                        ByteBuffer b = ByteBuffer.allocate(appSize + dst.position());
+                        int netSize = packetBufferSize
+                                = engine.getSession().getPacketBufferSize();
+                        ByteBuffer b = ByteBuffer.allocate(netSize + dst.position());
                         dst.flip();
                         b.put(dst);
                         dst = b;
@@ -759,13 +830,16 @@
     }
 
     final AtomicInteger handshakeState;
-    final ConcurrentLinkedQueue<String> stateList = new ConcurrentLinkedQueue<>();
+    final ConcurrentLinkedQueue<String> stateList =
+            debug.on() ? new ConcurrentLinkedQueue<>() : null;
 
     private boolean doHandshake(EngineResult r, int caller) {
         // unconditionally sets the HANDSHAKING bit, while preserving DOING_TASKS
         handshakeState.getAndAccumulate(HANDSHAKING, (current, update) -> update | (current & DOING_TASKS));
-        stateList.add(r.handshakeStatus().toString());
-        stateList.add(Integer.toString(caller));
+        if (stateList != null && debug.on()) {
+            stateList.add(r.handshakeStatus().toString());
+            stateList.add(Integer.toString(caller));
+        }
         switch (r.handshakeStatus()) {
             case NEED_TASK:
                 int s = handshakeState.getAndUpdate((current) -> current | DOING_TASKS);
@@ -778,7 +852,7 @@
                 return false;  // executeTasks will resume activity
             case NEED_WRAP:
                 if (caller == READER) {
-                    writer.addData(HS_TRIGGER);
+                    writer.triggerWrite();
                     return false;
                 }
                 break;
@@ -818,7 +892,6 @@
                     }
                 } while (true);
                 handshakeState.getAndUpdate((current) -> current & ~DOING_TASKS);
-                //writer.addData(HS_TRIGGER);
                 resumeActivity();
             } catch (Throwable t) {
                 handleError(t);
@@ -839,7 +912,17 @@
             if (engine.isInboundDone() && !engine.isOutboundDone()) {
                 if (debug.on()) debug.log("doClosure: close_notify received");
                 close_notify_received = true;
-                doHandshake(r, READER);
+                if (!writer.scheduler.isStopped()) {
+                    doHandshake(r, READER);
+                } else {
+                    // We have received closed notify, but we
+                    // won't be able to send the acknowledgement.
+                    // Nothing more will come from the socket either,
+                    // so mark the reader as completed.
+                    synchronized (reader.readBufferLock) {
+                        reader.completing = true;
+                    }
+                }
             }
         }
         return r;
@@ -914,12 +997,22 @@
         }
     }
 
-    public ByteBuffer getNetBuffer() {
-        return ByteBuffer.allocate(engine.getSession().getPacketBufferSize());
+    volatile int packetBufferSize;
+    final ByteBuffer getNetBuffer() {
+        int netSize = packetBufferSize;
+        if (netSize <= 0) {
+            packetBufferSize = netSize = engine.getSession().getPacketBufferSize();
+        }
+        return ByteBuffer.allocate(netSize);
     }
 
-    private ByteBuffer getAppBuffer() {
-        return ByteBuffer.allocate(engine.getSession().getApplicationBufferSize());
+    volatile int applicationBufferSize;
+    final ByteBuffer getAppBuffer() {
+        int appSize = applicationBufferSize;
+        if (appSize <= 0) {
+            applicationBufferSize = appSize = engine.getSession().getApplicationBufferSize();
+        }
+        return ByteBuffer.allocate(appSize);
     }
 
     final String dbgString() {
--- a/src/java.net.http/share/classes/jdk/internal/net/http/common/SSLTube.java	Wed May 02 10:47:16 2018 +0200
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/common/SSLTube.java	Wed May 02 02:36:17 2018 -0700
@@ -77,6 +77,13 @@
     private volatile boolean finished;
 
     public SSLTube(SSLEngine engine, Executor executor, FlowTube tube) {
+        this(engine, executor, null, tube);
+    }
+
+    public SSLTube(SSLEngine engine,
+                   Executor executor,
+                   Consumer<ByteBuffer> recycler,
+                   FlowTube tube) {
         Objects.requireNonNull(engine);
         Objects.requireNonNull(executor);
         this.tube = Objects.requireNonNull(tube);
@@ -85,15 +92,17 @@
         this.engine = engine;
         sslDelegate = new SSLTubeFlowDelegate(engine,
                                               executor,
+                                              recycler,
                                               readSubscriber,
                                               tube);
     }
 
     final class SSLTubeFlowDelegate extends SSLFlowDelegate {
         SSLTubeFlowDelegate(SSLEngine engine, Executor executor,
+                            Consumer<ByteBuffer> recycler,
                             SSLSubscriberWrapper readSubscriber,
                             FlowTube tube) {
-            super(engine, executor, readSubscriber, tube);
+            super(engine, executor, recycler, readSubscriber, tube);
         }
         protected SchedulingAction enterReadScheduling() {
             readSubscriber.processPendingSubscriber();
--- a/src/java.net.http/share/classes/jdk/internal/net/http/common/SubscriberWrapper.java	Wed May 02 10:47:16 2018 +0200
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/common/SubscriberWrapper.java	Wed May 02 02:36:17 2018 -0700
@@ -306,14 +306,16 @@
                                downstreamSubscription);
             }
 
+            boolean datasent = false;
             while (!outputQ.isEmpty() && downstreamSubscription.tryDecrement()) {
                 List<ByteBuffer> b = outputQ.poll();
                 if (debug.on())
                     debug.log("DownstreamPusher: Pushing %d bytes downstream",
                               Utils.remaining(b));
                 downstreamSubscriber.onNext(b);
+                datasent = true;
             }
-            upstreamWindowUpdate();
+            if (datasent) upstreamWindowUpdate();
             checkCompletion();
         }
     }
--- a/src/java.net.http/share/classes/jdk/internal/net/http/common/Utils.java	Wed May 02 10:47:16 2018 +0200
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/common/Utils.java	Wed May 02 02:36:17 2018 -0700
@@ -44,10 +44,14 @@
 import java.net.URLPermission;
 import java.net.http.HttpHeaders;
 import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.CharacterCodingException;
 import java.nio.charset.Charset;
+import java.nio.charset.CodingErrorAction;
 import java.nio.charset.StandardCharsets;
 import java.security.AccessController;
 import java.security.PrivilegedAction;
+import java.text.Normalizer;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
@@ -581,25 +585,6 @@
         return (int) remain;
     }
 
-    public static long remaining(ByteBufferReference[] refs) {
-        long remain = 0;
-        for (ByteBufferReference ref : refs) {
-            remain += ref.get().remaining();
-        }
-        return remain;
-    }
-
-    public static int remaining(ByteBufferReference[] refs, int max) {
-        long remain = 0;
-        for (ByteBufferReference ref : refs) {
-            remain += ref.get().remaining();
-            if (remain > max) {
-                throw new IllegalArgumentException("too many bytes");
-            }
-        }
-        return (int) remain;
-    }
-
     public static int remaining(ByteBuffer[] refs, int max) {
         long remain = 0;
         for (ByteBuffer b : refs) {
@@ -623,7 +608,6 @@
     public static final ByteBuffer EMPTY_BYTEBUFFER = ByteBuffer.allocate(0);
     public static final ByteBuffer[] EMPTY_BB_ARRAY = new ByteBuffer[0];
     public static final List<ByteBuffer> EMPTY_BB_LIST = List.of();
-    public static final ByteBufferReference[] EMPTY_BBR_ARRAY = new ByteBufferReference[0];
 
     /**
      * Returns a slice of size {@code amount} from the given buffer. If the
@@ -959,4 +943,55 @@
             return 1 << (32 - Integer.numberOfLeadingZeros(n - 1));
         }
     }
+
+    // -- toAsciiString-like support to encode path and query URI segments
+
+    private static final char[] hexDigits = {
+            '0', '1', '2', '3', '4', '5', '6', '7',
+            '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
+    };
+
+    private static void appendEscape(StringBuilder sb, byte b) {
+        sb.append('%');
+        sb.append(hexDigits[(b >> 4) & 0x0f]);
+        sb.append(hexDigits[(b >> 0) & 0x0f]);
+    }
+
+    // Encodes all characters >= \u0080 into escaped, normalized UTF-8 octets,
+    // assuming that s is otherwise legal
+    //
+    public static String encode(String s) {
+        int n = s.length();
+        if (n == 0)
+            return s;
+
+        // First check whether we actually need to encode
+        for (int i = 0;;) {
+            if (s.charAt(i) >= '\u0080')
+                break;
+            if (++i >= n)
+                return s;
+        }
+
+        String ns = Normalizer.normalize(s, Normalizer.Form.NFC);
+        ByteBuffer bb = null;
+        try {
+            bb = StandardCharsets.UTF_8.newEncoder()
+                    .onMalformedInput(CodingErrorAction.REPORT)
+                    .onUnmappableCharacter(CodingErrorAction.REPORT)
+                    .encode(CharBuffer.wrap(ns));
+        } catch (CharacterCodingException x) {
+            assert false : x;
+        }
+
+        StringBuilder sb = new StringBuilder();
+        while (bb.hasRemaining()) {
+            int b = bb.get() & 0xff;
+            if (b >= 0x80)
+                appendEscape(sb, (byte)b);
+            else
+                sb.append((char)b);
+        }
+        return sb.toString();
+    }
 }
--- a/src/java.net.http/share/classes/jdk/internal/net/http/hpack/Huffman.java	Wed May 02 10:47:16 2018 +0200
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/hpack/Huffman.java	Wed May 02 02:36:17 2018 -0700
@@ -27,655 +27,60 @@
 import java.io.IOException;
 import java.nio.ByteBuffer;
 
-import static java.lang.String.format;
-
 /**
- * Huffman coding table.
- *
- * <p> Instances of this class are safe for use by multiple threads.
+ * Huffman coding.
  *
  * @since 9
  */
 public final class Huffman {
 
-    // TODO: check if reset is done in both reader and writer
-
-    static final class Reader {
-
-        private Node curr; // position in the trie
-        private int len;   // length of the path from the root to 'curr'
-        private int p;     // byte probe
+    public interface Reader {
 
-        {
-            reset();
-        }
-
-        public void read(ByteBuffer source,
-                         Appendable destination,
-                         boolean isLast) throws IOException {
-            read(source, destination, true, isLast);
-        }
-
-        // Takes 'isLast' rather than returns whether the reading is done or
-        // not, for more informative exceptions.
         void read(ByteBuffer source,
                   Appendable destination,
-                  boolean reportEOS, /* reportEOS is exposed for tests */
-                  boolean isLast) throws IOException {
-            Node c = curr;
-            int l = len;
-            /*
-               Since ByteBuffer is itself stateful, its position is
-               remembered here NOT as a part of Reader's state,
-               but to set it back in the case of a failure
-             */
-            int pos = source.position();
-
-            while (source.hasRemaining()) {
-                int d = source.get();
-                for (; p != 0; p >>= 1) {
-                    c = c.getChild(p & d);
-                    l++;
-                    if (c.isLeaf()) {
-                        if (reportEOS && c.isEOSPath) {
-                            throw new IOException("Encountered EOS");
-                        }
-                        char ch;
-                        try {
-                            ch = c.getChar();
-                        } catch (IllegalStateException e) {
-                            source.position(pos); // do we need this?
-                            throw new IOException(e);
-                        }
-                        try {
-                            destination.append(ch);
-                        } catch (IOException e) {
-                            source.position(pos); // do we need this?
-                            throw e;
-                        }
-                        c = INSTANCE.root;
-                        l = 0;
-                    }
-                    curr = c;
-                    len = l;
-                }
-                resetProbe();
-                pos++;
-            }
-            if (!isLast) {
-                return; // it's too early to jump to any conclusions, let's wait
-            }
-            if (c.isLeaf()) {
-                return; // it's perfectly ok, no extra padding bits
-            }
-            if (c.isEOSPath && len <= 7) {
-                return; // it's ok, some extra padding bits
-            }
-            if (c.isEOSPath) {
-                throw new IOException(
-                        "Padding is too long (len=" + len + ") " +
-                                "or unexpected end of data");
-            }
-            throw new IOException(
-                    "Not a EOS prefix padding or unexpected end of data");
-        }
-
-        public void reset() {
-            curr = INSTANCE.root;
-            len = 0;
-            resetProbe();
-        }
-
-        private void resetProbe() {
-            p = 0x80;
-        }
-    }
+                  boolean isLast) throws IOException;
 
-    static final class Writer {
-
-        private int pos;       // position in 'source'
-        private int avail = 8; // number of least significant bits available in 'curr'
-        private int curr;      // next byte to put to the destination
-        private int rem;       // number of least significant bits in 'code' yet to be processed
-        private int code;      // current code being written
-
-        private CharSequence source;
-        private int end;
-
-        public Writer from(CharSequence input, int start, int end) {
-            if (start < 0 || end < 0 || end > input.length() || start > end) {
-                throw new IndexOutOfBoundsException(
-                        String.format("input.length()=%s, start=%s, end=%s",
-                                input.length(), start, end));
-            }
-            pos = start;
-            this.end = end;
-            this.source = input;
-            return this;
-        }
-
-        public boolean write(ByteBuffer destination) {
-            for (; pos < end; pos++) {
-                if (rem == 0) {
-                    Code desc = INSTANCE.codeOf(source.charAt(pos));
-                    rem = desc.length;
-                    code = desc.code;
-                }
-                while (rem > 0) {
-                    if (rem < avail) {
-                        curr |= (code << (avail - rem));
-                        avail -= rem;
-                        rem = 0;
-                    } else {
-                        int c = (curr | (code >>> (rem - avail)));
-                        if (destination.hasRemaining()) {
-                            destination.put((byte) c);
-                        } else {
-                            return false;
-                        }
-                        curr = c;
-                        code <<= (32 - rem + avail);  // throw written bits off the cliff (is this Sparta?)
-                        code >>>= (32 - rem + avail); // return to the position
-                        rem -= avail;
-                        curr = 0;
-                        avail = 8;
-                    }
-                }
-            }
-
-            if (avail < 8) { // have to pad
-                if (destination.hasRemaining()) {
-                    destination.put((byte) (curr | (INSTANCE.EOS.code >>> (INSTANCE.EOS.length - avail))));
-                    avail = 8;
-                } else {
-                    return false;
-                }
-            }
-
-            return true;
-        }
-
-        public Writer reset() {
-            source = null;
-            end = -1;
-            pos = -1;
-            avail = 8;
-            curr = 0;
-            code = 0;
-            return this;
-        }
+        /**
+         * Brings this reader to the state it had upon construction.
+         */
+        void reset();
     }
 
-    /**
-     * Shared instance.
-     */
-    public static final Huffman INSTANCE = new Huffman();
+    public interface Writer {
+
+        Writer from(CharSequence input, int start, int end);
+
+        boolean write(ByteBuffer destination);
 
-    private final Code EOS = new Code(0x3fffffff, 30);
-    private final Code[] codes = new Code[257];
-    private final Node root = new Node() {
-        @Override
-        public String toString() { return "root"; }
-    };
+        /**
+         * Brings this writer to the state it had upon construction.
+         *
+         * @return this writer
+         */
+        Writer reset();
 
-    // TODO: consider builder and immutable trie
-    private Huffman() {
-        // @formatter:off
-        addChar(0,   0x1ff8,     13);
-        addChar(1,   0x7fffd8,   23);
-        addChar(2,   0xfffffe2,  28);
-        addChar(3,   0xfffffe3,  28);
-        addChar(4,   0xfffffe4,  28);
-        addChar(5,   0xfffffe5,  28);
-        addChar(6,   0xfffffe6,  28);
-        addChar(7,   0xfffffe7,  28);
-        addChar(8,   0xfffffe8,  28);
-        addChar(9,   0xffffea,   24);
-        addChar(10,  0x3ffffffc, 30);
-        addChar(11,  0xfffffe9,  28);
-        addChar(12,  0xfffffea,  28);
-        addChar(13,  0x3ffffffd, 30);
-        addChar(14,  0xfffffeb,  28);
-        addChar(15,  0xfffffec,  28);
-        addChar(16,  0xfffffed,  28);
-        addChar(17,  0xfffffee,  28);
-        addChar(18,  0xfffffef,  28);
-        addChar(19,  0xffffff0,  28);
-        addChar(20,  0xffffff1,  28);
-        addChar(21,  0xffffff2,  28);
-        addChar(22,  0x3ffffffe, 30);
-        addChar(23,  0xffffff3,  28);
-        addChar(24,  0xffffff4,  28);
-        addChar(25,  0xffffff5,  28);
-        addChar(26,  0xffffff6,  28);
-        addChar(27,  0xffffff7,  28);
-        addChar(28,  0xffffff8,  28);
-        addChar(29,  0xffffff9,  28);
-        addChar(30,  0xffffffa,  28);
-        addChar(31,  0xffffffb,  28);
-        addChar(32,  0x14,        6);
-        addChar(33,  0x3f8,      10);
-        addChar(34,  0x3f9,      10);
-        addChar(35,  0xffa,      12);
-        addChar(36,  0x1ff9,     13);
-        addChar(37,  0x15,        6);
-        addChar(38,  0xf8,        8);
-        addChar(39,  0x7fa,      11);
-        addChar(40,  0x3fa,      10);
-        addChar(41,  0x3fb,      10);
-        addChar(42,  0xf9,        8);
-        addChar(43,  0x7fb,      11);
-        addChar(44,  0xfa,        8);
-        addChar(45,  0x16,        6);
-        addChar(46,  0x17,        6);
-        addChar(47,  0x18,        6);
-        addChar(48,  0x0,         5);
-        addChar(49,  0x1,         5);
-        addChar(50,  0x2,         5);
-        addChar(51,  0x19,        6);
-        addChar(52,  0x1a,        6);
-        addChar(53,  0x1b,        6);
-        addChar(54,  0x1c,        6);
-        addChar(55,  0x1d,        6);
-        addChar(56,  0x1e,        6);
-        addChar(57,  0x1f,        6);
-        addChar(58,  0x5c,        7);
-        addChar(59,  0xfb,        8);
-        addChar(60,  0x7ffc,     15);
-        addChar(61,  0x20,        6);
-        addChar(62,  0xffb,      12);
-        addChar(63,  0x3fc,      10);
-        addChar(64,  0x1ffa,     13);
-        addChar(65,  0x21,        6);
-        addChar(66,  0x5d,        7);
-        addChar(67,  0x5e,        7);
-        addChar(68,  0x5f,        7);
-        addChar(69,  0x60,        7);
-        addChar(70,  0x61,        7);
-        addChar(71,  0x62,        7);
-        addChar(72,  0x63,        7);
-        addChar(73,  0x64,        7);
-        addChar(74,  0x65,        7);
-        addChar(75,  0x66,        7);
-        addChar(76,  0x67,        7);
-        addChar(77,  0x68,        7);
-        addChar(78,  0x69,        7);
-        addChar(79,  0x6a,        7);
-        addChar(80,  0x6b,        7);
-        addChar(81,  0x6c,        7);
-        addChar(82,  0x6d,        7);
-        addChar(83,  0x6e,        7);
-        addChar(84,  0x6f,        7);
-        addChar(85,  0x70,        7);
-        addChar(86,  0x71,        7);
-        addChar(87,  0x72,        7);
-        addChar(88,  0xfc,        8);
-        addChar(89,  0x73,        7);
-        addChar(90,  0xfd,        8);
-        addChar(91,  0x1ffb,     13);
-        addChar(92,  0x7fff0,    19);
-        addChar(93,  0x1ffc,     13);
-        addChar(94,  0x3ffc,     14);
-        addChar(95,  0x22,        6);
-        addChar(96,  0x7ffd,     15);
-        addChar(97,  0x3,         5);
-        addChar(98,  0x23,        6);
-        addChar(99,  0x4,         5);
-        addChar(100, 0x24,        6);
-        addChar(101, 0x5,         5);
-        addChar(102, 0x25,        6);
-        addChar(103, 0x26,        6);
-        addChar(104, 0x27,        6);
-        addChar(105, 0x6,         5);
-        addChar(106, 0x74,        7);
-        addChar(107, 0x75,        7);
-        addChar(108, 0x28,        6);
-        addChar(109, 0x29,        6);
-        addChar(110, 0x2a,        6);
-        addChar(111, 0x7,         5);
-        addChar(112, 0x2b,        6);
-        addChar(113, 0x76,        7);
-        addChar(114, 0x2c,        6);
-        addChar(115, 0x8,         5);
-        addChar(116, 0x9,         5);
-        addChar(117, 0x2d,        6);
-        addChar(118, 0x77,        7);
-        addChar(119, 0x78,        7);
-        addChar(120, 0x79,        7);
-        addChar(121, 0x7a,        7);
-        addChar(122, 0x7b,        7);
-        addChar(123, 0x7ffe,     15);
-        addChar(124, 0x7fc,      11);
-        addChar(125, 0x3ffd,     14);
-        addChar(126, 0x1ffd,     13);
-        addChar(127, 0xffffffc,  28);
-        addChar(128, 0xfffe6,    20);
-        addChar(129, 0x3fffd2,   22);
-        addChar(130, 0xfffe7,    20);
-        addChar(131, 0xfffe8,    20);
-        addChar(132, 0x3fffd3,   22);
-        addChar(133, 0x3fffd4,   22);
-        addChar(134, 0x3fffd5,   22);
-        addChar(135, 0x7fffd9,   23);
-        addChar(136, 0x3fffd6,   22);
-        addChar(137, 0x7fffda,   23);
-        addChar(138, 0x7fffdb,   23);
-        addChar(139, 0x7fffdc,   23);
-        addChar(140, 0x7fffdd,   23);
-        addChar(141, 0x7fffde,   23);
-        addChar(142, 0xffffeb,   24);
-        addChar(143, 0x7fffdf,   23);
-        addChar(144, 0xffffec,   24);
-        addChar(145, 0xffffed,   24);
-        addChar(146, 0x3fffd7,   22);
-        addChar(147, 0x7fffe0,   23);
-        addChar(148, 0xffffee,   24);
-        addChar(149, 0x7fffe1,   23);
-        addChar(150, 0x7fffe2,   23);
-        addChar(151, 0x7fffe3,   23);
-        addChar(152, 0x7fffe4,   23);
-        addChar(153, 0x1fffdc,   21);
-        addChar(154, 0x3fffd8,   22);
-        addChar(155, 0x7fffe5,   23);
-        addChar(156, 0x3fffd9,   22);
-        addChar(157, 0x7fffe6,   23);
-        addChar(158, 0x7fffe7,   23);
-        addChar(159, 0xffffef,   24);
-        addChar(160, 0x3fffda,   22);
-        addChar(161, 0x1fffdd,   21);
-        addChar(162, 0xfffe9,    20);
-        addChar(163, 0x3fffdb,   22);
-        addChar(164, 0x3fffdc,   22);
-        addChar(165, 0x7fffe8,   23);
-        addChar(166, 0x7fffe9,   23);
-        addChar(167, 0x1fffde,   21);
-        addChar(168, 0x7fffea,   23);
-        addChar(169, 0x3fffdd,   22);
-        addChar(170, 0x3fffde,   22);
-        addChar(171, 0xfffff0,   24);
-        addChar(172, 0x1fffdf,   21);
-        addChar(173, 0x3fffdf,   22);
-        addChar(174, 0x7fffeb,   23);
-        addChar(175, 0x7fffec,   23);
-        addChar(176, 0x1fffe0,   21);
-        addChar(177, 0x1fffe1,   21);
-        addChar(178, 0x3fffe0,   22);
-        addChar(179, 0x1fffe2,   21);
-        addChar(180, 0x7fffed,   23);
-        addChar(181, 0x3fffe1,   22);
-        addChar(182, 0x7fffee,   23);
-        addChar(183, 0x7fffef,   23);
-        addChar(184, 0xfffea,    20);
-        addChar(185, 0x3fffe2,   22);
-        addChar(186, 0x3fffe3,   22);
-        addChar(187, 0x3fffe4,   22);
-        addChar(188, 0x7ffff0,   23);
-        addChar(189, 0x3fffe5,   22);
-        addChar(190, 0x3fffe6,   22);
-        addChar(191, 0x7ffff1,   23);
-        addChar(192, 0x3ffffe0,  26);
-        addChar(193, 0x3ffffe1,  26);
-        addChar(194, 0xfffeb,    20);
-        addChar(195, 0x7fff1,    19);
-        addChar(196, 0x3fffe7,   22);
-        addChar(197, 0x7ffff2,   23);
-        addChar(198, 0x3fffe8,   22);
-        addChar(199, 0x1ffffec,  25);
-        addChar(200, 0x3ffffe2,  26);
-        addChar(201, 0x3ffffe3,  26);
-        addChar(202, 0x3ffffe4,  26);
-        addChar(203, 0x7ffffde,  27);
-        addChar(204, 0x7ffffdf,  27);
-        addChar(205, 0x3ffffe5,  26);
-        addChar(206, 0xfffff1,   24);
-        addChar(207, 0x1ffffed,  25);
-        addChar(208, 0x7fff2,    19);
-        addChar(209, 0x1fffe3,   21);
-        addChar(210, 0x3ffffe6,  26);
-        addChar(211, 0x7ffffe0,  27);
-        addChar(212, 0x7ffffe1,  27);
-        addChar(213, 0x3ffffe7,  26);
-        addChar(214, 0x7ffffe2,  27);
-        addChar(215, 0xfffff2,   24);
-        addChar(216, 0x1fffe4,   21);
-        addChar(217, 0x1fffe5,   21);
-        addChar(218, 0x3ffffe8,  26);
-        addChar(219, 0x3ffffe9,  26);
-        addChar(220, 0xffffffd,  28);
-        addChar(221, 0x7ffffe3,  27);
-        addChar(222, 0x7ffffe4,  27);
-        addChar(223, 0x7ffffe5,  27);
-        addChar(224, 0xfffec,    20);
-        addChar(225, 0xfffff3,   24);
-        addChar(226, 0xfffed,    20);
-        addChar(227, 0x1fffe6,   21);
-        addChar(228, 0x3fffe9,   22);
-        addChar(229, 0x1fffe7,   21);
-        addChar(230, 0x1fffe8,   21);
-        addChar(231, 0x7ffff3,   23);
-        addChar(232, 0x3fffea,   22);
-        addChar(233, 0x3fffeb,   22);
-        addChar(234, 0x1ffffee,  25);
-        addChar(235, 0x1ffffef,  25);
-        addChar(236, 0xfffff4,   24);
-        addChar(237, 0xfffff5,   24);
-        addChar(238, 0x3ffffea,  26);
-        addChar(239, 0x7ffff4,   23);
-        addChar(240, 0x3ffffeb,  26);
-        addChar(241, 0x7ffffe6,  27);
-        addChar(242, 0x3ffffec,  26);
-        addChar(243, 0x3ffffed,  26);
-        addChar(244, 0x7ffffe7,  27);
-        addChar(245, 0x7ffffe8,  27);
-        addChar(246, 0x7ffffe9,  27);
-        addChar(247, 0x7ffffea,  27);
-        addChar(248, 0x7ffffeb,  27);
-        addChar(249, 0xffffffe,  28);
-        addChar(250, 0x7ffffec,  27);
-        addChar(251, 0x7ffffed,  27);
-        addChar(252, 0x7ffffee,  27);
-        addChar(253, 0x7ffffef,  27);
-        addChar(254, 0x7fffff0,  27);
-        addChar(255, 0x3ffffee,  26);
-        addEOS (256, EOS.code,   EOS.length);
-        // @formatter:on
-    }
+        /**
+         * Calculates the number of bytes required to represent a subsequence of
+         * the given {@code CharSequence} using the Huffman coding.
+         *
+         * @param value
+         *         characters
+         * @param start
+         *         the start index, inclusive
+         * @param end
+         *         the end index, exclusive
+         *
+         * @return number of bytes
+         * @throws NullPointerException
+         *         if the value is null
+         * @throws IndexOutOfBoundsException
+         *         if any invocation of {@code value.charAt(i)}, where
+         *         {@code start <= i < end}, throws an IndexOutOfBoundsException
+         */
+        int lengthOf(CharSequence value, int start, int end);
 
-
-    /**
-     * Calculates the number of bytes required to represent the given {@code
-     * CharSequence} with the Huffman coding.
-     *
-     * @param value
-     *         characters
-     *
-     * @return number of bytes
-     *
-     * @throws NullPointerException
-     *         if the value is null
-     */
-    public int lengthOf(CharSequence value) {
-        return lengthOf(value, 0, value.length());
-    }
-
-    /**
-     * Calculates the number of bytes required to represent a subsequence of the
-     * given {@code CharSequence} with the Huffman coding.
-     *
-     * @param value
-     *         characters
-     * @param start
-     *         the start index, inclusive
-     * @param end
-     *         the end index, exclusive
-     *
-     * @return number of bytes
-     *
-     * @throws NullPointerException
-     *         if the value is null
-     * @throws IndexOutOfBoundsException
-     *         if any invocation of {@code value.charAt(i)}, where
-     *         {@code start <= i < end} would throw an IndexOutOfBoundsException
-     */
-    public int lengthOf(CharSequence value, int start, int end) {
-        int len = 0;
-        for (int i = start; i < end; i++) {
-            char c = value.charAt(i);
-            len += INSTANCE.codeOf(c).length;
-        }
-        // Integer division with ceiling, assumption:
-        assert (len / 8 + (len % 8 != 0 ? 1 : 0)) == (len + 7) / 8 : len;
-        return (len + 7) / 8;
-    }
-
-    private void addChar(int c, int code, int bitLength) {
-        addLeaf(c, code, bitLength, false);
-        codes[c] = new Code(code, bitLength);
-    }
-
-    private void addEOS(int c, int code, int bitLength) {
-        addLeaf(c, code, bitLength, true);
-        codes[c] = new Code(code, bitLength);
-    }
-
-    private void addLeaf(int c, int code, int bitLength, boolean isEOS) {
-        if (bitLength < 1) {
-            throw new IllegalArgumentException("bitLength < 1");
-        }
-        Node curr = root;
-        for (int p = 1 << bitLength - 1; p != 0 && !curr.isLeaf(); p = p >> 1) {
-            curr.isEOSPath |= isEOS; // If it's already true, it can't become false
-            curr = curr.addChildIfAbsent(p & code);
-        }
-        curr.isEOSPath |= isEOS; // The last one needs to have this property as well
-        if (curr.isLeaf()) {
-            throw new IllegalStateException("Specified code is already taken");
-        }
-        curr.setChar((char) c);
-    }
-
-    private Code codeOf(char c) {
-        if (c > 255) {
-            throw new IllegalArgumentException("char=" + ((int) c));
-        }
-        return codes[c];
-    }
-
-    //
-    // For debugging/testing purposes
-    //
-    Node getRoot() {
-        return root;
-    }
-
-    //
-    // Guarantees:
-    //
-    //  if (isLeaf() == true) => getChar() is a legal call
-    //  if (isLeaf() == false) => getChild(i) is a legal call (though it can
-    //                                                           return null)
-    //
-    static class Node {
-
-        Node left;
-        Node right;
-        boolean isEOSPath;
-
-        boolean charIsSet;
-        char c;
-
-        Node getChild(int selector) {
-            if (isLeaf()) {
-                throw new IllegalStateException("This is a leaf node");
-            }
-            Node result = selector == 0 ? left : right;
-            if (result == null) {
-                throw new IllegalStateException(format(
-                        "Node doesn't have a child (selector=%s)", selector));
-            }
-            return result;
-        }
-
-        boolean isLeaf() {
-            return charIsSet;
-        }
-
-        char getChar() {
-            if (!isLeaf()) {
-                throw new IllegalStateException("This node is not a leaf node");
-            }
-            return c;
-        }
-
-        void setChar(char c) {
-            if (charIsSet) {
-                throw new IllegalStateException(
-                        "This node has been taken already");
-            }
-            if (left != null || right != null) {
-                throw new IllegalStateException("The node cannot be made "
-                        + "a leaf as it's already has a child");
-            }
-            this.c = c;
-            charIsSet = true;
-        }
-
-        Node addChildIfAbsent(int i) {
-            if (charIsSet) {
-                throw new IllegalStateException("The node cannot have a child "
-                        + "as it's already a leaf node");
-            }
-            Node child;
-            if (i == 0) {
-                if ((child = left) == null) {
-                    child = left = new Node();
-                }
-            } else {
-                if ((child = right) == null) {
-                    child = right = new Node();
-                }
-            }
-            return child;
-        }
-
-        @Override
-        public String toString() {
-            if (isLeaf()) {
-                if (isEOSPath) {
-                    return "EOS";
-                } else {
-                    return format("char: (%3s) '%s'", (int) c, c);
-                }
-            }
-            return "/\\";
-        }
-    }
-
-    // TODO: value-based class?
-    // FIXME: can we re-use Node instead of this class?
-    private static final class Code {
-
-        final int code;
-        final int length;
-
-        private Code(int code, int length) {
-            this.code = code;
-            this.length = length;
-        }
-
-        public int getCode() {
-            return code;
-        }
-
-        public int getLength() {
-            return length;
-        }
-
-        @Override
-        public String toString() {
-            long p = 1 << length;
-            return Long.toBinaryString(code + p).substring(1)
-                    + ", length=" + length;
+        default int lengthOf(CharSequence value) {
+            return lengthOf(value, 0, value.length());
         }
     }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/hpack/NaiveHuffman.java	Wed May 02 02:36:17 2018 -0700
@@ -0,0 +1,691 @@
+/*
+ * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.internal.net.http.hpack;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+import static java.lang.String.format;
+
+/**
+ * Huffman coding table.
+ *
+ * <p> Instances of this class are safe for use by multiple threads.
+ *
+ * @since 9
+ */
+public final class NaiveHuffman {
+
+    // TODO: check if reset is done in both reader and writer
+
+    static final class Reader implements Huffman.Reader {
+
+        private Node curr; // position in the trie
+        private int len;   // length of the path from the root to 'curr'
+        private int p;     // byte probe
+
+        {
+            reset();
+        }
+
+        @Override
+        public void read(ByteBuffer source,
+                         Appendable destination,
+                         boolean isLast) throws IOException {
+            read(source, destination, true, isLast);
+        }
+
+        // Takes 'isLast' rather than returns whether the reading is done or
+        // not, for more informative exceptions.
+        void read(ByteBuffer source,
+                  Appendable destination,
+                  boolean reportEOS, /* reportEOS is exposed for tests */
+                  boolean isLast) throws IOException {
+            Node c = curr;
+            int l = len;
+            /*
+               Since ByteBuffer is itself stateful, its position is
+               remembered here NOT as a part of Reader's state,
+               but to set it back in the case of a failure
+             */
+            int pos = source.position();
+
+            while (source.hasRemaining()) {
+                int d = source.get();
+                for (; p != 0; p >>= 1) {
+                    c = c.getChild(p & d);
+                    l++;
+                    if (c.isLeaf()) {
+                        if (reportEOS && c.isEOSPath) {
+                            throw new IOException("Encountered EOS");
+                        }
+                        char ch;
+                        try {
+                            ch = c.getChar();
+                        } catch (IllegalStateException e) {
+                            source.position(pos); // do we need this?
+                            throw new IOException(e);
+                        }
+                        try {
+                            destination.append(ch);
+                        } catch (IOException e) {
+                            source.position(pos); // do we need this?
+                            throw e;
+                        }
+                        c = INSTANCE.root;
+                        l = 0;
+                    }
+                    curr = c;
+                    len = l;
+                }
+                resetProbe();
+                pos++;
+            }
+            if (!isLast) {
+                return; // it's too early to jump to any conclusions, let's wait
+            }
+            if (c.isLeaf()) {
+                return; // it's perfectly ok, no extra padding bits
+            }
+            if (c.isEOSPath && len <= 7) {
+                return; // it's ok, some extra padding bits
+            }
+            if (c.isEOSPath) {
+                throw new IOException(
+                        "Padding is too long (len=" + len + ") " +
+                                "or unexpected end of data");
+            }
+            throw new IOException(
+                    "Not a EOS prefix padding or unexpected end of data");
+        }
+
+        @Override
+        public void reset() {
+            curr = INSTANCE.root;
+            len = 0;
+            resetProbe();
+        }
+
+        private void resetProbe() {
+            p = 0x80;
+        }
+    }
+
+    static final class Writer implements Huffman.Writer {
+
+        private int pos;       // position in 'source'
+        private int avail = 8; // number of least significant bits available in 'curr'
+        private int curr;      // next byte to put to the destination
+        private int rem;       // number of least significant bits in 'code' yet to be processed
+        private int code;      // current code being written
+
+        private CharSequence source;
+        private int end;
+
+        @Override
+        public Writer from(CharSequence input, int start, int end) {
+            if (start < 0 || end < 0 || end > input.length() || start > end) {
+                throw new IndexOutOfBoundsException(
+                        String.format("input.length()=%s, start=%s, end=%s",
+                                      input.length(), start, end));
+            }
+            pos = start;
+            this.end = end;
+            this.source = input;
+            return this;
+        }
+
+        @Override
+        public boolean write(ByteBuffer destination) {
+            for (; pos < end; pos++) {
+                if (rem == 0) {
+                    Code desc = INSTANCE.codeOf(source.charAt(pos));
+                    rem = desc.length;
+                    code = desc.code;
+                }
+                while (rem > 0) {
+                    if (rem < avail) {
+                        curr |= (code << (avail - rem));
+                        avail -= rem;
+                        rem = 0;
+                    } else {
+                        int c = (curr | (code >>> (rem - avail)));
+                        if (destination.hasRemaining()) {
+                            destination.put((byte) c);
+                        } else {
+                            return false;
+                        }
+                        curr = c;
+                        code <<= (32 - rem + avail);  // throw written bits off the cliff (is this Sparta?)
+                        code >>>= (32 - rem + avail); // return to the position
+                        rem -= avail;
+                        curr = 0;
+                        avail = 8;
+                    }
+                }
+            }
+
+            if (avail < 8) { // have to pad
+                if (destination.hasRemaining()) {
+                    destination.put((byte) (curr | (INSTANCE.EOS.code >>> (INSTANCE.EOS.length - avail))));
+                    avail = 8;
+                } else {
+                    return false;
+                }
+            }
+
+            return true;
+        }
+
+        @Override
+        public Writer reset() {
+            source = null;
+            end = -1;
+            pos = -1;
+            avail = 8;
+            curr = 0;
+            code = 0;
+            return this;
+        }
+
+        @Override
+        public int lengthOf(CharSequence value, int start, int end) {
+            return INSTANCE.lengthOf(value, start, end);
+        }
+    }
+
+    /**
+     * Shared instance.
+     */
+    public static final NaiveHuffman INSTANCE = new NaiveHuffman();
+
+    private final Code EOS = new Code(0x3fffffff, 30);
+    private final Code[] codes = new Code[257];
+    private final Node root = new Node() {
+        @Override
+        public String toString() { return "root"; }
+    };
+
+    // TODO: consider builder and immutable trie
+    private NaiveHuffman() {
+        // @formatter:off
+        addChar(0,   0x1ff8,     13);
+        addChar(1,   0x7fffd8,   23);
+        addChar(2,   0xfffffe2,  28);
+        addChar(3,   0xfffffe3,  28);
+        addChar(4,   0xfffffe4,  28);
+        addChar(5,   0xfffffe5,  28);
+        addChar(6,   0xfffffe6,  28);
+        addChar(7,   0xfffffe7,  28);
+        addChar(8,   0xfffffe8,  28);
+        addChar(9,   0xffffea,   24);
+        addChar(10,  0x3ffffffc, 30);
+        addChar(11,  0xfffffe9,  28);
+        addChar(12,  0xfffffea,  28);
+        addChar(13,  0x3ffffffd, 30);
+        addChar(14,  0xfffffeb,  28);
+        addChar(15,  0xfffffec,  28);
+        addChar(16,  0xfffffed,  28);
+        addChar(17,  0xfffffee,  28);
+        addChar(18,  0xfffffef,  28);
+        addChar(19,  0xffffff0,  28);
+        addChar(20,  0xffffff1,  28);
+        addChar(21,  0xffffff2,  28);
+        addChar(22,  0x3ffffffe, 30);
+        addChar(23,  0xffffff3,  28);
+        addChar(24,  0xffffff4,  28);
+        addChar(25,  0xffffff5,  28);
+        addChar(26,  0xffffff6,  28);
+        addChar(27,  0xffffff7,  28);
+        addChar(28,  0xffffff8,  28);
+        addChar(29,  0xffffff9,  28);
+        addChar(30,  0xffffffa,  28);
+        addChar(31,  0xffffffb,  28);
+        addChar(32,  0x14,        6);
+        addChar(33,  0x3f8,      10);
+        addChar(34,  0x3f9,      10);
+        addChar(35,  0xffa,      12);
+        addChar(36,  0x1ff9,     13);
+        addChar(37,  0x15,        6);
+        addChar(38,  0xf8,        8);
+        addChar(39,  0x7fa,      11);
+        addChar(40,  0x3fa,      10);
+        addChar(41,  0x3fb,      10);
+        addChar(42,  0xf9,        8);
+        addChar(43,  0x7fb,      11);
+        addChar(44,  0xfa,        8);
+        addChar(45,  0x16,        6);
+        addChar(46,  0x17,        6);
+        addChar(47,  0x18,        6);
+        addChar(48,  0x0,         5);
+        addChar(49,  0x1,         5);
+        addChar(50,  0x2,         5);
+        addChar(51,  0x19,        6);
+        addChar(52,  0x1a,        6);
+        addChar(53,  0x1b,        6);
+        addChar(54,  0x1c,        6);
+        addChar(55,  0x1d,        6);
+        addChar(56,  0x1e,        6);
+        addChar(57,  0x1f,        6);
+        addChar(58,  0x5c,        7);
+        addChar(59,  0xfb,        8);
+        addChar(60,  0x7ffc,     15);
+        addChar(61,  0x20,        6);
+        addChar(62,  0xffb,      12);
+        addChar(63,  0x3fc,      10);
+        addChar(64,  0x1ffa,     13);
+        addChar(65,  0x21,        6);
+        addChar(66,  0x5d,        7);
+        addChar(67,  0x5e,        7);
+        addChar(68,  0x5f,        7);
+        addChar(69,  0x60,        7);
+        addChar(70,  0x61,        7);
+        addChar(71,  0x62,        7);
+        addChar(72,  0x63,        7);
+        addChar(73,  0x64,        7);
+        addChar(74,  0x65,        7);
+        addChar(75,  0x66,        7);
+        addChar(76,  0x67,        7);
+        addChar(77,  0x68,        7);
+        addChar(78,  0x69,        7);
+        addChar(79,  0x6a,        7);
+        addChar(80,  0x6b,        7);
+        addChar(81,  0x6c,        7);
+        addChar(82,  0x6d,        7);
+        addChar(83,  0x6e,        7);
+        addChar(84,  0x6f,        7);
+        addChar(85,  0x70,        7);
+        addChar(86,  0x71,        7);
+        addChar(87,  0x72,        7);
+        addChar(88,  0xfc,        8);
+        addChar(89,  0x73,        7);
+        addChar(90,  0xfd,        8);
+        addChar(91,  0x1ffb,     13);
+        addChar(92,  0x7fff0,    19);
+        addChar(93,  0x1ffc,     13);
+        addChar(94,  0x3ffc,     14);
+        addChar(95,  0x22,        6);
+        addChar(96,  0x7ffd,     15);
+        addChar(97,  0x3,         5);
+        addChar(98,  0x23,        6);
+        addChar(99,  0x4,         5);
+        addChar(100, 0x24,        6);
+        addChar(101, 0x5,         5);
+        addChar(102, 0x25,        6);
+        addChar(103, 0x26,        6);
+        addChar(104, 0x27,        6);
+        addChar(105, 0x6,         5);
+        addChar(106, 0x74,        7);
+        addChar(107, 0x75,        7);
+        addChar(108, 0x28,        6);
+        addChar(109, 0x29,        6);
+        addChar(110, 0x2a,        6);
+        addChar(111, 0x7,         5);
+        addChar(112, 0x2b,        6);
+        addChar(113, 0x76,        7);
+        addChar(114, 0x2c,        6);
+        addChar(115, 0x8,         5);
+        addChar(116, 0x9,         5);
+        addChar(117, 0x2d,        6);
+        addChar(118, 0x77,        7);
+        addChar(119, 0x78,        7);
+        addChar(120, 0x79,        7);
+        addChar(121, 0x7a,        7);
+        addChar(122, 0x7b,        7);
+        addChar(123, 0x7ffe,     15);
+        addChar(124, 0x7fc,      11);
+        addChar(125, 0x3ffd,     14);
+        addChar(126, 0x1ffd,     13);
+        addChar(127, 0xffffffc,  28);
+        addChar(128, 0xfffe6,    20);
+        addChar(129, 0x3fffd2,   22);
+        addChar(130, 0xfffe7,    20);
+        addChar(131, 0xfffe8,    20);
+        addChar(132, 0x3fffd3,   22);
+        addChar(133, 0x3fffd4,   22);
+        addChar(134, 0x3fffd5,   22);
+        addChar(135, 0x7fffd9,   23);
+        addChar(136, 0x3fffd6,   22);
+        addChar(137, 0x7fffda,   23);
+        addChar(138, 0x7fffdb,   23);
+        addChar(139, 0x7fffdc,   23);
+        addChar(140, 0x7fffdd,   23);
+        addChar(141, 0x7fffde,   23);
+        addChar(142, 0xffffeb,   24);
+        addChar(143, 0x7fffdf,   23);
+        addChar(144, 0xffffec,   24);
+        addChar(145, 0xffffed,   24);
+        addChar(146, 0x3fffd7,   22);
+        addChar(147, 0x7fffe0,   23);
+        addChar(148, 0xffffee,   24);
+        addChar(149, 0x7fffe1,   23);
+        addChar(150, 0x7fffe2,   23);
+        addChar(151, 0x7fffe3,   23);
+        addChar(152, 0x7fffe4,   23);
+        addChar(153, 0x1fffdc,   21);
+        addChar(154, 0x3fffd8,   22);
+        addChar(155, 0x7fffe5,   23);
+        addChar(156, 0x3fffd9,   22);
+        addChar(157, 0x7fffe6,   23);
+        addChar(158, 0x7fffe7,   23);
+        addChar(159, 0xffffef,   24);
+        addChar(160, 0x3fffda,   22);
+        addChar(161, 0x1fffdd,   21);
+        addChar(162, 0xfffe9,    20);
+        addChar(163, 0x3fffdb,   22);
+        addChar(164, 0x3fffdc,   22);
+        addChar(165, 0x7fffe8,   23);
+        addChar(166, 0x7fffe9,   23);
+        addChar(167, 0x1fffde,   21);
+        addChar(168, 0x7fffea,   23);
+        addChar(169, 0x3fffdd,   22);
+        addChar(170, 0x3fffde,   22);
+        addChar(171, 0xfffff0,   24);
+        addChar(172, 0x1fffdf,   21);
+        addChar(173, 0x3fffdf,   22);
+        addChar(174, 0x7fffeb,   23);
+        addChar(175, 0x7fffec,   23);
+        addChar(176, 0x1fffe0,   21);
+        addChar(177, 0x1fffe1,   21);
+        addChar(178, 0x3fffe0,   22);
+        addChar(179, 0x1fffe2,   21);
+        addChar(180, 0x7fffed,   23);
+        addChar(181, 0x3fffe1,   22);
+        addChar(182, 0x7fffee,   23);
+        addChar(183, 0x7fffef,   23);
+        addChar(184, 0xfffea,    20);
+        addChar(185, 0x3fffe2,   22);
+        addChar(186, 0x3fffe3,   22);
+        addChar(187, 0x3fffe4,   22);
+        addChar(188, 0x7ffff0,   23);
+        addChar(189, 0x3fffe5,   22);
+        addChar(190, 0x3fffe6,   22);
+        addChar(191, 0x7ffff1,   23);
+        addChar(192, 0x3ffffe0,  26);
+        addChar(193, 0x3ffffe1,  26);
+        addChar(194, 0xfffeb,    20);
+        addChar(195, 0x7fff1,    19);
+        addChar(196, 0x3fffe7,   22);
+        addChar(197, 0x7ffff2,   23);
+        addChar(198, 0x3fffe8,   22);
+        addChar(199, 0x1ffffec,  25);
+        addChar(200, 0x3ffffe2,  26);
+        addChar(201, 0x3ffffe3,  26);
+        addChar(202, 0x3ffffe4,  26);
+        addChar(203, 0x7ffffde,  27);
+        addChar(204, 0x7ffffdf,  27);
+        addChar(205, 0x3ffffe5,  26);
+        addChar(206, 0xfffff1,   24);
+        addChar(207, 0x1ffffed,  25);
+        addChar(208, 0x7fff2,    19);
+        addChar(209, 0x1fffe3,   21);
+        addChar(210, 0x3ffffe6,  26);
+        addChar(211, 0x7ffffe0,  27);
+        addChar(212, 0x7ffffe1,  27);
+        addChar(213, 0x3ffffe7,  26);
+        addChar(214, 0x7ffffe2,  27);
+        addChar(215, 0xfffff2,   24);
+        addChar(216, 0x1fffe4,   21);
+        addChar(217, 0x1fffe5,   21);
+        addChar(218, 0x3ffffe8,  26);
+        addChar(219, 0x3ffffe9,  26);
+        addChar(220, 0xffffffd,  28);
+        addChar(221, 0x7ffffe3,  27);
+        addChar(222, 0x7ffffe4,  27);
+        addChar(223, 0x7ffffe5,  27);
+        addChar(224, 0xfffec,    20);
+        addChar(225, 0xfffff3,   24);
+        addChar(226, 0xfffed,    20);
+        addChar(227, 0x1fffe6,   21);
+        addChar(228, 0x3fffe9,   22);
+        addChar(229, 0x1fffe7,   21);
+        addChar(230, 0x1fffe8,   21);
+        addChar(231, 0x7ffff3,   23);
+        addChar(232, 0x3fffea,   22);
+        addChar(233, 0x3fffeb,   22);
+        addChar(234, 0x1ffffee,  25);
+        addChar(235, 0x1ffffef,  25);
+        addChar(236, 0xfffff4,   24);
+        addChar(237, 0xfffff5,   24);
+        addChar(238, 0x3ffffea,  26);
+        addChar(239, 0x7ffff4,   23);
+        addChar(240, 0x3ffffeb,  26);
+        addChar(241, 0x7ffffe6,  27);
+        addChar(242, 0x3ffffec,  26);
+        addChar(243, 0x3ffffed,  26);
+        addChar(244, 0x7ffffe7,  27);
+        addChar(245, 0x7ffffe8,  27);
+        addChar(246, 0x7ffffe9,  27);
+        addChar(247, 0x7ffffea,  27);
+        addChar(248, 0x7ffffeb,  27);
+        addChar(249, 0xffffffe,  28);
+        addChar(250, 0x7ffffec,  27);
+        addChar(251, 0x7ffffed,  27);
+        addChar(252, 0x7ffffee,  27);
+        addChar(253, 0x7ffffef,  27);
+        addChar(254, 0x7fffff0,  27);
+        addChar(255, 0x3ffffee,  26);
+        addEOS (256, EOS.code,   EOS.length);
+        // @formatter:on
+    }
+
+
+    /**
+     * Calculates the number of bytes required to represent the given {@code
+     * CharSequence} with the Huffman coding.
+     *
+     * @param value
+     *         characters
+     *
+     * @return number of bytes
+     *
+     * @throws NullPointerException
+     *         if the value is null
+     */
+    public int lengthOf(CharSequence value) {
+        return lengthOf(value, 0, value.length());
+    }
+
+    /**
+     * Calculates the number of bytes required to represent a subsequence of the
+     * given {@code CharSequence} with the Huffman coding.
+     *
+     * @param value
+     *         characters
+     * @param start
+     *         the start index, inclusive
+     * @param end
+     *         the end index, exclusive
+     *
+     * @return number of bytes
+     *
+     * @throws NullPointerException
+     *         if the value is null
+     * @throws IndexOutOfBoundsException
+     *         if any invocation of {@code value.charAt(i)}, where
+     *         {@code start <= i < end} would throw an IndexOutOfBoundsException
+     */
+    public int lengthOf(CharSequence value, int start, int end) {
+        int len = 0;
+        for (int i = start; i < end; i++) {
+            char c = value.charAt(i);
+            len += INSTANCE.codeOf(c).length;
+        }
+        // Integer division with ceiling, assumption:
+        assert (len / 8 + (len % 8 != 0 ? 1 : 0)) == (len + 7) / 8 : len;
+        return (len + 7) / 8;
+    }
+
+    private void addChar(int c, int code, int bitLength) {
+        addLeaf(c, code, bitLength, false);
+        codes[c] = new Code(code, bitLength);
+    }
+
+    private void addEOS(int c, int code, int bitLength) {
+        addLeaf(c, code, bitLength, true);
+        codes[c] = new Code(code, bitLength);
+    }
+
+    private void addLeaf(int c, int code, int bitLength, boolean isEOS) {
+        if (bitLength < 1) {
+            throw new IllegalArgumentException("bitLength < 1");
+        }
+        Node curr = root;
+        for (int p = 1 << bitLength - 1; p != 0 && !curr.isLeaf(); p = p >> 1) {
+            curr.isEOSPath |= isEOS; // If it's already true, it can't become false
+            curr = curr.addChildIfAbsent(p & code);
+        }
+        curr.isEOSPath |= isEOS; // The last one needs to have this property as well
+        if (curr.isLeaf()) {
+            throw new IllegalStateException("Specified code is already taken");
+        }
+        curr.setChar((char) c);
+    }
+
+    private Code codeOf(char c) {
+        if (c > 255) {
+            throw new IllegalArgumentException("char=" + ((int) c));
+        }
+        return codes[c];
+    }
+
+    //
+    // For debugging/testing purposes
+    //
+    Node getRoot() {
+        return root;
+    }
+
+    //
+    // Guarantees:
+    //
+    //  if (isLeaf() == true) => getChar() is a legal call
+    //  if (isLeaf() == false) => getChild(i) is a legal call (though it can
+    //                                                           return null)
+    //
+    static class Node {
+
+        Node left;
+        Node right;
+        boolean isEOSPath;
+
+        boolean charIsSet;
+        char c;
+
+        Node getChild(int selector) {
+            if (isLeaf()) {
+                throw new IllegalStateException("This is a leaf node");
+            }
+            Node result = selector == 0 ? left : right;
+            if (result == null) {
+                throw new IllegalStateException(format(
+                        "Node doesn't have a child (selector=%s)", selector));
+            }
+            return result;
+        }
+
+        boolean isLeaf() {
+            return charIsSet;
+        }
+
+        char getChar() {
+            if (!isLeaf()) {
+                throw new IllegalStateException("This node is not a leaf node");
+            }
+            return c;
+        }
+
+        void setChar(char c) {
+            if (charIsSet) {
+                throw new IllegalStateException(
+                        "This node has been taken already");
+            }
+            if (left != null || right != null) {
+                throw new IllegalStateException("The node cannot be made "
+                                                        + "a leaf as it's already has a child");
+            }
+            this.c = c;
+            charIsSet = true;
+        }
+
+        Node addChildIfAbsent(int i) {
+            if (charIsSet) {
+                throw new IllegalStateException("The node cannot have a child "
+                                                        + "as it's already a leaf node");
+            }
+            Node child;
+            if (i == 0) {
+                if ((child = left) == null) {
+                    child = left = new Node();
+                }
+            } else {
+                if ((child = right) == null) {
+                    child = right = new Node();
+                }
+            }
+            return child;
+        }
+
+        @Override
+        public String toString() {
+            if (isLeaf()) {
+                if (isEOSPath) {
+                    return "EOS";
+                } else {
+                    return format("char: (%3s) '%s'", (int) c, c);
+                }
+            }
+            return "/\\";
+        }
+    }
+
+    // TODO: value-based class?
+    // FIXME: can we re-use Node instead of this class?
+    private static final class Code {
+
+        final int code;
+        final int length;
+
+        private Code(int code, int length) {
+            this.code = code;
+            this.length = length;
+        }
+
+        public int getCode() {
+            return code;
+        }
+
+        public int getLength() {
+            return length;
+        }
+
+        @Override
+        public String toString() {
+            long p = 1 << length;
+            return Long.toBinaryString(code + p).substring(1)
+                    + ", length=" + length;
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/hpack/QuickHuffman.java	Wed May 02 02:36:17 2018 -0700
@@ -0,0 +1,826 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.internal.net.http.hpack;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.List;
+import java.util.Objects;
+
+public final class QuickHuffman {
+
+    /*
+     * Huffman codes for encoding.
+     *
+     * EOS will never be matched, since there is no symbol for it, thus no need
+     * to store it in the array. Thus, the length of the array is 256, not 257.
+     * Code information for each character is encoded as follows:
+     *
+     *     MSB                             LSB
+     *     +----------------+----------------+
+     *     |~code           |         length~|
+     *     +----------------+----------------+
+     *     |<----- 32 ----->|<----- 32 ----->|
+     *     |<------------- 64 -------------->|
+     *
+     * The leftmost 32 bits hold the code value. This value is aligned left
+     * (or MSB). The rightmost 32 bits hold the length of the code value.
+     * This length is aligned right (or LSB).
+     */
+    private static final long[] codes = new long[256];
+
+    private static long codeValueOf(char c) {
+        return codes[c] & 0xffffffff00000000L;
+    }
+
+    private static long codeLengthOf(char c) {
+        return codes[c] & 0x00000000ffffffffL;
+    }
+
+    private static final int EOS_LENGTH = 30;
+    private static final int EOS_LSB = 0x3fffffff;
+    private static final long EOS_MSB = EOS_LSB << (64 - EOS_LENGTH);
+
+    /*
+     * Huffman codes for decoding.
+     *
+     * Root node contains 257 descendant nodes, including EOS.
+     */
+    private static final Node root;
+
+    static {
+        TemporaryNode tmpRoot = new TemporaryNode();
+        addChar(tmpRoot,   0, 0x1ff8,     13);
+        addChar(tmpRoot,   1, 0x7fffd8,   23);
+        addChar(tmpRoot,   2, 0xfffffe2,  28);
+        addChar(tmpRoot,   3, 0xfffffe3,  28);
+        addChar(tmpRoot,   4, 0xfffffe4,  28);
+        addChar(tmpRoot,   5, 0xfffffe5,  28);
+        addChar(tmpRoot,   6, 0xfffffe6,  28);
+        addChar(tmpRoot,   7, 0xfffffe7,  28);
+        addChar(tmpRoot,   8, 0xfffffe8,  28);
+        addChar(tmpRoot,   9, 0xffffea,   24);
+        addChar(tmpRoot,  10, 0x3ffffffc, 30);
+        addChar(tmpRoot,  11, 0xfffffe9,  28);
+        addChar(tmpRoot,  12, 0xfffffea,  28);
+        addChar(tmpRoot,  13, 0x3ffffffd, 30);
+        addChar(tmpRoot,  14, 0xfffffeb,  28);
+        addChar(tmpRoot,  15, 0xfffffec,  28);
+        addChar(tmpRoot,  16, 0xfffffed,  28);
+        addChar(tmpRoot,  17, 0xfffffee,  28);
+        addChar(tmpRoot,  18, 0xfffffef,  28);
+        addChar(tmpRoot,  19, 0xffffff0,  28);
+        addChar(tmpRoot,  20, 0xffffff1,  28);
+        addChar(tmpRoot,  21, 0xffffff2,  28);
+        addChar(tmpRoot,  22, 0x3ffffffe, 30);
+        addChar(tmpRoot,  23, 0xffffff3,  28);
+        addChar(tmpRoot,  24, 0xffffff4,  28);
+        addChar(tmpRoot,  25, 0xffffff5,  28);
+        addChar(tmpRoot,  26, 0xffffff6,  28);
+        addChar(tmpRoot,  27, 0xffffff7,  28);
+        addChar(tmpRoot,  28, 0xffffff8,  28);
+        addChar(tmpRoot,  29, 0xffffff9,  28);
+        addChar(tmpRoot,  30, 0xffffffa,  28);
+        addChar(tmpRoot,  31, 0xffffffb,  28);
+        addChar(tmpRoot,  32, 0x14,        6);
+        addChar(tmpRoot,  33, 0x3f8,      10);
+        addChar(tmpRoot,  34, 0x3f9,      10);
+        addChar(tmpRoot,  35, 0xffa,      12);
+        addChar(tmpRoot,  36, 0x1ff9,     13);
+        addChar(tmpRoot,  37, 0x15,        6);
+        addChar(tmpRoot,  38, 0xf8,        8);
+        addChar(tmpRoot,  39, 0x7fa,      11);
+        addChar(tmpRoot,  40, 0x3fa,      10);
+        addChar(tmpRoot,  41, 0x3fb,      10);
+        addChar(tmpRoot,  42, 0xf9,        8);
+        addChar(tmpRoot,  43, 0x7fb,      11);
+        addChar(tmpRoot,  44, 0xfa,        8);
+        addChar(tmpRoot,  45, 0x16,        6);
+        addChar(tmpRoot,  46, 0x17,        6);
+        addChar(tmpRoot,  47, 0x18,        6);
+        addChar(tmpRoot,  48, 0x0,         5);
+        addChar(tmpRoot,  49, 0x1,         5);
+        addChar(tmpRoot,  50, 0x2,         5);
+        addChar(tmpRoot,  51, 0x19,        6);
+        addChar(tmpRoot,  52, 0x1a,        6);
+        addChar(tmpRoot,  53, 0x1b,        6);
+        addChar(tmpRoot,  54, 0x1c,        6);
+        addChar(tmpRoot,  55, 0x1d,        6);
+        addChar(tmpRoot,  56, 0x1e,        6);
+        addChar(tmpRoot,  57, 0x1f,        6);
+        addChar(tmpRoot,  58, 0x5c,        7);
+        addChar(tmpRoot,  59, 0xfb,        8);
+        addChar(tmpRoot,  60, 0x7ffc,     15);
+        addChar(tmpRoot,  61, 0x20,        6);
+        addChar(tmpRoot,  62, 0xffb,      12);
+        addChar(tmpRoot,  63, 0x3fc,      10);
+        addChar(tmpRoot,  64, 0x1ffa,     13);
+        addChar(tmpRoot,  65, 0x21,        6);
+        addChar(tmpRoot,  66, 0x5d,        7);
+        addChar(tmpRoot,  67, 0x5e,        7);
+        addChar(tmpRoot,  68, 0x5f,        7);
+        addChar(tmpRoot,  69, 0x60,        7);
+        addChar(tmpRoot,  70, 0x61,        7);
+        addChar(tmpRoot,  71, 0x62,        7);
+        addChar(tmpRoot,  72, 0x63,        7);
+        addChar(tmpRoot,  73, 0x64,        7);
+        addChar(tmpRoot,  74, 0x65,        7);
+        addChar(tmpRoot,  75, 0x66,        7);
+        addChar(tmpRoot,  76, 0x67,        7);
+        addChar(tmpRoot,  77, 0x68,        7);
+        addChar(tmpRoot,  78, 0x69,        7);
+        addChar(tmpRoot,  79, 0x6a,        7);
+        addChar(tmpRoot,  80, 0x6b,        7);
+        addChar(tmpRoot,  81, 0x6c,        7);
+        addChar(tmpRoot,  82, 0x6d,        7);
+        addChar(tmpRoot,  83, 0x6e,        7);
+        addChar(tmpRoot,  84, 0x6f,        7);
+        addChar(tmpRoot,  85, 0x70,        7);
+        addChar(tmpRoot,  86, 0x71,        7);
+        addChar(tmpRoot,  87, 0x72,        7);
+        addChar(tmpRoot,  88, 0xfc,        8);
+        addChar(tmpRoot,  89, 0x73,        7);
+        addChar(tmpRoot,  90, 0xfd,        8);
+        addChar(tmpRoot,  91, 0x1ffb,     13);
+        addChar(tmpRoot,  92, 0x7fff0,    19);
+        addChar(tmpRoot,  93, 0x1ffc,     13);
+        addChar(tmpRoot,  94, 0x3ffc,     14);
+        addChar(tmpRoot,  95, 0x22,        6);
+        addChar(tmpRoot,  96, 0x7ffd,     15);
+        addChar(tmpRoot,  97, 0x3,         5);
+        addChar(tmpRoot,  98, 0x23,        6);
+        addChar(tmpRoot,  99, 0x4,         5);
+        addChar(tmpRoot, 100, 0x24,        6);
+        addChar(tmpRoot, 101, 0x5,         5);
+        addChar(tmpRoot, 102, 0x25,        6);
+        addChar(tmpRoot, 103, 0x26,        6);
+        addChar(tmpRoot, 104, 0x27,        6);
+        addChar(tmpRoot, 105, 0x6,         5);
+        addChar(tmpRoot, 106, 0x74,        7);
+        addChar(tmpRoot, 107, 0x75,        7);
+        addChar(tmpRoot, 108, 0x28,        6);
+        addChar(tmpRoot, 109, 0x29,        6);
+        addChar(tmpRoot, 110, 0x2a,        6);
+        addChar(tmpRoot, 111, 0x7,         5);
+        addChar(tmpRoot, 112, 0x2b,        6);
+        addChar(tmpRoot, 113, 0x76,        7);
+        addChar(tmpRoot, 114, 0x2c,        6);
+        addChar(tmpRoot, 115, 0x8,         5);
+        addChar(tmpRoot, 116, 0x9,         5);
+        addChar(tmpRoot, 117, 0x2d,        6);
+        addChar(tmpRoot, 118, 0x77,        7);
+        addChar(tmpRoot, 119, 0x78,        7);
+        addChar(tmpRoot, 120, 0x79,        7);
+        addChar(tmpRoot, 121, 0x7a,        7);
+        addChar(tmpRoot, 122, 0x7b,        7);
+        addChar(tmpRoot, 123, 0x7ffe,     15);
+        addChar(tmpRoot, 124, 0x7fc,      11);
+        addChar(tmpRoot, 125, 0x3ffd,     14);
+        addChar(tmpRoot, 126, 0x1ffd,     13);
+        addChar(tmpRoot, 127, 0xffffffc,  28);
+        addChar(tmpRoot, 128, 0xfffe6,    20);
+        addChar(tmpRoot, 129, 0x3fffd2,   22);
+        addChar(tmpRoot, 130, 0xfffe7,    20);
+        addChar(tmpRoot, 131, 0xfffe8,    20);
+        addChar(tmpRoot, 132, 0x3fffd3,   22);
+        addChar(tmpRoot, 133, 0x3fffd4,   22);
+        addChar(tmpRoot, 134, 0x3fffd5,   22);
+        addChar(tmpRoot, 135, 0x7fffd9,   23);
+        addChar(tmpRoot, 136, 0x3fffd6,   22);
+        addChar(tmpRoot, 137, 0x7fffda,   23);
+        addChar(tmpRoot, 138, 0x7fffdb,   23);
+        addChar(tmpRoot, 139, 0x7fffdc,   23);
+        addChar(tmpRoot, 140, 0x7fffdd,   23);
+        addChar(tmpRoot, 141, 0x7fffde,   23);
+        addChar(tmpRoot, 142, 0xffffeb,   24);
+        addChar(tmpRoot, 143, 0x7fffdf,   23);
+        addChar(tmpRoot, 144, 0xffffec,   24);
+        addChar(tmpRoot, 145, 0xffffed,   24);
+        addChar(tmpRoot, 146, 0x3fffd7,   22);
+        addChar(tmpRoot, 147, 0x7fffe0,   23);
+        addChar(tmpRoot, 148, 0xffffee,   24);
+        addChar(tmpRoot, 149, 0x7fffe1,   23);
+        addChar(tmpRoot, 150, 0x7fffe2,   23);
+        addChar(tmpRoot, 151, 0x7fffe3,   23);
+        addChar(tmpRoot, 152, 0x7fffe4,   23);
+        addChar(tmpRoot, 153, 0x1fffdc,   21);
+        addChar(tmpRoot, 154, 0x3fffd8,   22);
+        addChar(tmpRoot, 155, 0x7fffe5,   23);
+        addChar(tmpRoot, 156, 0x3fffd9,   22);
+        addChar(tmpRoot, 157, 0x7fffe6,   23);
+        addChar(tmpRoot, 158, 0x7fffe7,   23);
+        addChar(tmpRoot, 159, 0xffffef,   24);
+        addChar(tmpRoot, 160, 0x3fffda,   22);
+        addChar(tmpRoot, 161, 0x1fffdd,   21);
+        addChar(tmpRoot, 162, 0xfffe9,    20);
+        addChar(tmpRoot, 163, 0x3fffdb,   22);
+        addChar(tmpRoot, 164, 0x3fffdc,   22);
+        addChar(tmpRoot, 165, 0x7fffe8,   23);
+        addChar(tmpRoot, 166, 0x7fffe9,   23);
+        addChar(tmpRoot, 167, 0x1fffde,   21);
+        addChar(tmpRoot, 168, 0x7fffea,   23);
+        addChar(tmpRoot, 169, 0x3fffdd,   22);
+        addChar(tmpRoot, 170, 0x3fffde,   22);
+        addChar(tmpRoot, 171, 0xfffff0,   24);
+        addChar(tmpRoot, 172, 0x1fffdf,   21);
+        addChar(tmpRoot, 173, 0x3fffdf,   22);
+        addChar(tmpRoot, 174, 0x7fffeb,   23);
+        addChar(tmpRoot, 175, 0x7fffec,   23);
+        addChar(tmpRoot, 176, 0x1fffe0,   21);
+        addChar(tmpRoot, 177, 0x1fffe1,   21);
+        addChar(tmpRoot, 178, 0x3fffe0,   22);
+        addChar(tmpRoot, 179, 0x1fffe2,   21);
+        addChar(tmpRoot, 180, 0x7fffed,   23);
+        addChar(tmpRoot, 181, 0x3fffe1,   22);
+        addChar(tmpRoot, 182, 0x7fffee,   23);
+        addChar(tmpRoot, 183, 0x7fffef,   23);
+        addChar(tmpRoot, 184, 0xfffea,    20);
+        addChar(tmpRoot, 185, 0x3fffe2,   22);
+        addChar(tmpRoot, 186, 0x3fffe3,   22);
+        addChar(tmpRoot, 187, 0x3fffe4,   22);
+        addChar(tmpRoot, 188, 0x7ffff0,   23);
+        addChar(tmpRoot, 189, 0x3fffe5,   22);
+        addChar(tmpRoot, 190, 0x3fffe6,   22);
+        addChar(tmpRoot, 191, 0x7ffff1,   23);
+        addChar(tmpRoot, 192, 0x3ffffe0,  26);
+        addChar(tmpRoot, 193, 0x3ffffe1,  26);
+        addChar(tmpRoot, 194, 0xfffeb,    20);
+        addChar(tmpRoot, 195, 0x7fff1,    19);
+        addChar(tmpRoot, 196, 0x3fffe7,   22);
+        addChar(tmpRoot, 197, 0x7ffff2,   23);
+        addChar(tmpRoot, 198, 0x3fffe8,   22);
+        addChar(tmpRoot, 199, 0x1ffffec,  25);
+        addChar(tmpRoot, 200, 0x3ffffe2,  26);
+        addChar(tmpRoot, 201, 0x3ffffe3,  26);
+        addChar(tmpRoot, 202, 0x3ffffe4,  26);
+        addChar(tmpRoot, 203, 0x7ffffde,  27);
+        addChar(tmpRoot, 204, 0x7ffffdf,  27);
+        addChar(tmpRoot, 205, 0x3ffffe5,  26);
+        addChar(tmpRoot, 206, 0xfffff1,   24);
+        addChar(tmpRoot, 207, 0x1ffffed,  25);
+        addChar(tmpRoot, 208, 0x7fff2,    19);
+        addChar(tmpRoot, 209, 0x1fffe3,   21);
+        addChar(tmpRoot, 210, 0x3ffffe6,  26);
+        addChar(tmpRoot, 211, 0x7ffffe0,  27);
+        addChar(tmpRoot, 212, 0x7ffffe1,  27);
+        addChar(tmpRoot, 213, 0x3ffffe7,  26);
+        addChar(tmpRoot, 214, 0x7ffffe2,  27);
+        addChar(tmpRoot, 215, 0xfffff2,   24);
+        addChar(tmpRoot, 216, 0x1fffe4,   21);
+        addChar(tmpRoot, 217, 0x1fffe5,   21);
+        addChar(tmpRoot, 218, 0x3ffffe8,  26);
+        addChar(tmpRoot, 219, 0x3ffffe9,  26);
+        addChar(tmpRoot, 220, 0xffffffd,  28);
+        addChar(tmpRoot, 221, 0x7ffffe3,  27);
+        addChar(tmpRoot, 222, 0x7ffffe4,  27);
+        addChar(tmpRoot, 223, 0x7ffffe5,  27);
+        addChar(tmpRoot, 224, 0xfffec,    20);
+        addChar(tmpRoot, 225, 0xfffff3,   24);
+        addChar(tmpRoot, 226, 0xfffed,    20);
+        addChar(tmpRoot, 227, 0x1fffe6,   21);
+        addChar(tmpRoot, 228, 0x3fffe9,   22);
+        addChar(tmpRoot, 229, 0x1fffe7,   21);
+        addChar(tmpRoot, 230, 0x1fffe8,   21);
+        addChar(tmpRoot, 231, 0x7ffff3,   23);
+        addChar(tmpRoot, 232, 0x3fffea,   22);
+        addChar(tmpRoot, 233, 0x3fffeb,   22);
+        addChar(tmpRoot, 234, 0x1ffffee,  25);
+        addChar(tmpRoot, 235, 0x1ffffef,  25);
+        addChar(tmpRoot, 236, 0xfffff4,   24);
+        addChar(tmpRoot, 237, 0xfffff5,   24);
+        addChar(tmpRoot, 238, 0x3ffffea,  26);
+        addChar(tmpRoot, 239, 0x7ffff4,   23);
+        addChar(tmpRoot, 240, 0x3ffffeb,  26);
+        addChar(tmpRoot, 241, 0x7ffffe6,  27);
+        addChar(tmpRoot, 242, 0x3ffffec,  26);
+        addChar(tmpRoot, 243, 0x3ffffed,  26);
+        addChar(tmpRoot, 244, 0x7ffffe7,  27);
+        addChar(tmpRoot, 245, 0x7ffffe8,  27);
+        addChar(tmpRoot, 246, 0x7ffffe9,  27);
+        addChar(tmpRoot, 247, 0x7ffffea,  27);
+        addChar(tmpRoot, 248, 0x7ffffeb,  27);
+        addChar(tmpRoot, 249, 0xffffffe,  28);
+        addChar(tmpRoot, 250, 0x7ffffec,  27);
+        addChar(tmpRoot, 251, 0x7ffffed,  27);
+        addChar(tmpRoot, 252, 0x7ffffee,  27);
+        addChar(tmpRoot, 253, 0x7ffffef,  27);
+        addChar(tmpRoot, 254, 0x7fffff0,  27);
+        addChar(tmpRoot, 255, 0x3ffffee,  26);
+        addEOS (tmpRoot, 256, EOS_LSB, EOS_LENGTH);
+
+        // The difference in performance can always be checked by not using
+        // the immutable trie:
+        //     root = tmpRoot;
+        root = ImmutableNode.copyOf(tmpRoot);
+    }
+
+    private QuickHuffman() { }
+
+    private static void addChar(Node root, int symbol, int code, int bitLength)
+    {
+        addLeaf(root, (char) symbol, code, bitLength, false);
+        long value = ((long) code) << (64 - bitLength); // re-align MSB <- LSB
+        codes[symbol] = value | bitLength;
+    }
+
+    private static void addEOS(Node root, int symbol, int code, int bitLength)
+    {
+        addLeaf(root, (char) symbol, code, bitLength, true);
+    }
+
+    private static void addLeaf(Node root,
+                                char symbol,
+                                int code,
+                                int bitLength,
+                                boolean isEOS)
+    {
+        assert 0 < bitLength && bitLength <= 32 : bitLength;
+        Node curr = root;
+        int nBytes = bytesForBits(bitLength);
+        // The number of bits the code needs to be shifted to the left in order
+        // to align with the byte #nBytes boundary:
+        int align = (nBytes << 3) - bitLength;
+        code <<= align;
+        // descend the trie until the last element
+        int l = 0;
+        for (int i = 0, probe = 0xff << ((nBytes - 1) << 3);
+             i < nBytes - 1;
+             i++, probe >>>= 8)
+        {
+            curr.setEOSPath(curr.isEOSPath() | isEOS);
+            int idx = (code & probe) >>> ((nBytes - 1 - i) << 3);
+            curr = curr.getOrCreateChild(idx);
+            curr.setLength(8);
+            l += 8;
+        }
+        // Assign the same char to all byte variants. For example, if the code
+        // and its length are 00011b and 5 respectively (letter 'a') then, in
+        // order to be able to match any byte starting with 00011b prefix,
+        // the following nodes need to be created:
+        //
+        //     00011000b, 00011001b, 00011010b, 00011011b, 00011100b, 00011101b,
+        //     00011110b and 00011111b
+        int idx = code & 0xff;
+        curr.setEOSPath(curr.isEOSPath() | isEOS);
+        for (int i = 0; i < (1 << align); i++) {
+            Node child = curr.getOrCreateChild(idx | i);
+            child.setSymbol(symbol);
+            child.setEOSPath(child.isEOSPath() | isEOS);
+            child.setLength(bitLength - l);
+        }
+    }
+
+    /*
+     * A node in the Huffman trie.
+     */
+    interface Node {
+
+        boolean isEOSPath();
+
+        void setEOSPath(boolean value);
+
+        boolean isLeaf();
+
+        Node getChild(int index);
+
+        Node getOrCreateChild(int index);
+
+        Node[] getChildren();
+
+        char getSymbol();
+
+        void setSymbol(char symbol);
+
+        int getLength();
+
+        void setLength(int value);
+    }
+
+    /*
+     * Mutable nodes used for initial construction of the Huffman trie.
+     * (These nodes are perfectly ok to be used after that.)
+     */
+    static final class TemporaryNode implements Node {
+
+        private char symbol;
+        private boolean eosPath;
+        private TemporaryNode[] children;
+        private int length;
+
+        @Override
+        public TemporaryNode getOrCreateChild(int index) {
+            ensureChildrenExist();
+            if (children[index] == null) {
+                children[index] = new TemporaryNode();
+            }
+            return children[index];
+        }
+
+        private void ensureChildrenExist() {
+            if (children == null) {
+                children = new TemporaryNode[256];
+            }
+        }
+
+        @Override
+        public boolean isLeaf() {
+            return children == null;
+        }
+
+        @Override
+        public boolean isEOSPath() {
+            return eosPath;
+        }
+
+        @Override
+        public void setEOSPath(boolean value) {
+            eosPath = value;
+        }
+
+        @Override
+        public TemporaryNode getChild(int index) {
+            ensureChildrenExist();
+            return children[index];
+        }
+
+        @Override
+        public Node[] getChildren() {
+            if (children == null) {
+                return new Node[0];
+            }
+            return children;
+        }
+
+        @Override
+        public char getSymbol() {
+            return symbol;
+        }
+
+        @Override
+        public int getLength() {
+            return length;
+        }
+
+        @Override
+        public void setSymbol(char value) {
+            this.symbol = value;
+        }
+
+        @Override
+        public void setLength(int value) {
+            this.length = value;
+        }
+    }
+
+    /*
+     * Immutable node used to construct traversal-only Huffman trie.
+     *
+     * Once the trie has been built, the support of modifications is no longer
+     * required. An immutable trie should be used. Not only it will help to
+     * catch possible bugs, but hopefully speedup the traversal operations.
+     */
+    static final class ImmutableNode implements Node {
+
+        private final char symbol;
+        private final boolean eosPath;
+        private final int length;
+        private final List<ImmutableNode> children;
+
+        public static ImmutableNode copyOf(Node node) {
+            if (node.isLeaf()) {
+                return new ImmutableNode(node.getSymbol(), node.isEOSPath(),
+                                         node.getLength());
+            }
+            Node[] children = node.getChildren();
+            ImmutableNode[] immutableChildren = new ImmutableNode[children.length];
+            for (int i = 0; i < children.length; i++) {
+                immutableChildren[i] = copyOf(children[i]);
+            }
+            return new ImmutableNode(node.isEOSPath(), node.getLength(),
+                                     immutableChildren);
+        }
+
+        /* Creates a leaf node */
+        private ImmutableNode(char symbol,
+                              boolean eosPath,
+                              int length) {
+            this.symbol = symbol;
+            this.eosPath = eosPath;
+            this.length = length;
+            this.children = List.of();
+        }
+
+        /* Creates a node with children */
+        private ImmutableNode(boolean eosPath,
+                              int length,
+                              ImmutableNode[] children)
+        {
+            this.symbol = 0;
+            this.eosPath = eosPath;
+            this.length = length;
+            if (children.length == 0) {
+                throw new IllegalArgumentException();
+            }
+            // A list produced by List.of should not be slower than array for
+            // accessing elements by index, and hopefully use additional
+            // optimizations (e.g. jdk.internal.vm.annotation.Stable)
+            this.children = List.of(children);
+        }
+
+        @Override
+        public boolean isLeaf() {
+            return children.isEmpty();
+        }
+
+        @Override
+        public boolean isEOSPath() {
+            return eosPath;
+        }
+
+        @Override
+        public void setEOSPath(boolean value) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public ImmutableNode getChild(int index) {
+            return children.get(index);
+        }
+
+        @Override
+        public ImmutableNode getOrCreateChild(int index) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public ImmutableNode[] getChildren() {
+            // This method is not expected to be called on an immutable node.
+            // If it is called, it requires some investigation.
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public char getSymbol() {
+            return symbol;
+        }
+
+        @Override
+        public void setSymbol(char symbol) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public int getLength() {
+            return length;
+        }
+
+        @Override
+        public void setLength(int value) {
+            throw new UnsupportedOperationException();
+        }
+    }
+
+    static final class Reader implements Huffman.Reader {
+
+        private Node curr = root;  // current position in the trie
+        private long buffer;       // bits left from the previous match (aligned to the left, or MSB)
+        private int bufferLen;     // number of bits in the buffer
+        private int len;           // length (in bits) of path to curr
+        private boolean done;
+
+        @Override
+        public void read(ByteBuffer source,
+                         Appendable destination,
+                         boolean isLast) throws IOException
+        {
+            read(source, destination, true, isLast);
+        }
+
+        @Override
+        public void reset() {
+            curr = root;
+            len = 0;
+            buffer = 0;
+            bufferLen = 0;
+            done = false;
+        }
+
+        @SuppressWarnings("fallthrough")
+        void read(ByteBuffer source,
+                  Appendable destination,
+                  boolean reportEOS, /* reportEOS is exposed for tests */
+                  boolean isLast) throws IOException
+        {
+            while (!done) {
+                // read as much as possible (up to 8 bytes)
+                int remaining = source.remaining();
+                int nBytes = Math.min((64 - bufferLen) >> 3, remaining);
+                switch (nBytes) {
+                    case 0:
+                        break;
+                    case 3:
+                        readByte(source);
+                    case 2:
+                        readByte(source);
+                    case 1:
+                        readByte(source);
+                        break;
+                    case 7:
+                        readByte(source);
+                    case 6:
+                        readByte(source);
+                    case 5:
+                        readByte(source);
+                    case 4:
+                        readInt(source);
+                        break;
+                    case 8:
+                        readLong(source);
+                        break;
+                    default:
+                        throw new InternalError(String.valueOf(nBytes));
+                }
+                // write as much as possible
+                while (true) {
+                    if (bufferLen < 8) {
+                        if (nBytes < remaining) { // read again
+                            break;
+                        } else if (!isLast) { // exit the method to accept more input
+                            return;
+                        } else if (bufferLen > 0) { // no more data is expected, pad
+                            // (this padding may be done more than once)
+                            buffer |= ((0xff00000000000000L >>> bufferLen)
+                                    & 0xff00000000000000L);
+                            // do not update bufferLen, since all those ones are
+                            // synthetic and are appended merely for the sake of
+                            // lookup
+                        } else {
+                            done = true;
+                            break;
+                        }
+                    }
+                    int idx = (int) (buffer >>> 56);
+                    Node node = curr.getChild(idx);
+                    if (node == null) { // TODO: TEST
+                        throw new IOException("Unexpected byte");
+                    }
+                    if (node.isLeaf()) {
+                        if (node.getLength() > bufferLen) { // matched more than we actually could (because of padding)
+                            throw new IOException(
+                                    "Not a EOS prefix padding or unexpected end of data");
+                        }
+                        if (reportEOS && node.isEOSPath()) {
+                            throw new IOException("Encountered EOS");
+                        }
+                        destination.append(node.getSymbol());
+                        curr = root;
+                        len = 0;
+                    } else {
+                        curr = node;
+                        // because of the padding, we can't match more bits than
+                        // there are currently in the buffer
+                        len += Math.min(bufferLen, node.getLength());
+                    }
+                    buffer <<= node.getLength();
+                    bufferLen -= node.getLength();
+                }
+                if (done && (curr.isEOSPath() && len > 7)) {
+                    throw new IOException(
+                            "Padding is too long (len=" + len + ") "
+                                    + "or unexpected end of data");
+                }
+            }
+        }
+
+        private void readLong(ByteBuffer source) {
+            buffer = source.getLong();
+            bufferLen = 64;
+        }
+
+        private void readInt(ByteBuffer source) {
+            long b;
+            b = source.getInt() & 0x00000000ffffffffL;
+            buffer |= (b << (32 - bufferLen));
+            bufferLen += 32;
+        }
+
+        private void readByte(ByteBuffer source) {
+            long b = source.get() & 0x00000000000000ffL;
+            buffer |= (b << (56 - bufferLen));
+            bufferLen += 8;
+        }
+    }
+
+    static final class Writer implements Huffman.Writer {
+
+        private CharSequence source;
+        private boolean padded;
+        private int pos;
+        private int end;
+        private long buffer;
+        private int bufferLen;
+
+        @Override
+        public QuickHuffman.Writer from(CharSequence input, int start, int end) {
+            Objects.checkFromToIndex(start, end, input.length());
+            this.pos = start;
+            this.end = end;
+            this.source = input;
+            return this;
+        }
+
+        @SuppressWarnings("fallthrough")
+        @Override
+        public boolean write(ByteBuffer destination) {
+            while (true) {
+                while (bufferLen < 32 && pos < end) {
+                    char c = source.charAt(pos++);
+                    buffer |= (codeValueOf(c) >>> bufferLen); // append
+                    bufferLen += codeLengthOf(c);
+                }
+                if (bufferLen == 0) {
+                    return true;
+                }
+                if (pos >= end && !padded) { // no more chars, pad
+                    padded = true;
+                    buffer |= (EOS_MSB >>> bufferLen);
+                    bufferLen = bytesForBits(bufferLen) << 3;
+                }
+                // The number of bytes that can be written at once
+                // (calculating in bytes, not bits, since
+                //  destination.remaining() * 8 might overflow)
+
+                int nBytes = Math.min(bytesForBits(bufferLen), destination.remaining()); // ceil?
+                switch (nBytes) {
+                    case 0:
+                        return false;
+                    case 1:
+                    case 2:
+                    case 3:
+                        destination.put((byte) (buffer >>> 56));
+                        buffer <<= 8;
+                        bufferLen -= 8;
+                        break;
+                    default:
+                        destination.putInt((int) (buffer >>> 32));
+                        buffer <<= 32;
+                        bufferLen -= 32;
+                        break;
+                }
+            }
+        }
+
+        @Override
+        public QuickHuffman.Writer reset() {
+            source = null;
+            buffer = 0;
+            bufferLen = 0;
+            end = 0;
+            pos = 0;
+            padded = false;
+            return this;
+        }
+
+        @Override
+        public int lengthOf(CharSequence value, int start, int end) {
+            int len = 0;
+            for (int i = start; i < end; i++) {
+                char c = value.charAt(i);
+                len += codeLengthOf(c);
+            }
+            return bytesForBits(len);
+        }
+    }
+
+    /*
+     * Returns the number of bytes the given number of bits constitute.
+     */
+    private static int bytesForBits(int n) {
+        assert (n / 8 + (n % 8 != 0 ? 1 : 0)) == (n + 7) / 8
+                && (n + 7) / 8 == ((n + 7) >> 3) : n;
+        return (n + 7) >> 3;
+    }
+}
--- a/src/java.net.http/share/classes/jdk/internal/net/http/hpack/SimpleHeaderTable.java	Wed May 02 10:47:16 2018 +0200
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/hpack/SimpleHeaderTable.java	Wed May 02 02:36:17 2018 -0700
@@ -28,6 +28,7 @@
 
 import java.util.NoSuchElementException;
 
+import static jdk.internal.net.http.common.Utils.pow2Size;
 import static jdk.internal.net.http.hpack.HPACK.Logger.Level.EXTRA;
 import static jdk.internal.net.http.hpack.HPACK.Logger.Level.NORMAL;
 import static java.lang.String.format;
@@ -306,8 +307,8 @@
         Object[] elements;
 
         CircularBuffer(int capacity) {
-            this.capacity = capacity;
-            elements = new Object[capacity];
+            this.capacity = pow2Size(capacity);
+            elements = new Object[this.capacity];
         }
 
         void add(E elem) {
@@ -316,7 +317,7 @@
                         format("No room for '%s': capacity=%s", elem, capacity));
             }
             elements[head] = elem;
-            head = (head + 1) % capacity;
+            head = (head + 1) & (capacity - 1);
             size++;
         }
 
@@ -327,7 +328,7 @@
             }
             E elem = (E) elements[tail];
             elements[tail] = null;
-            tail = (tail + 1) % capacity;
+            tail = (tail + 1) & (capacity - 1);
             size--;
             return elem;
         }
@@ -339,7 +340,7 @@
                         format("0 <= index <= capacity: index=%s, capacity=%s",
                                index, capacity));
             }
-            int idx = (tail + (size - index - 1)) % capacity;
+            int idx = (tail + (size - index - 1)) & (capacity - 1);
             return (E) elements[idx];
         }
 
@@ -350,7 +351,8 @@
                                newCapacity, size));
             }
 
-            Object[] newElements = new Object[newCapacity];
+            int capacity = pow2Size(newCapacity);
+            Object[] newElements = new Object[capacity];
 
             if (tail < head || size == 0) {
                 System.arraycopy(elements, tail, newElements, 0, size);
@@ -362,7 +364,7 @@
             elements = newElements;
             tail = 0;
             head = size;
-            this.capacity = newCapacity;
+            this.capacity = capacity;
         }
     }
 }
--- a/src/java.net.http/share/classes/jdk/internal/net/http/hpack/StringReader.java	Wed May 02 10:47:16 2018 +0200
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/hpack/StringReader.java	Wed May 02 02:36:17 2018 -0700
@@ -44,7 +44,7 @@
     private static final int DONE            = 4;
 
     private final IntegerReader intReader = new IntegerReader();
-    private final Huffman.Reader huffmanReader = new Huffman.Reader();
+    private final Huffman.Reader huffmanReader = new QuickHuffman.Reader();
     private final ISO_8859_1.Reader plainReader = new ISO_8859_1.Reader();
 
     private int state = NEW;
--- a/src/java.net.http/share/classes/jdk/internal/net/http/hpack/StringWriter.java	Wed May 02 10:47:16 2018 +0200
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/hpack/StringWriter.java	Wed May 02 02:36:17 2018 -0700
@@ -52,7 +52,7 @@
     private static final int DONE           = 4;
 
     private final IntegerWriter intWriter = new IntegerWriter();
-    private final Huffman.Writer huffmanWriter = new Huffman.Writer();
+    private final Huffman.Writer huffmanWriter = new QuickHuffman.Writer();
     private final ISO_8859_1.Writer plainWriter = new ISO_8859_1.Writer();
 
     private int state = NEW;
@@ -76,7 +76,7 @@
             intWriter.configure(end - start, 7, 0b0000_0000);
         } else {
             huffmanWriter.from(input, start, end);
-            intWriter.configure(Huffman.INSTANCE.lengthOf(input, start, end),
+            intWriter.configure(huffmanWriter.lengthOf(input, start, end),
                     7, 0b1000_0000);
         }
 
--- a/test/jdk/java/net/httpclient/BodyProcessorInputStreamTest.java	Wed May 02 10:47:16 2018 +0200
+++ b/test/jdk/java/net/httpclient/BodyProcessorInputStreamTest.java	Wed May 02 02:36:17 2018 -0700
@@ -40,8 +40,8 @@
 /*
  * @test
  * @bug 8187503
- * @summary An example on how to read a response body with InputStream...
- * @run main/othervm -Dtest.debug=true BodyProcessorInputStreamTest
+ * @summary An example on how to read a response body with InputStream.
+ * @run main/manual -Dtest.debug=true BodyProcessorInputStreamTest
  * @author daniel fuchs
  */
 public class BodyProcessorInputStreamTest {
--- a/test/jdk/java/net/httpclient/DependentPromiseActionsTest.java	Wed May 02 10:47:16 2018 +0200
+++ b/test/jdk/java/net/httpclient/DependentPromiseActionsTest.java	Wed May 02 02:36:17 2018 -0700
@@ -177,6 +177,8 @@
         };
     }
 
+    enum SubscriberType {EAGER, LAZZY}
+
     static final class SemaphoreStallerSupplier
             implements Supplier<SemaphoreStaller> {
         @Override
@@ -291,7 +293,8 @@
         String test = format("testAsStringAsync(%s, %b, %s)",
                 uri, sameClient, stallers);
         testDependent(test, uri, sameClient, BodyHandlers::ofString,
-                this::finish, this::extractString, stallers);
+                this::finish, this::extractString, stallers,
+                SubscriberType.EAGER);
     }
 
     @Test(dataProvider = "variants")
@@ -303,7 +306,8 @@
         String test = format("testAsLinesAsync(%s, %b, %s)",
                 uri, sameClient, stallers);
         testDependent(test, uri, sameClient, BodyHandlers::ofLines,
-                this::finish, this::extractStream, stallers);
+                this::finish, this::extractStream, stallers,
+                SubscriberType.LAZZY);
     }
 
     @Test(dataProvider = "variants")
@@ -315,19 +319,22 @@
         String test = format("testAsInputStreamAsync(%s, %b, %s)",
                 uri, sameClient, stallers);
         testDependent(test, uri, sameClient, BodyHandlers::ofInputStream,
-                this::finish, this::extractInputStream, stallers);
+                this::finish, this::extractInputStream, stallers,
+                SubscriberType.LAZZY);
     }
 
     private <T,U> void testDependent(String name, String uri, boolean sameClient,
                                      Supplier<BodyHandler<T>> handlers,
                                      Finisher finisher,
                                      Extractor<T> extractor,
-                                     Supplier<Staller> stallers)
+                                     Supplier<Staller> stallers,
+                                     SubscriberType subscriberType)
             throws Exception
     {
         out.printf("%n%s%s%n", now(), name);
         try {
-            testDependent(uri, sameClient, handlers, finisher, extractor, stallers);
+            testDependent(uri, sameClient, handlers, finisher,
+                          extractor, stallers, subscriberType);
         } catch (Error | Exception x) {
             FAILURES.putIfAbsent(name, x);
             throw x;
@@ -338,7 +345,8 @@
                                      Supplier<BodyHandler<T>> handlers,
                                      Finisher finisher,
                                      Extractor<T> extractor,
-                                     Supplier<Staller> stallers)
+                                     Supplier<Staller> stallers,
+                                     SubscriberType subscriberType)
             throws Exception
     {
         HttpClient client = null;
@@ -355,7 +363,7 @@
             System.out.println("try stalling in " + where);
             CompletableFuture<HttpResponse<T>> responseCF =
                     client.sendAsync(req, handler, promiseHandler);
-            assert !responseCF.isDone();
+            assert subscriberType == SubscriberType.LAZZY || !responseCF.isDone();
             finisher.finish(where, responseCF, promiseHandler, extractor);
         }
     }
--- a/test/jdk/java/net/httpclient/EscapedOctetsInURI.java	Wed May 02 10:47:16 2018 +0200
+++ b/test/jdk/java/net/httpclient/EscapedOctetsInURI.java	Wed May 02 02:36:17 2018 -0700
@@ -116,6 +116,8 @@
 
     @Test(dataProvider = "variants")
     void test(String uriString, boolean sameClient) throws Exception {
+        System.out.println("\n--- Starting ");
+
         // The single-argument factory requires any illegal characters in its
         // argument to be quoted and preserves any escaped octets and other
         // characters that are present.
@@ -146,6 +148,7 @@
 
     @Test(dataProvider = "variants")
     void testAsync(String uriString, boolean sameClient) {
+        System.out.println("\n--- Starting ");
         URI uri = URI.create(uriString);
 
         HttpClient client = null;
--- a/test/jdk/java/net/httpclient/HttpInputStreamTest.java	Wed May 02 10:47:16 2018 +0200
+++ b/test/jdk/java/net/httpclient/HttpInputStreamTest.java	Wed May 02 02:36:17 2018 -0700
@@ -46,8 +46,8 @@
 
 /*
  * @test
- * @summary An example on how to read a response body with InputStream...
- * @run main/othervm -Dtest.debug=true HttpInputStreamTest
+ * @summary An example on how to read a response body with InputStream.
+ * @run main/manual -Dtest.debug=true HttpInputStreamTest
  * @author daniel fuchs
  */
 public class HttpInputStreamTest {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/NonAsciiCharsInURI.java	Wed May 02 02:36:17 2018 -0700
@@ -0,0 +1,252 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @summary Verify that non-US-ASCII chars are replaced with a sequence of
+ *          escaped octets that represent that char in the UTF-8 character set.
+ * @bug 8201238
+ * @modules java.base/sun.net.www.http
+ *          java.net.http/jdk.internal.net.http.common
+ *          java.net.http/jdk.internal.net.http.frame
+ *          java.net.http/jdk.internal.net.http.hpack
+ *          java.logging
+ *          jdk.httpserver
+ * @library /lib/testlibrary http2/server
+ * @build Http2TestServer
+ * @build jdk.testlibrary.SimpleSSLContext
+ * @compile -encoding utf-8 NonAsciiCharsInURI.java
+ * @run testng/othervm
+ *       -Djdk.httpclient.HttpClient.log=reqeusts,headers
+ *       NonAsciiCharsInURI
+ */
+
+import com.sun.net.httpserver.HttpServer;
+import com.sun.net.httpserver.HttpsConfigurator;
+import com.sun.net.httpserver.HttpsServer;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.URI;
+import javax.net.ssl.SSLContext;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.net.http.HttpResponse.BodyHandlers;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import jdk.testlibrary.SimpleSSLContext;
+import org.testng.annotations.AfterTest;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+import static java.lang.System.err;
+import static java.lang.System.out;
+import static java.nio.charset.StandardCharsets.US_ASCII;
+import static java.net.http.HttpClient.Builder.NO_PROXY;
+import static org.testng.Assert.assertEquals;
+
+public class NonAsciiCharsInURI implements HttpServerAdapters {
+
+    SSLContext sslContext;
+    HttpTestServer httpTestServer;         // HTTP/1.1    [ 4 servers ]
+    HttpTestServer httpsTestServer;        // HTTPS/1.1
+    HttpTestServer http2TestServer;        // HTTP/2 ( h2c )
+    HttpTestServer https2TestServer;       // HTTP/2 ( h2  )
+    String httpURI;
+    String httpsURI;
+    String http2URI;
+    String https2URI;
+
+    // € = '\u20AC' => 0xE20x820xAC
+    static final String[][] pathsAndQueryStrings = new String[][] {
+               // partial-path
+            {  "/001/plain"                                                            },
+            {  "/002/plain?plainQuery"                                                 },
+            {  "/003/withEuroSymbol/€"                                                 },
+            {  "/004/withEuroSymbol/€?euroSymbol=€"                                    },
+            {  "/005/wiki/エリザベス1世_(イングランド女王)"                                },
+            {  "/006/x?url=https://ja.wikipedia.org/wiki/エリザベス1世_(イングランド女王)" },
+    };
+
+    @DataProvider(name = "variants")
+    public Object[][] variants() {
+        List<Object[]> list = new ArrayList<>();
+
+        for (boolean sameClient : new boolean[] { false, true }) {
+            Arrays.asList(pathsAndQueryStrings).stream()
+                    .map(e -> new Object[] {httpURI + e[0], sameClient})
+                    .forEach(list::add);
+            Arrays.asList(pathsAndQueryStrings).stream()
+                    .map(e -> new Object[] {httpsURI + e[0], sameClient})
+                    .forEach(list::add);
+            Arrays.asList(pathsAndQueryStrings).stream()
+                    .map(e -> new Object[] {http2URI + e[0], sameClient})
+                    .forEach(list::add);
+            Arrays.asList(pathsAndQueryStrings).stream()
+                    .map(e -> new Object[] {https2URI + e[0], sameClient})
+                    .forEach(list::add);
+        }
+        return list.stream().toArray(Object[][]::new);
+    }
+
+    static final int ITERATION_COUNT = 3; // checks upgrade and re-use
+
+    @Test(dataProvider = "variants")
+    void test(String uriString, boolean sameClient) throws Exception {
+        out.println("\n--- Starting ");
+        // The single-argument factory requires any illegal characters in its
+        // argument to be quoted and preserves any escaped octets and other
+        // characters that are present.
+        URI uri = URI.create(uriString);
+
+        HttpClient client = null;
+        for (int i=0; i< ITERATION_COUNT; i++) {
+            if (!sameClient || client == null)
+                client = HttpClient.newBuilder()
+                        .proxy(NO_PROXY)
+                        .sslContext(sslContext)
+                        .build();
+
+            HttpRequest request = HttpRequest.newBuilder(uri).build();
+            HttpResponse<String> resp = client.send(request, BodyHandlers.ofString());
+
+            out.println("Got response: " + resp);
+            out.println("Got body: " + resp.body());
+            assertEquals(resp.statusCode(), 200,
+                    "Expected 200, got:" + resp.statusCode());
+
+            // the response body should contain the toASCIIString
+            // representation of the URI
+            String expectedURIString = uri.toASCIIString();
+            if (!expectedURIString.contains(resp.body())) {
+                err.println("Test failed: " + resp);
+                throw new AssertionError(expectedURIString +
+                                         " does not contain '" + resp.body() + "'");
+            } else {
+                out.println("Found expected " + resp.body() + " in " + expectedURIString);
+            }
+        }
+    }
+
+    @Test(dataProvider = "variants")
+    void testAsync(String uriString, boolean sameClient) {
+        out.println("\n--- Starting ");
+        URI uri = URI.create(uriString);
+
+        HttpClient client = null;
+        for (int i=0; i< ITERATION_COUNT; i++) {
+            if (!sameClient || client == null)
+                client = HttpClient.newBuilder()
+                        .proxy(NO_PROXY)
+                        .sslContext(sslContext)
+                        .build();
+
+            HttpRequest request = HttpRequest.newBuilder(uri).build();
+
+            client.sendAsync(request, BodyHandlers.ofString())
+                    .thenApply(response -> {
+                        out.println("Got response: " + response);
+                        out.println("Got body: " + response.body());
+                        assertEquals(response.statusCode(), 200);
+                        return response.body(); })
+                    .thenAccept(body -> {
+                        // the response body should contain the toASCIIString
+                        // representation of the URI
+                        String expectedURIString = uri.toASCIIString();
+                        if (!expectedURIString.contains(body)) {
+                            err.println("Test failed: " + body);
+                            throw new AssertionError(expectedURIString +
+                                    " does not contain '" + body + "'");
+                        } else {
+                            out.println("Found expected " + body + " in "
+                                        + expectedURIString);
+                        } })
+                    .join();
+        }
+    }
+
+    static String serverAuthority(HttpTestServer server) {
+        return InetAddress.getLoopbackAddress().getHostName() + ":"
+                + server.getAddress().getPort();
+    }
+
+    @BeforeTest
+    public void setup() throws Exception {
+        sslContext = new SimpleSSLContext().get();
+        if (sslContext == null)
+            throw new AssertionError("Unexpected null sslContext");
+
+        HttpTestHandler handler = new HttpUriStringHandler();
+        InetSocketAddress sa = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0);
+        httpTestServer = HttpTestServer.of(HttpServer.create(sa, 0));
+        httpTestServer.addHandler(handler, "/http1");
+        httpURI = "http://" + serverAuthority(httpTestServer) + "/http1";
+
+        HttpsServer httpsServer = HttpsServer.create(sa, 0);
+        httpsServer.setHttpsConfigurator(new HttpsConfigurator(sslContext));
+        httpsTestServer = HttpTestServer.of(httpsServer);
+        httpsTestServer.addHandler(handler, "/https1");
+        httpsURI = "https://" + serverAuthority(httpsTestServer) + "/https1";
+
+        http2TestServer = HttpTestServer.of(new Http2TestServer("localhost", false, 0));
+        http2TestServer.addHandler(handler, "/http2");
+        http2URI = "http://" + http2TestServer.serverAuthority() + "/http2";
+
+        https2TestServer = HttpTestServer.of(new Http2TestServer("localhost", true, 0));
+        https2TestServer.addHandler(handler, "/https2");
+        https2URI = "https://" + https2TestServer.serverAuthority() + "/https2";
+
+        httpTestServer.start();
+        httpsTestServer.start();
+        http2TestServer.start();
+        https2TestServer.start();
+    }
+
+    @AfterTest
+    public void teardown() throws Exception {
+        httpTestServer.stop();
+        httpsTestServer.stop();
+        http2TestServer.stop();
+        https2TestServer.stop();
+    }
+
+    /** A handler that returns, as its body, the exact received request URI. */
+    static class HttpUriStringHandler implements HttpTestHandler {
+        @Override
+        public void handle(HttpTestExchange t) throws IOException {
+            String uri = t.getRequestURI().toString();
+            out.println("Http1UriStringHandler received, uri: " + uri);
+            try (InputStream is = t.getRequestBody();
+                 OutputStream os = t.getResponseBody()) {
+                is.readAllBytes();
+                byte[] bytes = uri.getBytes(US_ASCII);
+                t.sendResponseHeaders(200, bytes.length);
+                os.write(bytes);
+            }
+        }
+    }
+}
--- a/test/jdk/java/net/httpclient/ProxyServer.java	Wed May 02 10:47:16 2018 +0200
+++ b/test/jdk/java/net/httpclient/ProxyServer.java	Wed May 02 02:36:17 2018 -0700
@@ -73,12 +73,10 @@
      */
     public void close() throws IOException {
         if (debug) System.out.println("Proxy: closing");
-            done = true;
+        done = true;
         listener.close();
         for (Connection c : connections) {
-            if (c.running()) {
-                c.close();
-            }
+            c.close();
         }
     }
 
@@ -179,7 +177,9 @@
             return out.isAlive() || in.isAlive();
         }
 
-        public void close() throws IOException {
+        private volatile boolean closing;
+        public synchronized void close() throws IOException {
+            closing = true;
             if (debug) System.out.println("Closing connection (proxy)");
             if (serverSocket != null) serverSocket.close();
             if (clientSocket != null) clientSocket.close();
@@ -238,7 +238,13 @@
                 i++;
 
                 commonInit(dest, 80);
-                serverOut.write(buf, i, buf.length-i);
+                OutputStream sout;
+                synchronized (this) {
+                    if (closing) return;
+                    sout = serverOut;
+                }
+                // might fail if we're closing but we don't care.
+                sout.write(buf, i, buf.length-i);
                 proxyCommon();
 
             } catch (URISyntaxException e) {
@@ -246,7 +252,8 @@
             }
         }
 
-        void commonInit(String dest, int defaultPort) throws IOException {
+        synchronized void commonInit(String dest, int defaultPort) throws IOException {
+            if (closing) return;
             int port;
             String[] hostport = dest.split(":");
             if (hostport.length == 1) {
@@ -261,7 +268,8 @@
             serverIn = new BufferedInputStream(serverSocket.getInputStream());
         }
 
-        void proxyCommon() throws IOException {
+        synchronized void proxyCommon() throws IOException {
+            if (closing) return;
             out = new Thread(() -> {
                 try {
                     byte[] bb = new byte[8000];
@@ -269,6 +277,7 @@
                     while ((n = clientIn.read(bb)) != -1) {
                         serverOut.write(bb, 0, n);
                     }
+                    closing = true;
                     serverSocket.close();
                     clientSocket.close();
                 } catch (IOException e) {
@@ -284,6 +293,7 @@
                     while ((n = serverIn.read(bb)) != -1) {
                         clientOut.write(bb, 0, n);
                     }
+                    closing = true;
                     serverSocket.close();
                     clientSocket.close();
                 } catch (IOException e) {
@@ -302,7 +312,9 @@
         }
 
         void doTunnel(String dest) throws IOException {
+            if (closing) return; // no need to go further.
             commonInit(dest, 443);
+            // might fail if we're closing, but we don't care.
             clientOut.write("HTTP/1.1 200 OK\r\n\r\n".getBytes());
             proxyCommon();
         }
--- a/test/jdk/java/net/httpclient/RetryWithCookie.java	Wed May 02 10:47:16 2018 +0200
+++ b/test/jdk/java/net/httpclient/RetryWithCookie.java	Wed May 02 02:36:17 2018 -0700
@@ -69,6 +69,7 @@
 import java.util.concurrent.atomic.AtomicLong;
 
 import static java.lang.System.out;
+import static java.net.http.HttpClient.Builder.NO_PROXY;
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertTrue;
@@ -106,6 +107,7 @@
         out.printf("%n---- starting (%s) ----%n", uriString);
         CookieManager cookieManager = new CookieManager();
         HttpClient client = HttpClient.newBuilder()
+                .proxy(NO_PROXY)
                 .followRedirects(Redirect.ALWAYS)
                 .cookieHandler(cookieManager)
                 .sslContext(sslContext)
--- a/test/jdk/java/net/httpclient/SmallTimeout.java	Wed May 02 10:47:16 2018 +0200
+++ b/test/jdk/java/net/httpclient/SmallTimeout.java	Wed May 02 02:36:17 2018 -0700
@@ -85,12 +85,14 @@
             ss.setReuseAddress(false);
             ss.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0));
             int port = ss.getLocalPort();
-            URI uri = new URI("http://localhost:" + port + "/");
+            URI u = new URI("http://localhost:" + port + "/");
 
             HttpRequest[] requests = new HttpRequest[TIMEOUTS.length];
 
             out.println("--- TESTING Async");
             for (int i = 0; i < TIMEOUTS.length; i++) {
+                final int n = i;
+                URI uri = new URI(u.toString() + "/r" + n);
                 requests[i] = HttpRequest.newBuilder(uri)
                                          .timeout(Duration.ofMillis(TIMEOUTS[i]))
                                          .GET()
@@ -102,24 +104,25 @@
                     .whenComplete((HttpResponse<Object> r, Throwable t) -> {
                         Throwable cause = null;
                         if (r != null) {
-                            out.println("Unexpected response: " + r);
-                            cause = new RuntimeException("Unexpected response");
+                            out.println("Unexpected response for r" + n + ": " + r);
+                            cause = new RuntimeException("Unexpected response for r" + n);
                             error = true;
                         }
                         if (t != null) {
                             if (!(t.getCause() instanceof HttpTimeoutException)) {
-                                out.println("Wrong exception type:" + t.toString());
+                                out.println("Wrong exception type for r" + n + ":" + t.toString());
                                 Throwable c = t.getCause() == null ? t : t.getCause();
                                 c.printStackTrace();
                                 cause = c;
                                 error = true;
                             } else {
-                                out.println("Caught expected timeout: " + t.getCause());
+                                out.println("Caught expected timeout for r" + n +": " + t.getCause());
                             }
                         }
                         if (t == null && r == null) {
-                            out.println("Both response and throwable are null!");
-                            cause = new RuntimeException("Both response and throwable are null!");
+                            out.println("Both response and throwable are null for r" + n + "!");
+                            cause = new RuntimeException("Both response and throwable are null for r"
+                                    + n + "!");
                             error = true;
                         }
                         queue.add(HttpResult.of(req,cause));
@@ -134,11 +137,14 @@
 
             // Repeat blocking in separate threads. Use queue to wait.
             out.println("--- TESTING Sync");
+            System.err.println("================= TESTING Sync =====================");
 
             // For running blocking response tasks
             ExecutorService executor = Executors.newCachedThreadPool();
 
             for (int i = 0; i < TIMEOUTS.length; i++) {
+                final int n = i;
+                URI uri = new URI(u.toString()+"/sync/r" + n);
                 requests[i] = HttpRequest.newBuilder(uri)
                                          .timeout(Duration.ofMillis(TIMEOUTS[i]))
                                          .GET()
@@ -148,11 +154,13 @@
                 executor.execute(() -> {
                     Throwable cause = null;
                     try {
-                        client.send(req, BodyHandlers.replacing(null));
+                        HttpResponse<?> r = client.send(req, BodyHandlers.replacing(null));
+                        out.println("Unexpected success for r" + n +": " + r);
                     } catch (HttpTimeoutException e) {
-                        out.println("Caught expected timeout: " + e);
+                        out.println("Caught expected timeout for r" + n +": " + e);
                     } catch (Throwable ee) {
                         Throwable c = ee.getCause() == null ? ee : ee.getCause();
+                        out.println("Unexpected exception for r" + n + ": " + c);
                         c.printStackTrace();
                         cause = c;
                         error = true;
--- a/test/jdk/java/net/httpclient/TimeoutOrdering.java	Wed May 02 10:47:16 2018 +0200
+++ b/test/jdk/java/net/httpclient/TimeoutOrdering.java	Wed May 02 02:36:17 2018 -0700
@@ -77,21 +77,22 @@
                                          .build();
 
                 final HttpRequest req = requests[i];
+                final int j = i;
                 CompletableFuture<HttpResponse<Object>> response = client
                     .sendAsync(req, BodyHandlers.replacing(null))
                     .whenComplete((HttpResponse<Object> r, Throwable t) -> {
                         if (r != null) {
-                            out.println("Unexpected response: " + r);
+                            out.println("Unexpected response for r" + j + ": " + r);
                             error = true;
                         }
                         if (t != null) {
                             if (!(t.getCause() instanceof HttpTimeoutException)) {
-                                out.println("Wrong exception type:" + t.toString());
+                                out.println("Wrong exception type for r" + j + ": " + t.toString());
                                 Throwable c = t.getCause() == null ? t : t.getCause();
                                 c.printStackTrace();
                                 error = true;
                             } else {
-                                out.println("Caught expected timeout: " + t.getCause());
+                                out.println("Caught expected timeout for r" + j + ": " + t.getCause());
                             }
                         }
                         queue.add(req);
@@ -117,16 +118,21 @@
                                          .build();
 
                 final HttpRequest req = requests[i];
+                final int j = i;
                 executor.execute(() -> {
                     try {
-                        client.send(req, BodyHandlers.replacing(null));
+                        HttpResponse<?> r = client.send(req, BodyHandlers.replacing(null));
+                        out.println("Unexpected response for r" + j + ": " + r);
+                        error = true;
                     } catch (HttpTimeoutException e) {
-                        out.println("Caught expected timeout: " + e);
-                        queue.offer(req);
+                        out.println("Caught expected timeout for r" + j +": " + e);
                     } catch (IOException | InterruptedException ee) {
                         Throwable c = ee.getCause() == null ? ee : ee.getCause();
+                        out.println("Wrong exception type for r" + j + ": " + c.toString());
                         c.printStackTrace();
                         error = true;
+                    } finally {
+                        queue.offer(req);
                     }
                 });
             }
--- a/test/jdk/java/net/httpclient/http2/java.net.http/jdk/internal/net/http/hpack/CircularBufferTest.java	Wed May 02 10:47:16 2018 +0200
+++ b/test/jdk/java/net/httpclient/http2/java.net.http/jdk/internal/net/http/hpack/CircularBufferTest.java	Wed May 02 02:36:17 2018 -0700
@@ -25,13 +25,16 @@
 import org.testng.annotations.Test;
 import jdk.internal.net.http.hpack.SimpleHeaderTable.CircularBuffer;
 
+import java.util.Arrays;
 import java.util.Queue;
 import java.util.Random;
 import java.util.concurrent.ArrayBlockingQueue;
 
+import static jdk.internal.net.http.common.Utils.pow2Size;
 import static org.testng.Assert.assertEquals;
 import static jdk.internal.net.http.hpack.TestHelper.assertVoidThrows;
 import static jdk.internal.net.http.hpack.TestHelper.newRandom;
+import static org.testng.Assert.assertTrue;
 
 public final class CircularBufferTest {
 
@@ -80,6 +83,8 @@
 
     private void resizeOnce(int capacity) {
 
+        capacity = pow2Size(capacity);
+
         int nextNumberToPut = 0;
 
         Queue<Integer> referenceQueue = new ArrayBlockingQueue<>(capacity);
@@ -104,11 +109,15 @@
         Integer[] expected = referenceQueue.toArray(new Integer[0]);
         buffer.resize(expected.length);
 
-        assertEquals(buffer.elements, expected);
+        boolean equals = Arrays.equals(buffer.elements, 0, buffer.size,
+                                       expected, 0, expected.length);
+        assertTrue(equals);
     }
 
     private void queueOnce(int capacity, int numWraps) {
 
+        capacity = pow2Size(capacity);
+
         Queue<Integer> referenceQueue = new ArrayBlockingQueue<>(capacity);
         CircularBuffer<Integer> buffer = new CircularBuffer<>(capacity);
 
--- a/test/jdk/java/net/httpclient/http2/java.net.http/jdk/internal/net/http/hpack/DecoderTest.java	Wed May 02 10:47:16 2018 +0200
+++ b/test/jdk/java/net/httpclient/http2/java.net.http/jdk/internal/net/http/hpack/DecoderTest.java	Wed May 02 02:36:17 2018 -0700
@@ -623,8 +623,10 @@
         testAllSplits(() -> new Decoder(256), hexdump, expectedHeaderTable, expectedHeaderList);
     }
 
-    private static void testAllSplits(Supplier<Decoder> supplier, String hexdump,
-                                      String expectedHeaderTable, String expectedHeaderList) {
+    private static void testAllSplits(Supplier<Decoder> supplier,
+                                      String hexdump,
+                                      String expectedHeaderTable,
+                                      String expectedHeaderList) {
         ByteBuffer source = SpecHelper.toBytes(hexdump);
 
         BuffersTestingKit.forEachSplit(source, iterable -> {
@@ -651,6 +653,46 @@
             assertEquals(d.getTable().getStateString(), expectedHeaderTable);
             assertEquals(actual.stream().collect(Collectors.joining("\n")), expectedHeaderList);
         });
+
+        // Now introduce last ByteBuffer which is empty and EOF (mimics idiom
+        // I've found in HttpClient code)
+        BuffersTestingKit.forEachSplit(source, iterable -> {
+            List<String> actual = new LinkedList<>();
+            Iterator<? extends ByteBuffer> i = iterable.iterator();
+            if (!i.hasNext()) {
+                return;
+            }
+            Decoder d = supplier.get();
+            do {
+                ByteBuffer n = i.next();
+                try {
+                    d.decode(n, false, (name, value) -> {
+                        if (value == null) {
+                            actual.add(name.toString());
+                        } else {
+                            actual.add(name + ": " + value);
+                        }
+                    });
+                } catch (IOException e) {
+                    throw new UncheckedIOException(e);
+                }
+            } while (i.hasNext());
+
+            try {
+                d.decode(ByteBuffer.allocate(0), false, (name, value) -> {
+                    if (value == null) {
+                        actual.add(name.toString());
+                    } else {
+                        actual.add(name + ": " + value);
+                    }
+                });
+            } catch (IOException e) {
+                throw new UncheckedIOException(e);
+            }
+
+            assertEquals(d.getTable().getStateString(), expectedHeaderTable);
+            assertEquals(actual.stream().collect(Collectors.joining("\n")), expectedHeaderList);
+        });
     }
 
     //
--- a/test/jdk/java/net/httpclient/http2/java.net.http/jdk/internal/net/http/hpack/HuffmanTest.java	Wed May 02 10:47:16 2018 +0200
+++ b/test/jdk/java/net/httpclient/http2/java.net.http/jdk/internal/net/http/hpack/HuffmanTest.java	Wed May 02 02:36:17 2018 -0700
@@ -328,7 +328,7 @@
                     parseInt(hex, 16), parseInt(len));
 
             StringBuilder actual = new StringBuilder();
-            Huffman.Reader t = new Huffman.Reader();
+            NaiveHuffman.Reader t = new NaiveHuffman.Reader();
             t.read(ByteBuffer.wrap(bytes), actual, false, true);
 
             // What has been read MUST represent a single symbol
@@ -338,6 +338,8 @@
             // characters (as some of them might not be visible)
             assertEquals(actual.charAt(0), expected);
             i++;
+
+            // maybe not report EOS but rather throw an expected exception?
         }
         assertEquals(i, 257); // 256 + EOS
     }
@@ -503,12 +505,18 @@
     }
 
     @Test
+    public void read_13() {
+        read("6274 a6b4 0989 4de4 b27f 80",
+             "/https2/fixed?0");
+    }
+
+    @Test
     public void test_trie_has_no_empty_nodes() {
-        Huffman.Node root = Huffman.INSTANCE.getRoot();
-        Stack<Huffman.Node> backlog = new Stack<>();
+        NaiveHuffman.Node root = NaiveHuffman.INSTANCE.getRoot();
+        Stack<NaiveHuffman.Node> backlog = new Stack<>();
         backlog.push(root);
         while (!backlog.isEmpty()) {
-            Huffman.Node n = backlog.pop();
+            NaiveHuffman.Node n = backlog.pop();
             // The only type of nodes we couldn't possibly catch during
             // construction is an empty node: no children and no char
             if (n.left != null) {
@@ -525,11 +533,11 @@
     @Test
     public void test_trie_has_257_nodes() {
         int count = 0;
-        Huffman.Node root = Huffman.INSTANCE.getRoot();
-        Stack<Huffman.Node> backlog = new Stack<>();
+        NaiveHuffman.Node root = NaiveHuffman.INSTANCE.getRoot();
+        Stack<NaiveHuffman.Node> backlog = new Stack<>();
         backlog.push(root);
         while (!backlog.isEmpty()) {
-            Huffman.Node n = backlog.pop();
+            NaiveHuffman.Node n = backlog.pop();
             if (n.left != null) {
                 backlog.push(n.left);
             }
@@ -546,7 +554,7 @@
     @Test
     public void cant_encode_outside_byte() {
         TestHelper.Block<Object> coding =
-                () -> new Huffman.Writer()
+                () -> new NaiveHuffman.Writer()
                         .from(((char) 256) + "", 0, 1)
                         .write(ByteBuffer.allocate(1));
         RuntimeException e =
@@ -558,7 +566,7 @@
         ByteBuffer source = SpecHelper.toBytes(hexdump);
         Appendable actual = new StringBuilder();
         try {
-            new Huffman.Reader().read(source, actual, true);
+            new QuickHuffman.Reader().read(source, actual, true);
         } catch (IOException e) {
             throw new UncheckedIOException(e);
         }
@@ -566,9 +574,9 @@
     }
 
     private static void write(String decoded, String hexdump) {
-        int n = Huffman.INSTANCE.lengthOf(decoded);
+        Huffman.Writer writer = new QuickHuffman.Writer();
+        int n = writer.lengthOf(decoded);
         ByteBuffer destination = ByteBuffer.allocate(n); // Extra margin (1) to test having more bytes in the destination than needed is ok
-        Huffman.Writer writer = new Huffman.Writer();
         BuffersTestingKit.forEachSplit(destination, byteBuffers -> {
             writer.from(decoded, 0, decoded.length());
             boolean written = false;
--- a/test/jdk/java/net/httpclient/http2/server/Http2TestServer.java	Wed May 02 10:47:16 2018 +0200
+++ b/test/jdk/java/net/httpclient/http2/server/Http2TestServer.java	Wed May 02 02:36:17 2018 -0700
@@ -67,11 +67,11 @@
     }
 
     public Http2TestServer(String serverName, boolean secure, int port) throws Exception {
-        this(serverName, secure, port, getDefaultExecutor(), null);
+        this(serverName, secure, port, getDefaultExecutor(), 50, null);
     }
 
     public Http2TestServer(boolean secure, int port) throws Exception {
-        this(null, secure, port, getDefaultExecutor(), null);
+        this(null, secure, port, getDefaultExecutor(), 50, null);
     }
 
     public InetSocketAddress getAddress() {
@@ -85,19 +85,29 @@
 
     public Http2TestServer(boolean secure,
                            SSLContext context) throws Exception {
-        this(null, secure, 0, null, context);
+        this(null, secure, 0, null, 50, context);
     }
 
     public Http2TestServer(String serverName, boolean secure,
                            SSLContext context) throws Exception {
-        this(serverName, secure, 0, null, context);
+        this(serverName, secure, 0, null, 50, context);
     }
 
     public Http2TestServer(boolean secure,
                            int port,
                            ExecutorService exec,
                            SSLContext context) throws Exception {
-        this(null, secure, port, exec, context);
+        this(null, secure, port, exec, 50, context);
+    }
+
+    public Http2TestServer(String serverName,
+                           boolean secure,
+                           int port,
+                           ExecutorService exec,
+                           SSLContext context)
+        throws Exception
+    {
+        this(serverName, secure, port, exec, 50, context);
     }
 
     /**
@@ -109,20 +119,22 @@
      * @param secure https or http
      * @param port listen port
      * @param exec executor service (cached thread pool is used if null)
+     * @param backlog the server socket backlog
      * @param context the SSLContext used when secure is true
      */
     public Http2TestServer(String serverName,
                            boolean secure,
                            int port,
                            ExecutorService exec,
+                           int backlog,
                            SSLContext context)
         throws Exception
     {
         this.serverName = serverName;
         if (secure) {
-            server = initSecure(port);
+            server = initSecure(port, backlog);
         } else {
-            server = initPlaintext(port);
+            server = initPlaintext(port, backlog);
         }
         this.secure = secure;
         this.exec = exec == null ? getDefaultExecutor() : exec;
@@ -171,10 +183,10 @@
         return handler;
     }
 
-    final ServerSocket initPlaintext(int port) throws Exception {
+    final ServerSocket initPlaintext(int port, int backlog) throws Exception {
         ServerSocket ss = new ServerSocket();
         ss.setReuseAddress(false);
-        ss.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0));
+        ss.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), backlog);
         return ss;
     }
 
@@ -192,7 +204,7 @@
     }
 
 
-    final ServerSocket initSecure(int port) throws Exception {
+    final ServerSocket initSecure(int port, int backlog) throws Exception {
         ServerSocketFactory fac;
         if (sslContext != null) {
             fac = sslContext.getServerSocketFactory();
@@ -201,7 +213,7 @@
         }
         SSLServerSocket se = (SSLServerSocket) fac.createServerSocket();
         se.setReuseAddress(false);
-        se.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0));
+        se.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), backlog);
         SSLParameters sslp = se.getSSLParameters();
         sslp.setApplicationProtocols(new String[]{"h2"});
         sslp.setEndpointIdentificationAlgorithm("HTTPS");
--- a/test/jdk/java/net/httpclient/websocket/DummyWebSocketServer.java	Wed May 02 10:47:16 2018 +0200
+++ b/test/jdk/java/net/httpclient/websocket/DummyWebSocketServer.java	Wed May 02 02:36:17 2018 -0700
@@ -200,6 +200,7 @@
             thread.start();
         } catch (IOException e) {
             close(ssc);
+            throw e;
         }
         err.println("Started at: " + getURI());
     }
--- a/test/jdk/java/net/httpclient/websocket/PendingBinaryPingClose.java	Wed May 02 10:47:16 2018 +0200
+++ b/test/jdk/java/net/httpclient/websocket/PendingBinaryPingClose.java	Wed May 02 02:36:17 2018 -0700
@@ -37,8 +37,8 @@
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
-
-import static java.net.http.HttpClient.newHttpClient;
+import static java.net.http.HttpClient.Builder.NO_PROXY;
+import static java.net.http.HttpClient.newBuilder;
 
 public class PendingBinaryPingClose extends PendingOperations {
 
@@ -51,7 +51,7 @@
         repeatable(() -> {
             server = Support.notReadingServer();
             server.open();
-            webSocket = newHttpClient().newWebSocketBuilder()
+            webSocket = newBuilder().proxy(NO_PROXY).build().newWebSocketBuilder()
                     .buildAsync(server.getURI(), new WebSocket.Listener() { })
                     .join();
             ByteBuffer data = ByteBuffer.allocate(65536);
--- a/test/jdk/java/net/httpclient/websocket/PendingBinaryPongClose.java	Wed May 02 10:47:16 2018 +0200
+++ b/test/jdk/java/net/httpclient/websocket/PendingBinaryPongClose.java	Wed May 02 02:36:17 2018 -0700
@@ -37,8 +37,8 @@
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
-
-import static java.net.http.HttpClient.newHttpClient;
+import static java.net.http.HttpClient.Builder.NO_PROXY;
+import static java.net.http.HttpClient.newBuilder;
 
 public class PendingBinaryPongClose extends PendingOperations {
 
@@ -51,7 +51,7 @@
         repeatable(() -> {
             server = Support.notReadingServer();
             server.open();
-            webSocket = newHttpClient().newWebSocketBuilder()
+            webSocket = newBuilder().proxy(NO_PROXY).build().newWebSocketBuilder()
                     .buildAsync(server.getURI(), new WebSocket.Listener() { })
                     .join();
             ByteBuffer data = ByteBuffer.allocate(65536);
--- a/test/jdk/java/net/httpclient/websocket/PendingPingBinaryClose.java	Wed May 02 10:47:16 2018 +0200
+++ b/test/jdk/java/net/httpclient/websocket/PendingPingBinaryClose.java	Wed May 02 02:36:17 2018 -0700
@@ -39,8 +39,8 @@
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
-
-import static java.net.http.HttpClient.newHttpClient;
+import static java.net.http.HttpClient.Builder.NO_PROXY;
+import static java.net.http.HttpClient.newBuilder;
 
 public class PendingPingBinaryClose extends PendingOperations {
 
@@ -53,7 +53,7 @@
         repeatable( () -> {
             server = Support.notReadingServer();
             server.open();
-            webSocket = newHttpClient().newWebSocketBuilder()
+            webSocket = newBuilder().proxy(NO_PROXY).build().newWebSocketBuilder()
                     .buildAsync(server.getURI(), new WebSocket.Listener() { })
                     .join();
             ByteBuffer data = ByteBuffer.allocate(125);
--- a/test/jdk/java/net/httpclient/websocket/PendingPingTextClose.java	Wed May 02 10:47:16 2018 +0200
+++ b/test/jdk/java/net/httpclient/websocket/PendingPingTextClose.java	Wed May 02 02:36:17 2018 -0700
@@ -39,8 +39,8 @@
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
-
-import static java.net.http.HttpClient.newHttpClient;
+import static java.net.http.HttpClient.Builder.NO_PROXY;
+import static java.net.http.HttpClient.newBuilder;
 
 public class PendingPingTextClose extends PendingOperations {
 
@@ -53,7 +53,7 @@
         repeatable( () -> {
             server = Support.notReadingServer();
             server.open();
-            webSocket = newHttpClient().newWebSocketBuilder()
+            webSocket = newBuilder().proxy(NO_PROXY).build().newWebSocketBuilder()
                     .buildAsync(server.getURI(), new WebSocket.Listener() { })
                     .join();
             ByteBuffer data = ByteBuffer.allocate(125);
--- a/test/jdk/java/net/httpclient/websocket/PendingPongBinaryClose.java	Wed May 02 10:47:16 2018 +0200
+++ b/test/jdk/java/net/httpclient/websocket/PendingPongBinaryClose.java	Wed May 02 02:36:17 2018 -0700
@@ -39,8 +39,8 @@
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
-
-import static java.net.http.HttpClient.newHttpClient;
+import static java.net.http.HttpClient.Builder.NO_PROXY;
+import static java.net.http.HttpClient.newBuilder;
 
 public class PendingPongBinaryClose extends PendingOperations {
 
@@ -53,7 +53,7 @@
         repeatable( () -> {
             server = Support.notReadingServer();
             server.open();
-            webSocket = newHttpClient().newWebSocketBuilder()
+            webSocket = newBuilder().proxy(NO_PROXY).build().newWebSocketBuilder()
                     .buildAsync(server.getURI(), new WebSocket.Listener() { })
                     .join();
             ByteBuffer data = ByteBuffer.allocate(125);
--- a/test/jdk/java/net/httpclient/websocket/PendingPongTextClose.java	Wed May 02 10:47:16 2018 +0200
+++ b/test/jdk/java/net/httpclient/websocket/PendingPongTextClose.java	Wed May 02 02:36:17 2018 -0700
@@ -40,6 +40,8 @@
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 
+import static java.net.http.HttpClient.Builder.NO_PROXY;
+import static java.net.http.HttpClient.newBuilder;
 import static java.net.http.HttpClient.newHttpClient;
 
 public class PendingPongTextClose extends PendingOperations {
@@ -53,7 +55,7 @@
         repeatable( () -> {
             server = Support.notReadingServer();
             server.open();
-            webSocket = newHttpClient().newWebSocketBuilder()
+            webSocket = newBuilder().proxy(NO_PROXY).build().newWebSocketBuilder()
                     .buildAsync(server.getURI(), new WebSocket.Listener() { })
                     .join();
             ByteBuffer data = ByteBuffer.allocate(125);
--- a/test/jdk/java/net/httpclient/websocket/PendingTextPingClose.java	Wed May 02 10:47:16 2018 +0200
+++ b/test/jdk/java/net/httpclient/websocket/PendingTextPingClose.java	Wed May 02 02:36:17 2018 -0700
@@ -38,8 +38,8 @@
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
-
-import static java.net.http.HttpClient.newHttpClient;
+import static java.net.http.HttpClient.Builder.NO_PROXY;
+import static java.net.http.HttpClient.newBuilder;
 
 public class PendingTextPingClose extends PendingOperations {
 
@@ -52,7 +52,7 @@
         repeatable(() -> {
             server = Support.notReadingServer();
             server.open();
-            webSocket = newHttpClient().newWebSocketBuilder()
+            webSocket = newBuilder().proxy(NO_PROXY).build().newWebSocketBuilder()
                     .buildAsync(server.getURI(), new WebSocket.Listener() { })
                     .join();
             CharBuffer data = CharBuffer.allocate(65536);
--- a/test/jdk/java/net/httpclient/websocket/PendingTextPongClose.java	Wed May 02 10:47:16 2018 +0200
+++ b/test/jdk/java/net/httpclient/websocket/PendingTextPongClose.java	Wed May 02 02:36:17 2018 -0700
@@ -38,8 +38,8 @@
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
-
-import static java.net.http.HttpClient.newHttpClient;
+import static java.net.http.HttpClient.Builder.NO_PROXY;
+import static java.net.http.HttpClient.newBuilder;
 
 public class PendingTextPongClose extends PendingOperations {
 
@@ -52,7 +52,7 @@
         repeatable(() -> {
             server = Support.notReadingServer();
             server.open();
-            webSocket = newHttpClient().newWebSocketBuilder()
+            webSocket = newBuilder().proxy(NO_PROXY).build().newWebSocketBuilder()
                     .buildAsync(server.getURI(), new WebSocket.Listener() { })
                     .join();
             CharBuffer data = CharBuffer.allocate(65536);
--- a/test/jdk/java/net/httpclient/websocket/SendTest.java	Wed May 02 10:47:16 2018 +0200
+++ b/test/jdk/java/net/httpclient/websocket/SendTest.java	Wed May 02 02:36:17 2018 -0700
@@ -35,7 +35,8 @@
 import java.io.IOException;
 import java.net.http.WebSocket;
 
-import static java.net.http.HttpClient.newHttpClient;
+import static java.net.http.HttpClient.Builder.NO_PROXY;
+import static java.net.http.HttpClient.newBuilder;
 import static java.net.http.WebSocket.NORMAL_CLOSURE;
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertThrows;
@@ -58,7 +59,7 @@
     public void sendMethodsThrowNPE() throws IOException {
         server = new DummyWebSocketServer();
         server.open();
-        webSocket = newHttpClient().newWebSocketBuilder()
+        webSocket = newBuilder().proxy(NO_PROXY).build().newWebSocketBuilder()
                 .buildAsync(server.getURI(), new WebSocket.Listener() { })
                 .join();
 
@@ -89,7 +90,7 @@
     public void sendCloseCompleted() throws IOException {
         server = new DummyWebSocketServer();
         server.open();
-        webSocket = newHttpClient().newWebSocketBuilder()
+        webSocket = newBuilder().proxy(NO_PROXY).build().newWebSocketBuilder()
                 .buildAsync(server.getURI(), new WebSocket.Listener() { })
                 .join();
         webSocket.sendClose(NORMAL_CLOSURE, "").join();
--- a/test/jdk/java/net/httpclient/websocket/WSHandshakeExceptionTest.java	Wed May 02 10:47:16 2018 +0200
+++ b/test/jdk/java/net/httpclient/websocket/WSHandshakeExceptionTest.java	Wed May 02 02:36:17 2018 -0700
@@ -51,6 +51,8 @@
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
+
+import static java.net.http.HttpClient.Builder.NO_PROXY;
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertNotNull;
 import static org.testng.Assert.fail;
@@ -79,6 +81,7 @@
 
     HttpClient newHttpClient() {
         return HttpClient.newBuilder()
+                         .proxy(NO_PROXY)
                          .executor(executor)
                          .sslContext(sslContext)
                          .build();
--- a/test/jdk/java/net/httpclient/websocket/WebSocketExtendedTest.java	Wed May 02 10:47:16 2018 +0200
+++ b/test/jdk/java/net/httpclient/websocket/WebSocketExtendedTest.java	Wed May 02 02:36:17 2018 -0700
@@ -43,8 +43,8 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Random;
-
-import static java.net.http.HttpClient.newHttpClient;
+import static java.net.http.HttpClient.Builder.NO_PROXY;
+import static java.net.http.HttpClient.newBuilder;
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertTrue;
 
@@ -71,7 +71,7 @@
     public void binary(ByteBuffer expected) throws IOException, InterruptedException {
         try (DummyWebSocketServer server = new DummyWebSocketServer()) {
             server.open();
-            WebSocket ws = newHttpClient()
+            WebSocket ws = newBuilder().proxy(NO_PROXY).build()
                     .newWebSocketBuilder()
                     .buildAsync(server.getURI(), new WebSocket.Listener() { })
                     .join();
@@ -166,7 +166,7 @@
     public void ping(ByteBuffer expected) throws Exception {
         try (DummyWebSocketServer server = new DummyWebSocketServer()) {
             server.open();
-            WebSocket ws = newHttpClient()
+            WebSocket ws = newBuilder().proxy(NO_PROXY).build()
                     .newWebSocketBuilder()
                     .buildAsync(server.getURI(), new WebSocket.Listener() { })
                     .join();
@@ -188,7 +188,7 @@
     public void pong(ByteBuffer expected) throws Exception {
         try (DummyWebSocketServer server = new DummyWebSocketServer()) {
             server.open();
-            WebSocket ws = newHttpClient()
+            WebSocket ws = newBuilder().proxy(NO_PROXY).build()
                     .newWebSocketBuilder()
                     .buildAsync(server.getURI(), new WebSocket.Listener() { })
                     .join();
@@ -210,7 +210,7 @@
     public void close(int statusCode, String reason) throws Exception {
         try (DummyWebSocketServer server = new DummyWebSocketServer()) {
             server.open();
-            WebSocket ws = newHttpClient()
+            WebSocket ws = newBuilder().proxy(NO_PROXY).build()
                     .newWebSocketBuilder()
                     .buildAsync(server.getURI(), new WebSocket.Listener() { })
                     .join();
@@ -233,7 +233,7 @@
     public void text(String expected) throws Exception {
         try (DummyWebSocketServer server = new DummyWebSocketServer()) {
             server.open();
-            WebSocket ws = newHttpClient()
+            WebSocket ws = newBuilder().proxy(NO_PROXY).build()
                     .newWebSocketBuilder()
                     .buildAsync(server.getURI(), new WebSocket.Listener() { })
                     .join();
--- a/test/jdk/java/net/httpclient/websocket/WebSocketTest.java	Wed May 02 10:47:16 2018 +0200
+++ b/test/jdk/java/net/httpclient/websocket/WebSocketTest.java	Wed May 02 02:36:17 2018 -0700
@@ -42,8 +42,8 @@
 import java.util.concurrent.CompletionStage;
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
-
-import static java.net.http.HttpClient.newHttpClient;
+import static java.net.http.HttpClient.Builder.NO_PROXY;
+import static java.net.http.HttpClient.newBuilder;
 import static java.net.http.WebSocket.NORMAL_CLOSURE;
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertThrows;
@@ -73,7 +73,7 @@
     public void illegalArgument() throws IOException {
         server = new DummyWebSocketServer();
         server.open();
-        webSocket = newHttpClient()
+        webSocket = newBuilder().proxy(NO_PROXY).build()
                 .newWebSocketBuilder()
                 .buildAsync(server.getURI(), new WebSocket.Listener() { })
                 .join();
@@ -137,7 +137,7 @@
     public void partialBinaryThenText() throws IOException {
         server = new DummyWebSocketServer();
         server.open();
-        webSocket = newHttpClient().newWebSocketBuilder()
+        webSocket = newBuilder().proxy(NO_PROXY).build().newWebSocketBuilder()
                 .buildAsync(server.getURI(), new WebSocket.Listener() { })
                 .join();
         webSocket.sendBinary(ByteBuffer.allocate(16), false).join();
@@ -152,7 +152,7 @@
     public void partialTextThenBinary() throws IOException {
         server = new DummyWebSocketServer();
         server.open();
-        webSocket = newHttpClient().newWebSocketBuilder()
+        webSocket = newBuilder().proxy(NO_PROXY).build().newWebSocketBuilder()
                 .buildAsync(server.getURI(), new WebSocket.Listener() { })
                 .join();
 
@@ -168,7 +168,7 @@
     public void sendMethodsThrowIOE1() throws IOException {
         server = new DummyWebSocketServer();
         server.open();
-        webSocket = newHttpClient()
+        webSocket = newBuilder().proxy(NO_PROXY).build()
                 .newWebSocketBuilder()
                 .buildAsync(server.getURI(), new WebSocket.Listener() { })
                 .join();
@@ -221,7 +221,7 @@
             }
         };
 
-        webSocket = newHttpClient().newWebSocketBuilder()
+        webSocket = newBuilder().proxy(NO_PROXY).build().newWebSocketBuilder()
                 .buildAsync(server.getURI(), listener)
                 .join();
 
@@ -333,7 +333,7 @@
             }
         };
 
-        webSocket = newHttpClient().newWebSocketBuilder()
+        webSocket = newBuilder().proxy(NO_PROXY).build().newWebSocketBuilder()
                 .buildAsync(server.getURI(), listener)
                 .join();
 
@@ -407,7 +407,7 @@
             }
         };
 
-        webSocket = newHttpClient().newWebSocketBuilder()
+        webSocket = newBuilder().proxy(NO_PROXY).build().newWebSocketBuilder()
                 .buildAsync(server.getURI(), listener)
                 .join();
 
@@ -500,7 +500,7 @@
             }
         };
 
-        webSocket = newHttpClient().newWebSocketBuilder()
+        webSocket = newBuilder().proxy(NO_PROXY).build().newWebSocketBuilder()
                 .buildAsync(server.getURI(), listener)
                 .join();