8175814: Update default HttpClient protocol version and optional request version
Reviewed-by: chegar, dfuchs
--- a/jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/AbstractPushPublisher.java Fri Apr 28 16:51:56 2017 +0530
+++ b/jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/AbstractPushPublisher.java Fri Apr 28 14:16:33 2017 +0100
@@ -54,4 +54,4 @@
}
}
- }
+}
--- a/jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/AsyncConnection.java Fri Apr 28 16:51:56 2017 +0530
+++ b/jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/AsyncConnection.java Fri Apr 28 14:16:33 2017 +0100
@@ -71,6 +71,11 @@
void startReading();
/**
+ * Cancel asynchronous reading. Used to downgrade a HTTP/2 connection to HTTP/1
+ */
+ void stopAsyncReading();
+
+ /**
* In async mode, this method puts buffers at the end of the send queue.
* When in async mode, calling this method should later be followed by
* subsequent flushAsync invocation.
@@ -80,6 +85,11 @@
void writeAsync(ByteBufferReference[] buffers) throws IOException;
/**
+ * Re-enable asynchronous reads through the callback
+ */
+ void enableCallback();
+
+ /**
* In async mode, this method may put buffers at the beginning of send queue,
* breaking frames sequence and allowing to write these buffers before other
* buffers in the queue.
@@ -99,5 +109,4 @@
* and continue execution.
*/
void flushAsync() throws IOException;
-
}
--- a/jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/AsyncSSLConnection.java Fri Apr 28 16:51:56 2017 +0530
+++ b/jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/AsyncSSLConnection.java Fri Apr 28 14:16:33 2017 +0100
@@ -32,6 +32,7 @@
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import java.util.function.Supplier;
+import javax.net.ssl.SSLEngine;
import jdk.incubator.http.internal.common.ByteBufferReference;
import jdk.incubator.http.internal.common.ExceptionallyCloseable;
@@ -44,33 +45,48 @@
implements AsyncConnection, ExceptionallyCloseable {
final AsyncSSLDelegate sslDelegate;
- final PlainHttpConnection delegate;
+ final PlainHttpConnection plainConnection;
AsyncSSLConnection(InetSocketAddress addr, HttpClientImpl client, String[] ap) {
super(addr, client);
- delegate = new PlainHttpConnection(addr, client);
- sslDelegate = new AsyncSSLDelegate(delegate, client, ap);
+ plainConnection = new PlainHttpConnection(addr, client);
+ sslDelegate = new AsyncSSLDelegate(plainConnection, client, ap);
}
@Override
synchronized void configureMode(Mode mode) throws IOException {
super.configureMode(mode);
- delegate.configureMode(mode);
+ plainConnection.configureMode(mode);
+ }
+
+ private CompletableFuture<Void> configureModeAsync(Void ignore) {
+ CompletableFuture<Void> cf = new CompletableFuture<>();
+ try {
+ configureMode(Mode.ASYNC);
+ cf.complete(null);
+ } catch (Throwable t) {
+ cf.completeExceptionally(t);
+ }
+ return cf;
}
@Override
public void connect() throws IOException, InterruptedException {
- delegate.connect();
+ plainConnection.connect();
+ configureMode(Mode.ASYNC);
+ startReading();
+ sslDelegate.connect();
}
@Override
public CompletableFuture<Void> connectAsync() {
- return delegate.connectAsync();
+ // not used currently
+ throw new InternalError();
}
@Override
boolean connected() {
- return delegate.connected();
+ return plainConnection.connected() && sslDelegate.connected();
}
@Override
@@ -85,7 +101,12 @@
@Override
SocketChannel channel() {
- return delegate.channel();
+ return plainConnection.channel();
+ }
+
+ @Override
+ public void enableCallback() {
+ sslDelegate.enableCallback();
}
@Override
@@ -131,22 +152,26 @@
@Override
public void closeExceptionally(Throwable cause) {
- Utils.close(cause, sslDelegate, delegate.channel());
+ Utils.close(cause, sslDelegate, plainConnection.channel());
}
@Override
public void close() {
- Utils.close(sslDelegate, delegate.channel());
+ Utils.close(sslDelegate, plainConnection.channel());
}
@Override
void shutdownInput() throws IOException {
- delegate.channel().shutdownInput();
+ plainConnection.channel().shutdownInput();
}
@Override
void shutdownOutput() throws IOException {
- delegate.channel().shutdownOutput();
+ plainConnection.channel().shutdownOutput();
+ }
+
+ SSLEngine getEngine() {
+ return sslDelegate.getEngine();
}
@Override
@@ -154,7 +179,7 @@
Consumer<Throwable> errorReceiver,
Supplier<ByteBufferReference> readBufferSupplier) {
sslDelegate.setAsyncCallbacks(asyncReceiver, errorReceiver, readBufferSupplier);
- delegate.setAsyncCallbacks(sslDelegate::asyncReceive, errorReceiver, sslDelegate::getNetBuffer);
+ plainConnection.setAsyncCallbacks(sslDelegate::asyncReceive, errorReceiver, sslDelegate::getNetBuffer);
}
// Blocking read functions not used here
@@ -176,7 +201,12 @@
@Override
public void startReading() {
- delegate.startReading();
+ plainConnection.startReading();
sslDelegate.startReading();
}
+
+ @Override
+ public void stopAsyncReading() {
+ plainConnection.stopAsyncReading();
+ }
}
--- a/jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/AsyncSSLDelegate.java Fri Apr 28 16:51:56 2017 +0530
+++ b/jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/AsyncSSLDelegate.java Fri Apr 28 14:16:33 2017 +0100
@@ -28,8 +28,10 @@
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Semaphore;
+import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import java.util.function.Supplier;
@@ -72,13 +74,13 @@
* channelInputQ
* /\
* ||
- * "lowerRead" method puts buffers into channelInputQ. It is invoked from
+ * "asyncReceive" method puts buffers into channelInputQ. It is invoked from
* OP_READ events from the selector.
*
* Whenever handshaking is required, the doHandshaking() method is called
* which creates a thread to complete the handshake. It takes over the
* channelInputQ from upperRead, and puts outgoing packets on channelOutputQ.
- * Selector events are delivered to lowerRead and lowerWrite as normal.
+ * Selector events are delivered to asyncReceive and lowerWrite as normal.
*
* Errors
*
@@ -92,9 +94,6 @@
// while SSL handshaking happening.
final AsyncWriteQueue appOutputQ = new AsyncWriteQueue(this::upperWrite);
- // queue of wrapped ByteBuffers waiting to be sent on socket channel
- //final Queue<ByteBuffer> channelOutputQ;
-
// Bytes read into this queue before being unwrapped. Backup on this
// Q should only happen when the engine is stalled due to delegated tasks
final Queue<ByteBufferReference> channelInputQ;
@@ -107,35 +106,34 @@
final SSLEngine engine;
final SSLParameters sslParameters;
- //final SocketChannel chan;
final HttpConnection lowerOutput;
final HttpClientImpl client;
// should be volatile to provide proper synchronization(visibility) action
volatile Consumer<ByteBufferReference> asyncReceiver;
volatile Consumer<Throwable> errorHandler;
+ volatile boolean connected = false;
// Locks.
final Object reader = new Object();
// synchronizing handshake state
final Semaphore handshaker = new Semaphore(1);
- // flag set when frame or writer is blocked waiting for handshake to finish
- //boolean writerBlocked;
- //boolean readerBlocked;
+ final String[] alpn;
// alpn[] may be null. upcall is callback which receives incoming decoded bytes off socket
AsyncSSLDelegate(HttpConnection lowerOutput, HttpClientImpl client, String[] alpn)
{
SSLContext context = client.sslContext();
- //channelOutputQ = new Queue<>();
- //channelOutputQ.registerPutCallback(this::lowerWrite);
engine = context.createSSLEngine();
engine.setUseClientMode(true);
SSLParameters sslp = client.sslParameters()
.orElseGet(context::getSupportedSSLParameters);
sslParameters = Utils.copySSLParameters(sslp);
if (alpn != null) {
+ Log.logSSL("AsyncSSLDelegate: Setting application protocols: " + Arrays.toString(alpn));
sslParameters.setApplicationProtocols(alpn);
+ } else {
+ Log.logSSL("AsyncSSLDelegate: no applications set!");
}
logParams(sslParameters);
engine.setSSLParameters(sslParameters);
@@ -143,6 +141,7 @@
this.client = client;
this.channelInputQ = new Queue<>();
this.channelInputQ.registerPutCallback(this::upperRead);
+ this.alpn = alpn;
}
@Override
@@ -162,6 +161,10 @@
}
}
+ SSLEngine getEngine() {
+ return engine;
+ }
+
@Override
public void closeExceptionally(Throwable t) {
Utils.close(t, appOutputQ, channelInputQ, lowerOutput);
@@ -223,6 +226,18 @@
}
}
+ // Connecting at this level means the initial handshake has completed.
+ // This means that the initial SSL parameters are available including
+ // ALPN result.
+ void connect() throws IOException, InterruptedException {
+ doHandshakeNow("Init");
+ connected = true;
+ }
+
+ boolean connected() {
+ return connected;
+ }
+
private void startHandshake(String tag) {
Runnable run = () -> {
try {
@@ -241,22 +256,28 @@
{
handshaker.acquire();
try {
- channelInputQ.registerPutCallback(null);
+ channelInputQ.disableCallback();
lowerOutput.flushAsync();
Log.logTrace("{0}: Starting handshake...", tag);
doHandshakeImpl();
Log.logTrace("{0}: Handshake completed", tag);
- channelInputQ.registerPutCallback(this::upperRead);
+ // don't unblock the channel here, as we aren't sure yet, whether ALPN
+ // negotiation succeeded. Caller will call enableCallback() externally
} finally {
handshaker.release();
}
}
+ public void enableCallback() {
+ channelInputQ.enableCallback();
+ }
+
/**
* Executes entire handshake in calling thread.
* Returns after handshake is completed or error occurs
*/
private void doHandshakeImpl() throws IOException {
+ engine.beginHandshake();
while (true) {
SSLEngineResult.HandshakeStatus status = engine.getHandshakeStatus();
switch(status) {
@@ -272,7 +293,9 @@
case NEED_UNWRAP: case NEED_UNWRAP_AGAIN:
handshakeReceiveAndUnWrap();
break;
- case FINISHED: case NOT_HANDSHAKING:
+ case FINISHED:
+ return;
+ case NOT_HANDSHAKING:
return;
default:
throw new InternalError("Unexpected Handshake Status: "
@@ -311,6 +334,12 @@
// maybe this class does not need to implement AsyncConnection
}
+ @Override
+ public void stopAsyncReading() {
+ // maybe this class does not need to implement AsyncConnection
+ }
+
+
static class EngineResult {
final SSLEngineResult result;
final ByteBufferReference destBuffer;
--- a/jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/ConnectionPool.java Fri Apr 28 16:51:56 2017 +0530
+++ b/jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/ConnectionPool.java Fri Apr 28 14:16:33 2017 +0100
@@ -111,7 +111,8 @@
}
static CacheKey cacheKey(InetSocketAddress destination,
- InetSocketAddress proxy) {
+ InetSocketAddress proxy)
+ {
return new CacheKey(destination, proxy);
}
--- a/jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/Exchange.java Fri Apr 28 16:51:56 2017 +0530
+++ b/jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/Exchange.java Fri Apr 28 14:16:33 2017 +0100
@@ -549,7 +549,7 @@
}
HttpClient.Version version() {
- return client.version();
+ return multi.version();
}
private static SocketPermission getSocketPermissionFor(URI url) {
--- a/jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/ExchangeImpl.java Fri Apr 28 16:51:56 2017 +0530
+++ b/jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/ExchangeImpl.java Fri Apr 28 14:16:33 2017 +0100
@@ -81,7 +81,17 @@
} else {
Http2ClientImpl c2 = exchange.client().client2(); // TODO: improve
HttpRequestImpl request = exchange.request();
- Http2Connection c = c2.getConnectionFor(request);
+ Http2Connection c;
+ try {
+ c = c2.getConnectionFor(request);
+ } catch (Http2Connection.ALPNException e) {
+ // failed to negotiate "h2"
+ AsyncSSLConnection as = e.getConnection();
+ as.stopAsyncReading();
+ SSLConnection sslc = new SSLConnection(as);
+ ExchangeImpl<U> ex = new Http1Exchange<>(exchange, sslc);
+ return ex;
+ }
if (c == null) {
// no existing connection. Send request with HTTP 1 and then
// upgrade if successful
--- a/jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/Http2ClientImpl.java Fri Apr 28 16:51:56 2017 +0530
+++ b/jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/Http2ClientImpl.java Fri Apr 28 14:16:33 2017 +0100
@@ -104,7 +104,15 @@
return connection;
}
// we are opening the connection here blocking until it is done.
- connection = new Http2Connection(req, this);
+ try {
+ connection = new Http2Connection(req, this);
+ } catch (Throwable t) {
+ synchronized (opening) {
+ opening.remove(key);
+ opening.notifyAll();
+ }
+ throw t;
+ }
synchronized (opening) {
connections.put(key, connection);
opening.remove(key);
--- a/jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/Http2Connection.java Fri Apr 28 16:51:56 2017 +0530
+++ b/jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/Http2Connection.java Fri Apr 28 14:16:33 2017 +0100
@@ -43,6 +43,7 @@
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.stream.Collectors;
+import javax.net.ssl.SSLEngine;
import jdk.incubator.http.internal.common.*;
import jdk.incubator.http.internal.frame.*;
import jdk.incubator.http.internal.hpack.Encoder;
@@ -82,8 +83,6 @@
* stream are provided by calling Stream.incoming().
*/
class Http2Connection {
-
-
/*
* ByteBuffer pooling strategy for HTTP/2 protocol:
*
@@ -258,15 +257,46 @@
keyFor(request.uri(), request.proxy(h2client.client())));
Log.logTrace("Connection send window size {0} ", windowController.connectionWindowSize());
- connection.connect();
// start reading
AsyncConnection asyncConn = (AsyncConnection)connection;
asyncConn.setAsyncCallbacks(this::asyncReceive, this::shutdown, this::getReadBuffer);
- connection.configureMode(Mode.ASYNC); // set mode only AFTER setAsyncCallbacks to provide visibility.
- asyncConn.startReading();
+ connection.connect();
+ checkSSLConfig();
+ // safe to resume async reading now.
+ asyncConn.enableCallback();
sendConnectionPreface();
}
+ /**
+ * Throws an IOException if h2 was not negotiated
+ */
+ private void checkSSLConfig() throws IOException {
+ AsyncSSLConnection aconn = (AsyncSSLConnection)connection;
+ SSLEngine engine = aconn.getEngine();
+ String alpn = engine.getApplicationProtocol();
+ if (alpn == null || !alpn.equals("h2")) {
+ String msg;
+ if (alpn == null) {
+ Log.logSSL("ALPN not supported");
+ msg = "ALPN not supported";
+ } else switch (alpn) {
+ case "":
+ Log.logSSL("No ALPN returned");
+ msg = "No ALPN negotiated";
+ break;
+ case "http/1.1":
+ Log.logSSL("HTTP/1.1 ALPN returned");
+ msg = "HTTP/1.1 ALPN returned";
+ break;
+ default:
+ Log.logSSL("unknown ALPN returned");
+ msg = "Unexpected ALPN: " + alpn;
+ throw new IOException(msg);
+ }
+ throw new ALPNException(msg, aconn);
+ }
+ }
+
static String keyFor(HttpConnection connection) {
boolean isProxy = connection.isProxied();
boolean isSecure = connection.isSecure();
@@ -858,4 +888,21 @@
return 0;
}
}
+
+ /**
+ * Thrown when https handshake negotiates http/1.1 alpn instead of h2
+ */
+ static final class ALPNException extends IOException {
+ private static final long serialVersionUID = 23138275393635783L;
+ final AsyncSSLConnection connection;
+
+ ALPNException(String msg, AsyncSSLConnection connection) {
+ super(msg);
+ this.connection = connection;
+ }
+
+ AsyncSSLConnection getConnection() {
+ return connection;
+ }
+ }
}
--- a/jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpClient.java Fri Apr 28 16:51:56 2017 +0530
+++ b/jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpClient.java Fri Apr 28 14:16:33 2017 +0100
@@ -154,9 +154,9 @@
/**
* Requests a specific HTTP protocol version where possible. If not set,
- * the version defaults to {@link HttpClient.Version#HTTP_1_1}. If
+ * the version defaults to {@link HttpClient.Version#HTTP_2}. If
* {@link HttpClient.Version#HTTP_2} is set, then each request will
- * attempt to upgrade to HTTP/2. If the upgrade succeeds, then the
+ * attempt to upgrade to HTTP/2. If the upgrade succeeds, then the
* response to this request will use HTTP/2 and all subsequent requests
* and responses to the same
* <a href="https://tools.ietf.org/html/rfc6454#section-4">origin server</a>
@@ -267,7 +267,7 @@
/**
* Returns the HTTP protocol version requested for this client. The default
- * value is {@link HttpClient.Version#HTTP_1_1}
+ * value is {@link HttpClient.Version#HTTP_2}
*
* @return the HTTP protocol version requested
*/
--- a/jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpClientBuilderImpl.java Fri Apr 28 16:51:56 2017 +0530
+++ b/jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpClientBuilderImpl.java Fri Apr 28 14:16:33 2017 +0100
@@ -40,7 +40,7 @@
HttpClient.Redirect followRedirects;
ProxySelector proxy;
Authenticator authenticator;
- HttpClient.Version version = HttpClient.Version.HTTP_1_1;
+ HttpClient.Version version;
Executor executor;
// Security parameters
SSLContext sslContext;
--- a/jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpClientImpl.java Fri Apr 28 16:51:56 2017 +0530
+++ b/jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpClientImpl.java Fri Apr 28 14:16:33 2017 +0100
@@ -125,7 +125,11 @@
Redirect.NEVER : builder.followRedirects;
this.proxySelector = builder.proxy;
authenticator = builder.authenticator;
- version = builder.version;
+ if (builder.version == null) {
+ version = HttpClient.Version.HTTP_2;
+ } else {
+ version = builder.version;
+ }
if (builder.sslParams == null) {
sslParams = getDefaultParams(sslContext);
} else {
--- a/jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpConnection.java Fri Apr 28 16:51:56 2017 +0530
+++ b/jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpConnection.java Fri Apr 28 14:16:33 2017 +0100
@@ -152,15 +152,16 @@
HttpClientImpl client,
HttpRequestImpl request, boolean isHttp2)
{
- HttpConnection c;
+ HttpConnection c = null;
InetSocketAddress proxy = request.proxy(client);
boolean secure = request.secure();
ConnectionPool pool = client.connectionPool();
String[] alpn = null;
- if (secure && client.version() == HttpClient.Version.HTTP_2) {
- alpn = new String[1];
+ if (secure && isHttp2) {
+ alpn = new String[2];
alpn[0] = "h2";
+ alpn[1] = "http/1.1";
}
if (!secure) {
@@ -171,7 +172,9 @@
return getPlainConnection(addr, proxy, request, client);
}
} else {
- c = pool.getConnection(true, addr, proxy);
+ if (!isHttp2) { // if http2 we don't cache connections
+ c = pool.getConnection(true, addr, proxy);
+ }
if (c != null) {
return c;
} else {
--- a/jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpRequest.java Fri Apr 28 16:51:56 2017 +0530
+++ b/jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpRequest.java Fri Apr 28 14:16:33 2017 +0100
@@ -303,10 +303,11 @@
public abstract Builder expectContinue(boolean enable);
/**
- * Overrides the {@link HttpClient#version() } setting for this
- * request. This sets the version requested. The corresponding
- * {@link HttpResponse} should be checked for the version that was
- * used.
+ * Sets the preferred {@link HttpClient.Version} for this
+ * request. The corresponding {@link HttpResponse} should be checked
+ * for the version that was used. If the version is not set
+ * in a request, then the version requested will be that of the
+ * sending {@link HttpClient}.
*
* @param version the HTTP protocol version requested
* @return this request builder
@@ -497,13 +498,16 @@
public abstract URI uri();
/**
- * Returns the HTTP protocol version that will be requested for this
- * {@code HttpRequest}. The corresponding {@link HttpResponse} should be
+ * Returns an {@code Optional} containing the HTTP protocol version that
+ * will be requested for this {@code HttpRequest}. If the version was not
+ * set in the request's builder, then the {@code Optional} is empty.
+ * In that case, the version requested will be that of the sending
+ * {@link HttpClient}. The corresponding {@link HttpResponse} should be
* queried to determine the version that was actually used.
*
* @return HTTP protocol version
*/
- public abstract HttpClient.Version version();
+ public abstract Optional<HttpClient.Version> version();
/**
* The (user-accessible) request headers that this request was (or will be)
--- a/jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpRequestBuilderImpl.java Fri Apr 28 16:51:56 2017 +0530
+++ b/jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpRequestBuilderImpl.java Fri Apr 28 14:16:33 2017 +0100
@@ -28,6 +28,7 @@
import java.net.URI;
import jdk.incubator.http.HttpRequest.BodyProcessor;
import java.time.Duration;
+import java.util.Optional;
import static java.util.Objects.requireNonNull;
import jdk.incubator.http.internal.common.HttpHeadersImpl;
import static jdk.incubator.http.internal.common.Utils.isValidName;
@@ -41,7 +42,7 @@
//private HttpClient.Redirect followRedirects;
private boolean expectContinue;
private HttpRequest.BodyProcessor body;
- private HttpClient.Version version;
+ private volatile Optional<HttpClient.Version> version;
//private final HttpClientImpl client;
//private ProxySelector proxy;
private Duration duration;
@@ -52,10 +53,12 @@
this.uri = uri;
this.userHeaders = new HttpHeadersImpl();
this.method = "GET"; // default, as per spec
+ this.version = Optional.empty();
}
public HttpRequestBuilderImpl() {
this.userHeaders = new HttpHeadersImpl();
+ this.version = Optional.empty();
}
@Override
@@ -149,7 +152,7 @@
@Override
public HttpRequestBuilderImpl version(HttpClient.Version version) {
requireNonNull(version);
- this.version = version;
+ this.version = Optional.of(version);
return this;
}
@@ -169,7 +172,7 @@
public HttpRequest.BodyProcessor body() { return body; }
- HttpClient.Version version() { return version; }
+ Optional<HttpClient.Version> version() { return version; }
@Override
public HttpRequest.Builder GET() { return method("GET", null); }
--- a/jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpRequestImpl.java Fri Apr 28 16:51:56 2017 +0530
+++ b/jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpRequestImpl.java Fri Apr 28 14:16:33 2017 +0100
@@ -52,7 +52,7 @@
private boolean isWebSocket;
private AccessControlContext acc;
private final Duration duration;
- private final HttpClient.Version version;
+ private final Optional<HttpClient.Version> version;
/**
* Creates an HttpRequestImpl from the given builder.
@@ -128,8 +128,8 @@
this.authority = authority;
this.secure = false;
this.expectContinue = false;
- this.duration = null; // block TODO: fix
- this.version = client.version(); // TODO: ??
+ this.duration = null;
+ this.version = Optional.of(client.version());
}
/**
@@ -191,12 +191,6 @@
@Override
public boolean expectContinue() { return expectContinue; }
- public boolean requestHttp2() {
- return version.equals(HttpClient.Version.HTTP_2);
- }
-
-// AccessControlContext getAccessControlContext() { return acc; }
-
InetSocketAddress proxy(HttpClientImpl client) {
ProxySelector ps = client.proxy().orElse(null);
if (ps == null) {
@@ -254,7 +248,7 @@
HttpHeadersImpl getSystemHeaders() { return systemHeaders; }
@Override
- public HttpClient.Version version() { return version; }
+ public Optional<HttpClient.Version> version() { return version; }
void addSystemHeader(String name, String value) {
systemHeaders.addHeader(name, value);
--- a/jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/MultiExchange.java Fri Apr 28 16:51:56 2017 +0530
+++ b/jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/MultiExchange.java Fri Apr 28 14:16:33 2017 +0100
@@ -192,7 +192,7 @@
}
HttpClient.Version version() {
- return client.version();
+ return request.version().orElse(client.version());
}
private synchronized void setExchange(Exchange<T> exchange) {
--- a/jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/PlainHttpConnection.java Fri Apr 28 16:51:56 2017 +0530
+++ b/jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/PlainHttpConnection.java Fri Apr 28 14:16:33 2017 +0100
@@ -76,6 +76,11 @@
}
}
+ @Override
+ public void stopAsyncReading() {
+ client.cancelRegistration(chan);
+ }
+
class ConnectEvent extends AsyncEvent {
CompletableFuture<Void> cf;
@@ -213,6 +218,12 @@
}
}
+ @Override
+ public void enableCallback() {
+ // not used
+ assert false;
+ }
+
void asyncOutput(ByteBufferReference[] refs, AsyncWriteQueue delayCallback) {
try {
ByteBuffer[] bufs = ByteBufferReference.toBuffers(refs);
@@ -246,7 +257,6 @@
closed = true;
try {
Log.logError("Closing: " + toString());
- //System.out.println("Closing: " + this);
chan.close();
} catch (IOException e) {}
}
@@ -272,7 +282,6 @@
while (true) {
ByteBufferReference buf = readBufferSupplier.get();
int n = chan.read(buf.get());
- //System.err.printf("Read %d bytes from chan\n", n);
if (n == -1) {
throw new IOException();
}
--- a/jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/SSLConnection.java Fri Apr 28 16:51:56 2017 +0530
+++ b/jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/SSLConnection.java Fri Apr 28 14:16:33 2017 +0100
@@ -69,6 +69,18 @@
delegate = new PlainHttpConnection(addr, client);
}
+ /**
+ * Create an SSLConnection from an existing connected AsyncSSLConnection.
+ * Used when downgrading from HTTP/2 to HTTP/1.1
+ */
+ SSLConnection(AsyncSSLConnection c) {
+ super(c.address, c.client);
+ this.delegate = c.plainConnection;
+ AsyncSSLDelegate adel = c.sslDelegate;
+ this.sslDelegate = new SSLDelegate(adel.engine, delegate.channel(), client);
+ this.alpn = adel.alpn;
+ }
+
@Override
SSLParameters sslParameters() {
return sslDelegate.getSSLParameters();
--- a/jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/SSLDelegate.java Fri Apr 28 16:51:56 2017 +0530
+++ b/jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/SSLDelegate.java Fri Apr 28 14:16:33 2017 +0100
@@ -51,6 +51,15 @@
final SocketChannel chan;
final HttpClientImpl client;
+ SSLDelegate(SSLEngine eng, SocketChannel chan, HttpClientImpl client)
+ {
+ this.engine = eng;
+ this.chan = chan;
+ this.client = client;
+ this.wrapper = new EngineWrapper(chan, engine);
+ this.sslParameters = engine.getSSLParameters();
+ }
+
// alpn[] may be null
SSLDelegate(SocketChannel chan, HttpClientImpl client, String[] alpn)
throws IOException
@@ -63,9 +72,9 @@
sslParameters = Utils.copySSLParameters(sslp);
if (alpn != null) {
sslParameters.setApplicationProtocols(alpn);
- Log.logSSL(() -> "Setting application protocols: " + Arrays.toString(alpn));
+ Log.logSSL("SSLDelegate: Setting application protocols: {0}" + Arrays.toString(alpn));
} else {
- Log.logSSL("No application protocols proposed");
+ Log.logSSL("SSLDelegate: No application protocols proposed");
}
engine.setSSLParameters(sslParameters);
wrapper = new EngineWrapper(chan, engine);
@@ -181,7 +190,7 @@
boolean closed = false;
int u_remaining; // the number of bytes left in unwrap_src after an unwrap()
- EngineWrapper (SocketChannel chan, SSLEngine engine) throws IOException {
+ EngineWrapper (SocketChannel chan, SSLEngine engine) {
this.chan = chan;
this.engine = engine;
wrapLock = new Object();
--- a/jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/common/Queue.java Fri Apr 28 16:51:56 2017 +0530
+++ b/jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/common/Queue.java Fri Apr 28 14:16:33 2017 +0100
@@ -28,6 +28,7 @@
import java.io.IOException;
import java.util.LinkedList;
import java.util.stream.Stream;
+import java.util.Objects;
// Each stream has one of these for input. Each Http2Connection has one
// for output. Can be used blocking or asynchronously.
@@ -38,33 +39,9 @@
private volatile boolean closed = false;
private volatile Throwable exception = null;
private Runnable callback;
- private boolean forceCallback;
+ private boolean callbackDisabled = false;
private int waiters; // true if someone waiting
- public synchronized void putAll(T[] objs) throws IOException {
- if (closed) {
- throw new IOException("stream closed");
- }
- boolean wasEmpty = q.isEmpty();
-
- for (T obj : objs) {
- q.add(obj);
- }
-
- if (waiters > 0) {
- notifyAll();
- }
-
- if (wasEmpty || forceCallback) {
- forceCallback = false;
- if (callback != null) {
- // Note: calling callback while holding the lock is
- // dangerous and may lead to deadlocks.
- callback.run();
- }
- }
- }
-
public synchronized int size() {
return q.size();
}
@@ -81,17 +58,30 @@
}
q.add(obj);
+
if (waiters > 0) {
notifyAll();
}
- if (q.size() == 1 || forceCallback) {
- forceCallback = false;
- if (callback != null) {
- // Note: calling callback while holding the lock is
- // dangerous and may lead to deadlocks.
- callback.run();
- }
+ if (callbackDisabled) {
+ return;
+ }
+
+ if (q.size() > 0 && callback != null) {
+ // Note: calling callback while holding the lock is
+ // dangerous and may lead to deadlocks.
+ callback.run();
+ }
+ }
+
+ public synchronized void disableCallback() {
+ callbackDisabled = true;
+ }
+
+ public synchronized void enableCallback() {
+ callbackDisabled = false;
+ while (q.size() > 0) {
+ callback.run();
}
}
@@ -100,8 +90,9 @@
* the Queue was empty.
*/
public synchronized void registerPutCallback(Runnable callback) {
+ Objects.requireNonNull(callback);
this.callback = callback;
- if (callback != null && q.size() > 0) {
+ if (q.size() > 0) {
// Note: calling callback while holding the lock is
// dangerous and may lead to deadlocks.
callback.run();
@@ -167,12 +158,10 @@
}
public synchronized void pushback(T v) {
- forceCallback = true;
q.addFirst(v);
}
public synchronized void pushbackAll(T[] v) {
- forceCallback = true;
for (int i=v.length-1; i>=0; i--) {
q.addFirst(v[i]);
}
--- a/jdk/test/java/net/httpclient/RequestBodyTest.java Fri Apr 28 16:51:56 2017 +0530
+++ b/jdk/test/java/net/httpclient/RequestBodyTest.java Fri Apr 28 14:16:33 2017 +0100
@@ -104,6 +104,7 @@
SSLContext ctx = LightWeightHttpServer.ctx;
client = HttpClient.newBuilder()
.sslContext(ctx)
+ .version(HttpClient.Version.HTTP_1_1)
.followRedirects(HttpClient.Redirect.ALWAYS)
.executor(exec)
.build();
--- a/jdk/test/java/net/httpclient/SmokeTest.java Fri Apr 28 16:51:56 2017 +0530
+++ b/jdk/test/java/net/httpclient/SmokeTest.java Fri Apr 28 14:16:33 2017 +0100
@@ -165,6 +165,7 @@
client = HttpClient.newBuilder()
.sslContext(ctx)
.executor(e)
+ .version(HttpClient.Version.HTTP_1_1)
.sslParameters(sslparams)
.followRedirects(HttpClient.Redirect.ALWAYS)
.build();
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/net/httpclient/VersionTest.java Fri Apr 28 14:16:33 2017 +0100
@@ -0,0 +1,140 @@
+/*
+ * Copyright (c) 2017, 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
+ * @bug 8175814
+ * @modules jdk.incubator.httpclient java.logging jdk.httpserver
+ * @run main/othervm -Djdk.httpclient.HttpClient.log=errors,requests,headers,trace VersionTest
+ */
+
+import com.sun.net.httpserver.Headers;
+import com.sun.net.httpserver.HttpContext;
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpHandler;
+import com.sun.net.httpserver.HttpServer;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.URI;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ExecutorService;
+import java.net.InetSocketAddress;
+import jdk.incubator.http.HttpClient;
+import jdk.incubator.http.HttpRequest;
+import jdk.incubator.http.HttpResponse;
+import static jdk.incubator.http.HttpRequest.BodyProcessor.fromString;
+import static jdk.incubator.http.HttpResponse.*;
+import static jdk.incubator.http.HttpResponse.BodyHandler.asString;
+import static jdk.incubator.http.HttpResponse.BodyHandler.discard;
+import static jdk.incubator.http.HttpClient.Version.HTTP_1_1;
+import static jdk.incubator.http.HttpClient.Version.HTTP_2;
+
+/**
+ */
+public class VersionTest {
+ static HttpServer s1 ;
+ static ExecutorService executor;
+ static int port;
+ static HttpClient client;
+ static URI uri;
+ static volatile boolean error = false;
+
+ public static void main(String[] args) throws Exception {
+ initServer();
+
+ client = HttpClient.newBuilder()
+ .executor(executor)
+ .build();
+ // first check that the version is HTTP/2
+ if (client.version() != HttpClient.Version.HTTP_2) {
+ throw new RuntimeException("Default version not HTTP_2");
+ }
+ try {
+ test(HTTP_1_1);
+ test(HTTP_2);
+ } finally {
+ s1.stop(0);
+ executor.shutdownNow();
+ }
+ if (error)
+ throw new RuntimeException();
+ }
+
+ public static void test(HttpClient.Version version) throws Exception {
+ HttpRequest r = HttpRequest.newBuilder(uri)
+ .version(version)
+ .GET()
+ .build();
+ HttpResponse<Void> resp = client.send(r, discard(null));
+ System.out.printf("Client: response is %d\n", resp.statusCode());
+ if (resp.version() != HTTP_1_1) {
+ throw new RuntimeException();
+ }
+ //System.out.printf("Client: response body is %s\n", resp.body());
+ }
+
+ static void initServer() throws Exception {
+ InetSocketAddress addr = new InetSocketAddress (0);
+ s1 = HttpServer.create (addr, 0);
+ HttpHandler h = new Handler();
+
+ HttpContext c1 = s1.createContext("/", h);
+
+ executor = Executors.newCachedThreadPool();
+ s1.setExecutor(executor);
+ s1.start();
+
+ port = s1.getAddress().getPort();
+ uri = new URI("http://127.0.0.1:" + Integer.toString(port) + "/foo");
+ System.out.println("HTTP server port = " + port);
+ }
+}
+
+class Handler implements HttpHandler {
+ int counter = 0;
+
+ void checkHeader(Headers h) {
+ counter++;
+ if (counter == 1 && h.containsKey("Upgrade")) {
+ VersionTest.error = true;
+ }
+ if (counter > 1 && !h.containsKey("Upgrade")) {
+ VersionTest.error = true;
+ }
+ }
+
+ @Override
+ public synchronized void handle(HttpExchange t)
+ throws IOException
+ {
+ String reply = "Hello world";
+ int len = reply.length();
+ Headers h = t.getRequestHeaders();
+ checkHeader(h);
+ System.out.printf("Sending response 200\n");
+ t.sendResponseHeaders(200, len);
+ OutputStream o = t.getResponseBody();
+ o.write(reply.getBytes());
+ t.close();
+ }
+}
--- a/jdk/test/java/net/httpclient/http2/ErrorTest.java Fri Apr 28 16:51:56 2017 +0530
+++ b/jdk/test/java/net/httpclient/http2/ErrorTest.java Fri Apr 28 14:16:33 2017 +0100
@@ -31,7 +31,7 @@
* jdk.incubator.httpclient/jdk.incubator.http.internal.frame
* jdk.incubator.httpclient/jdk.incubator.http.internal.hpack
* java.security.jgss
- * @run testng/othervm -Djdk.httpclient.HttpClient.log=ssl,errors ErrorTest
+ * @run testng/othervm/timeout=60 -Djavax.net.debug=ssl -Djdk.httpclient.HttpClient.log=all ErrorTest
* @summary check exception thrown when bad TLS parameters selected
*/
@@ -76,10 +76,13 @@
Http2TestServer httpsServer = null;
try {
+ SSLContext serverContext = (new SimpleSSLContext()).get();
+ SSLParameters p = serverContext.getSupportedSSLParameters();
+ p.setApplicationProtocols(new String[]{"h2"});
httpsServer = new Http2TestServer(true,
0,
exec,
- sslContext);
+ serverContext);
httpsServer.addHandler(new EchoHandler(), "/");
int httpsPort = httpsServer.getAddress().getPort();
String httpsURIString = "https://127.0.0.1:" + httpsPort + "/bar/";
--- a/jdk/test/java/net/httpclient/http2/Timeout.java Fri Apr 28 16:51:56 2017 +0530
+++ b/jdk/test/java/net/httpclient/http2/Timeout.java Fri Apr 28 14:16:33 2017 +0100
@@ -32,6 +32,7 @@
import java.util.concurrent.TimeUnit;
import java.util.concurrent.CompletionException;
import javax.net.ssl.SSLServerSocket;
+import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSocket;
import static jdk.incubator.http.HttpRequest.BodyProcessor.fromString;
@@ -75,6 +76,9 @@
Thread server = new Thread(() -> {
while (true) {
System.out.println("server: ready");
+ SSLParameters params = ssocket.getSSLParameters();
+ params.setApplicationProtocols(new String[]{"h2"});
+ ssocket.setSSLParameters(params);
ready = true;
try (SSLSocket socket = (SSLSocket) ssocket.accept()) {