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