# HG changeset patch # User chegar # Date 1525253777 25200 # Node ID 4690a2871b447f28dd90d2829a659d126804a826 # Parent 8e1ed2a158451b260940703ed7f947f343955a96 8202423: Small HTTP Client refresh Reviewed-by: chegar, dfuchs, michaelm, prappo Contributed-by: Chris Hegarty , Daniel Fuchs , Michael McMahon , Pavel Rappo diff -r 8e1ed2a15845 -r 4690a2871b44 src/java.net.http/share/classes/jdk/internal/net/http/AsyncSSLConnection.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; } ); } diff -r 8e1ed2a15845 -r 4690a2871b44 src/java.net.http/share/classes/jdk/internal/net/http/AsyncSSLTunnelConnection.java --- 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;} ); } diff -r 8e1ed2a15845 -r 4690a2871b44 src/java.net.http/share/classes/jdk/internal/net/http/Http1AsyncReceiver.java --- 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 { diff -r 8e1ed2a15845 -r 4690a2871b44 src/java.net.http/share/classes/jdk/internal/net/http/Http1Exchange.java --- 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); } }); } diff -r 8e1ed2a15845 -r 4690a2871b44 src/java.net.http/share/classes/jdk/internal/net/http/Http1Request.java --- 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); } } diff -r 8e1ed2a15845 -r 4690a2871b44 src/java.net.http/share/classes/jdk/internal/net/http/Http1Response.java --- 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 bodyReaderCF = bodyReader.completion(); diff -r 8e1ed2a15845 -r 4690a2871b44 src/java.net.http/share/classes/jdk/internal/net/http/Http2ClientImpl.java --- 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); + } } } diff -r 8e1ed2a15845 -r 4690a2871b44 src/java.net.http/share/classes/jdk/internal/net/http/Http2Connection.java --- 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> 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(); } diff -r 8e1ed2a15845 -r 4690a2871b44 src/java.net.http/share/classes/jdk/internal/net/http/HttpClientImpl.java --- 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; + } + } + } diff -r 8e1ed2a15845 -r 4690a2871b44 src/java.net.http/share/classes/jdk/internal/net/http/PlainHttpConnection.java --- 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 diff -r 8e1ed2a15845 -r 4690a2871b44 src/java.net.http/share/classes/jdk/internal/net/http/SocketTube.java --- 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 buffersSource; + private final SliceBufferSource sliceBuffersSource; private final Object lock = new Object(); private final AtomicReference errorRef = new AtomicReference<>(); private final InternalReadPublisher readPublisher; @@ -67,10 +67,11 @@ private final long id = IDS.incrementAndGet(); public SocketTube(HttpClientImpl client, SocketChannel channel, - Supplier buffersSource) { + Supplier 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 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 bytes = readAvailable(); + List 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 append(List 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 factory; + private volatile ByteBuffer current; + + public SliceBufferSource() { + this(Utils::getBuffer); + } + public SliceBufferSource(Supplier 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 append(List 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 append(List 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 readAvailable() throws IOException { - ByteBuffer buf = current; - buf = (buf == null || !buf.hasRemaining()) - ? (current = buffersSource.get()) : buf; + private List 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 List addToList(List list, T item) { + private static List listOf(List 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 res = new ArrayList<>(list); + List res = list instanceof ArrayList ? list : new ArrayList<>(list); res.add(item); return res; } diff -r 8e1ed2a15845 -r 4690a2871b44 src/java.net.http/share/classes/jdk/internal/net/http/Stream.java --- 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() { diff -r 8e1ed2a15845 -r 4690a2871b44 src/java.net.http/share/classes/jdk/internal/net/http/common/BufferSupplier.java --- /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 { + /** + * 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); +} + diff -r 8e1ed2a15845 -r 4690a2871b44 src/java.net.http/share/classes/jdk/internal/net/http/common/ByteBufferPool.java --- 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 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); - } - -} diff -r 8e1ed2a15845 -r 4690a2871b44 src/java.net.http/share/classes/jdk/internal/net/http/common/ByteBufferReference.java --- 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 { - - 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); - } - } -} diff -r 8e1ed2a15845 -r 4690a2871b44 src/java.net.http/share/classes/jdk/internal/net/http/common/DebugLogger.java --- 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 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 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 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 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; + } } } diff -r 8e1ed2a15845 -r 4690a2871b44 src/java.net.http/share/classes/jdk/internal/net/http/common/FlowTube.java --- 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; } + } /** diff -r 8e1ed2a15845 -r 4690a2871b44 src/java.net.http/share/classes/jdk/internal/net/http/common/SSLFlowDelegate.java --- 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 readerCF; final CompletableFuture writerCF; + final Consumer recycler; static AtomicInteger scount = new AtomicInteger(1); final int id; @@ -110,8 +111,23 @@ Subscriber> downReader, Subscriber> 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 recycler, + Subscriber> downReader, + Subscriber> 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 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 stateList = new ConcurrentLinkedQueue<>(); + final ConcurrentLinkedQueue 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() { diff -r 8e1ed2a15845 -r 4690a2871b44 src/java.net.http/share/classes/jdk/internal/net/http/common/SSLTube.java --- 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 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 recycler, SSLSubscriberWrapper readSubscriber, FlowTube tube) { - super(engine, executor, readSubscriber, tube); + super(engine, executor, recycler, readSubscriber, tube); } protected SchedulingAction enterReadScheduling() { readSubscriber.processPendingSubscriber(); diff -r 8e1ed2a15845 -r 4690a2871b44 src/java.net.http/share/classes/jdk/internal/net/http/common/SubscriberWrapper.java --- 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 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(); } } diff -r 8e1ed2a15845 -r 4690a2871b44 src/java.net.http/share/classes/jdk/internal/net/http/common/Utils.java --- 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 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(); + } } diff -r 8e1ed2a15845 -r 4690a2871b44 src/java.net.http/share/classes/jdk/internal/net/http/hpack/Huffman.java --- 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. - * - *

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()); } } } diff -r 8e1ed2a15845 -r 4690a2871b44 src/java.net.http/share/classes/jdk/internal/net/http/hpack/NaiveHuffman.java --- /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. + * + *

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; + } + } +} diff -r 8e1ed2a15845 -r 4690a2871b44 src/java.net.http/share/classes/jdk/internal/net/http/hpack/QuickHuffman.java --- /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 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; + } +} diff -r 8e1ed2a15845 -r 4690a2871b44 src/java.net.http/share/classes/jdk/internal/net/http/hpack/SimpleHeaderTable.java --- 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; } } } diff -r 8e1ed2a15845 -r 4690a2871b44 src/java.net.http/share/classes/jdk/internal/net/http/hpack/StringReader.java --- 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; diff -r 8e1ed2a15845 -r 4690a2871b44 src/java.net.http/share/classes/jdk/internal/net/http/hpack/StringWriter.java --- 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); } diff -r 8e1ed2a15845 -r 4690a2871b44 test/jdk/java/net/httpclient/BodyProcessorInputStreamTest.java --- 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 { diff -r 8e1ed2a15845 -r 4690a2871b44 test/jdk/java/net/httpclient/DependentPromiseActionsTest.java --- 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 { @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 void testDependent(String name, String uri, boolean sameClient, Supplier> handlers, Finisher finisher, Extractor extractor, - Supplier stallers) + Supplier 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> handlers, Finisher finisher, Extractor extractor, - Supplier stallers) + Supplier stallers, + SubscriberType subscriberType) throws Exception { HttpClient client = null; @@ -355,7 +363,7 @@ System.out.println("try stalling in " + where); CompletableFuture> responseCF = client.sendAsync(req, handler, promiseHandler); - assert !responseCF.isDone(); + assert subscriberType == SubscriberType.LAZZY || !responseCF.isDone(); finisher.finish(where, responseCF, promiseHandler, extractor); } } diff -r 8e1ed2a15845 -r 4690a2871b44 test/jdk/java/net/httpclient/EscapedOctetsInURI.java --- 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; diff -r 8e1ed2a15845 -r 4690a2871b44 test/jdk/java/net/httpclient/HttpInputStreamTest.java --- 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 { diff -r 8e1ed2a15845 -r 4690a2871b44 test/jdk/java/net/httpclient/NonAsciiCharsInURI.java --- /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 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 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); + } + } + } +} diff -r 8e1ed2a15845 -r 4690a2871b44 test/jdk/java/net/httpclient/ProxyServer.java --- 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(); } diff -r 8e1ed2a15845 -r 4690a2871b44 test/jdk/java/net/httpclient/RetryWithCookie.java --- 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) diff -r 8e1ed2a15845 -r 4690a2871b44 test/jdk/java/net/httpclient/SmallTimeout.java --- 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 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; diff -r 8e1ed2a15845 -r 4690a2871b44 test/jdk/java/net/httpclient/TimeoutOrdering.java --- 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> response = client .sendAsync(req, BodyHandlers.replacing(null)) .whenComplete((HttpResponse 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); } }); } diff -r 8e1ed2a15845 -r 4690a2871b44 test/jdk/java/net/httpclient/http2/java.net.http/jdk/internal/net/http/hpack/CircularBufferTest.java --- 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 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 referenceQueue = new ArrayBlockingQueue<>(capacity); CircularBuffer buffer = new CircularBuffer<>(capacity); diff -r 8e1ed2a15845 -r 4690a2871b44 test/jdk/java/net/httpclient/http2/java.net.http/jdk/internal/net/http/hpack/DecoderTest.java --- 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 supplier, String hexdump, - String expectedHeaderTable, String expectedHeaderList) { + private static void testAllSplits(Supplier 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 actual = new LinkedList<>(); + Iterator 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); + }); } // diff -r 8e1ed2a15845 -r 4690a2871b44 test/jdk/java/net/httpclient/http2/java.net.http/jdk/internal/net/http/hpack/HuffmanTest.java --- 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 backlog = new Stack<>(); + NaiveHuffman.Node root = NaiveHuffman.INSTANCE.getRoot(); + Stack 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 backlog = new Stack<>(); + NaiveHuffman.Node root = NaiveHuffman.INSTANCE.getRoot(); + Stack 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 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; diff -r 8e1ed2a15845 -r 4690a2871b44 test/jdk/java/net/httpclient/http2/server/Http2TestServer.java --- 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"); diff -r 8e1ed2a15845 -r 4690a2871b44 test/jdk/java/net/httpclient/websocket/DummyWebSocketServer.java --- 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()); } diff -r 8e1ed2a15845 -r 4690a2871b44 test/jdk/java/net/httpclient/websocket/PendingBinaryPingClose.java --- 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); diff -r 8e1ed2a15845 -r 4690a2871b44 test/jdk/java/net/httpclient/websocket/PendingBinaryPongClose.java --- 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); diff -r 8e1ed2a15845 -r 4690a2871b44 test/jdk/java/net/httpclient/websocket/PendingPingBinaryClose.java --- 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); diff -r 8e1ed2a15845 -r 4690a2871b44 test/jdk/java/net/httpclient/websocket/PendingPingTextClose.java --- 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); diff -r 8e1ed2a15845 -r 4690a2871b44 test/jdk/java/net/httpclient/websocket/PendingPongBinaryClose.java --- 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); diff -r 8e1ed2a15845 -r 4690a2871b44 test/jdk/java/net/httpclient/websocket/PendingPongTextClose.java --- 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); diff -r 8e1ed2a15845 -r 4690a2871b44 test/jdk/java/net/httpclient/websocket/PendingTextPingClose.java --- 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); diff -r 8e1ed2a15845 -r 4690a2871b44 test/jdk/java/net/httpclient/websocket/PendingTextPongClose.java --- 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); diff -r 8e1ed2a15845 -r 4690a2871b44 test/jdk/java/net/httpclient/websocket/SendTest.java --- 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(); diff -r 8e1ed2a15845 -r 4690a2871b44 test/jdk/java/net/httpclient/websocket/WSHandshakeExceptionTest.java --- 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(); diff -r 8e1ed2a15845 -r 4690a2871b44 test/jdk/java/net/httpclient/websocket/WebSocketExtendedTest.java --- 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(); diff -r 8e1ed2a15845 -r 4690a2871b44 test/jdk/java/net/httpclient/websocket/WebSocketTest.java --- 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();