--- a/jdk/src/java.base/share/classes/module-info.java Wed Jun 28 17:57:20 2017 -0700
+++ b/jdk/src/java.base/share/classes/module-info.java Thu Jun 29 11:10:30 2017 +0100
@@ -222,7 +222,8 @@
jdk.naming.dns;
exports sun.net.util to
java.desktop,
- jdk.jconsole;
+ jdk.jconsole,
+ jdk.incubator.httpclient;
exports sun.net.www to
java.desktop,
jdk.incubator.httpclient,
--- a/jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/AsyncSSLConnection.java Wed Jun 28 17:57:20 2017 -0700
+++ b/jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/AsyncSSLConnection.java Thu Jun 29 11:10:30 2017 +0100
@@ -46,11 +46,13 @@
final AsyncSSLDelegate sslDelegate;
final PlainHttpConnection plainConnection;
+ final String serverName;
AsyncSSLConnection(InetSocketAddress addr, HttpClientImpl client, String[] ap) {
super(addr, client);
plainConnection = new PlainHttpConnection(addr, client);
- sslDelegate = new AsyncSSLDelegate(plainConnection, client, ap);
+ serverName = Utils.getServerName(addr);
+ sslDelegate = new AsyncSSLDelegate(plainConnection, client, ap, serverName);
}
@Override
--- a/jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/AsyncSSLDelegate.java Wed Jun 28 17:57:20 2017 -0700
+++ b/jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/AsyncSSLDelegate.java Thu Jun 29 11:10:30 2017 +0100
@@ -108,6 +108,7 @@
final SSLParameters sslParameters;
final HttpConnection lowerOutput;
final HttpClientImpl client;
+ final String serverName;
// should be volatile to provide proper synchronization(visibility) action
volatile Consumer<ByteBufferReference> asyncReceiver;
volatile Consumer<Throwable> errorHandler;
@@ -121,9 +122,10 @@
// alpn[] may be null. upcall is callback which receives incoming decoded bytes off socket
- AsyncSSLDelegate(HttpConnection lowerOutput, HttpClientImpl client, String[] alpn)
+ AsyncSSLDelegate(HttpConnection lowerOutput, HttpClientImpl client, String[] alpn, String sname)
{
SSLContext context = client.sslContext();
+ this.serverName = sname;
engine = context.createSSLEngine();
engine.setUseClientMode(true);
SSLParameters sslp = client.sslParameters()
@@ -135,6 +137,10 @@
} else {
Log.logSSL("AsyncSSLDelegate: no applications set!");
}
+ if (serverName != null) {
+ SNIHostName sn = new SNIHostName(serverName);
+ sslParameters.setServerNames(List.of(sn));
+ }
logParams(sslParameters);
engine.setSSLParameters(sslParameters);
this.lowerOutput = lowerOutput;
--- a/jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpRequestImpl.java Wed Jun 28 17:57:20 2017 -0700
+++ b/jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpRequestImpl.java Thu Jun 29 11:10:30 2017 +0100
@@ -58,7 +58,8 @@
* Creates an HttpRequestImpl from the given builder.
*/
public HttpRequestImpl(HttpRequestBuilderImpl builder) {
- this.method = builder.method();
+ String method = builder.method();
+ this.method = method == null? "GET" : method;
this.userHeaders = ImmutableHeaders.of(builder.headers().map(), ALLOWED_HEADERS);
this.systemHeaders = new HttpHeadersImpl();
this.uri = builder.uri();
@@ -77,7 +78,8 @@
* Creates an HttpRequestImpl from the given request.
*/
public HttpRequestImpl(HttpRequest request) {
- this.method = request.method();
+ String method = request.method();
+ this.method = method == null? "GET" : method;
this.userHeaders = request.headers();
if (request instanceof HttpRequestImpl) {
this.systemHeaders = ((HttpRequestImpl) request).systemHeaders;
--- a/jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/ResponseHeaders.java Wed Jun 28 17:57:20 2017 -0700
+++ b/jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/ResponseHeaders.java Thu Jun 29 11:10:30 2017 +0100
@@ -154,14 +154,12 @@
String k = key.toLowerCase(Locale.US);
cookedHeaders.merge(k, newValues,
(v1, v2) -> {
- if (v1 == null) {
- ArrayList<String> newV = new ArrayList<>();
- newV.addAll(v2);
- return newV;
- } else {
- v1.addAll(v2);
- return v1;
+ ArrayList<String> newV = new ArrayList<>();
+ if (v1 != null) {
+ newV.addAll(v1);
}
+ newV.addAll(v2);
+ return newV;
});
}
return cookedHeaders;
--- a/jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/SSLConnection.java Wed Jun 28 17:57:20 2017 -0700
+++ b/jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/SSLConnection.java Thu Jun 29 11:10:30 2017 +0100
@@ -46,13 +46,14 @@
PlainHttpConnection delegate;
SSLDelegate sslDelegate;
final String[] alpn;
+ final String serverName;
@Override
public CompletableFuture<Void> connectAsync() {
return delegate.connectAsync()
.thenCompose((Void v) ->
MinimalFuture.supply( () -> {
- this.sslDelegate = new SSLDelegate(delegate.channel(), client, alpn);
+ this.sslDelegate = new SSLDelegate(delegate.channel(), client, alpn, serverName);
return null;
}));
}
@@ -60,12 +61,13 @@
@Override
public void connect() throws IOException {
delegate.connect();
- this.sslDelegate = new SSLDelegate(delegate.channel(), client, alpn);
+ this.sslDelegate = new SSLDelegate(delegate.channel(), client, alpn, serverName);
}
SSLConnection(InetSocketAddress addr, HttpClientImpl client, String[] ap) {
super(addr, client);
this.alpn = ap;
+ this.serverName = Utils.getServerName(addr);
delegate = new PlainHttpConnection(addr, client);
}
@@ -77,8 +79,9 @@
super(c.address, c.client);
this.delegate = c.plainConnection;
AsyncSSLDelegate adel = c.sslDelegate;
- this.sslDelegate = new SSLDelegate(adel.engine, delegate.channel(), client);
+ this.sslDelegate = new SSLDelegate(adel.engine, delegate.channel(), client, adel.serverName);
this.alpn = adel.alpn;
+ this.serverName = adel.serverName;
}
@Override
--- a/jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/SSLDelegate.java Wed Jun 28 17:57:20 2017 -0700
+++ b/jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/SSLDelegate.java Thu Jun 29 11:10:30 2017 +0100
@@ -29,6 +29,7 @@
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.Arrays;
+import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import javax.net.ssl.SSLEngineResult.HandshakeStatus;
@@ -50,26 +51,33 @@
final SSLParameters sslParameters;
final SocketChannel chan;
final HttpClientImpl client;
+ final String serverName;
- SSLDelegate(SSLEngine eng, SocketChannel chan, HttpClientImpl client)
+ SSLDelegate(SSLEngine eng, SocketChannel chan, HttpClientImpl client, String sn)
{
this.engine = eng;
this.chan = chan;
this.client = client;
this.wrapper = new EngineWrapper(chan, engine);
this.sslParameters = engine.getSSLParameters();
+ this.serverName = sn;
}
// alpn[] may be null
- SSLDelegate(SocketChannel chan, HttpClientImpl client, String[] alpn)
+ SSLDelegate(SocketChannel chan, HttpClientImpl client, String[] alpn, String sn)
throws IOException
{
+ serverName = sn;
SSLContext context = client.sslContext();
engine = context.createSSLEngine();
engine.setUseClientMode(true);
SSLParameters sslp = client.sslParameters()
.orElseGet(context::getSupportedSSLParameters);
sslParameters = Utils.copySSLParameters(sslp);
+ if (sn != null) {
+ SNIHostName sni = new SNIHostName(sn);
+ sslParameters.setServerNames(List.of(sni));
+ }
if (alpn != null) {
sslParameters.setApplicationProtocols(alpn);
Log.logSSL("SSLDelegate: Setting application protocols: {0}" + Arrays.toString(alpn));
--- a/jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/SSLTunnelConnection.java Wed Jun 28 17:57:20 2017 -0700
+++ b/jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/SSLTunnelConnection.java Thu Jun 29 11:10:30 2017 +0100
@@ -46,11 +46,12 @@
final PlainTunnelingConnection delegate;
protected SSLDelegate sslDelegate;
private volatile boolean connected;
+ final String serverName;
@Override
public void connect() throws IOException, InterruptedException {
delegate.connect();
- this.sslDelegate = new SSLDelegate(delegate.channel(), client, null);
+ this.sslDelegate = new SSLDelegate(delegate.channel(), client, null, serverName);
connected = true;
}
@@ -67,7 +68,7 @@
// can this block?
this.sslDelegate = new SSLDelegate(delegate.channel(),
client,
- null);
+ null, serverName);
connected = true;
} catch (IOException e) {
throw new UncheckedIOException(e);
@@ -80,6 +81,7 @@
InetSocketAddress proxy)
{
super(addr, client);
+ this.serverName = Utils.getServerName(addr);
delegate = new PlainTunnelingConnection(addr, proxy, client);
}
--- a/jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/common/Utils.java Wed Jun 28 17:57:20 2017 -0700
+++ b/jdk/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/common/Utils.java Thu Jun 29 11:10:30 2017 +0100
@@ -27,6 +27,7 @@
import jdk.internal.misc.InnocuousThread;
import sun.net.NetProperties;
+import sun.net.util.IPAddressUtil;
import javax.net.ssl.SSLParameters;
import java.io.ByteArrayOutputStream;
@@ -35,6 +36,7 @@
import java.io.UncheckedIOException;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
+import java.net.InetSocketAddress;
import java.net.NetPermission;
import java.net.URI;
import java.net.URLPermission;
@@ -164,6 +166,22 @@
return !token.isEmpty();
}
+ /**
+ * If the address was created with a domain name, then return
+ * the domain name string. If created with a literal IP address
+ * then return null. We do this to avoid doing a reverse lookup
+ * Used to populate the TLS SNI parameter. So, SNI is only set
+ * when a domain name was supplied.
+ */
+ public static String getServerName(InetSocketAddress addr) {
+ String host = addr.getHostString();
+ if (IPAddressUtil.textToNumericFormatV4(host) != null)
+ return null;
+ if (IPAddressUtil.textToNumericFormatV6(host) != null)
+ return null;
+ return host;
+ }
+
/*
* Validates a RFC 7230 field-value.
*
--- a/jdk/test/java/net/httpclient/http2/TLSConnection.java Wed Jun 28 17:57:20 2017 -0700
+++ b/jdk/test/java/net/httpclient/http2/TLSConnection.java Thu Jun 29 11:10:30 2017 +0100
@@ -65,7 +65,7 @@
Handler handler = new Handler();
- try (Http2TestServer server = new Http2TestServer(true, 0)) {
+ try (Http2TestServer server = new Http2TestServer("127.0.0.1", true, 0)) {
server.addHandler(handler, "/");
server.start();
--- a/jdk/test/java/net/httpclient/http2/server/Http2TestServer.java Wed Jun 28 17:57:20 2017 -0700
+++ b/jdk/test/java/net/httpclient/http2/server/Http2TestServer.java Thu Jun 29 11:10:30 2017 +0100
@@ -33,6 +33,7 @@
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
+import javax.net.ssl.SNIServerName;
/**
* Waits for incoming TCP connections from a client and establishes
@@ -48,6 +49,7 @@
volatile boolean stopping = false;
final Map<String,Http2Handler> handlers;
final SSLContext sslContext;
+ final String serverName;
final HashMap<InetSocketAddress,Http2TestServerConnection> connections;
private static ThreadFactory defaultThreadFac =
@@ -62,8 +64,12 @@
return Executors.newCachedThreadPool(defaultThreadFac);
}
+ public Http2TestServer(String serverName, boolean secure, int port) throws Exception {
+ this(serverName, secure, port, getDefaultExecutor(), null);
+ }
+
public Http2TestServer(boolean secure, int port) throws Exception {
- this(secure, port, getDefaultExecutor(), null);
+ this(null, secure, port, getDefaultExecutor(), null);
}
public InetSocketAddress getAddress() {
@@ -72,7 +78,19 @@
public Http2TestServer(boolean secure,
SSLContext context) throws Exception {
- this(secure, 0, null, context);
+ this(null, secure, 0, null, context);
+ }
+
+ public Http2TestServer(String serverName, boolean secure,
+ SSLContext context) throws Exception {
+ this(serverName, secure, 0, null, context);
+ }
+
+ public Http2TestServer(boolean secure,
+ int port,
+ ExecutorService exec,
+ SSLContext context) throws Exception {
+ this(null, secure, port, exec, context);
}
/**
@@ -80,17 +98,20 @@
* to know in advance whether incoming connections are plain TCP "h2c"
* or TLS "h2"/
*
+ * @param serverName SNI servername
* @param secure https or http
* @param port listen port
* @param exec executor service (cached thread pool is used if null)
* @param context the SSLContext used when secure is true
*/
- public Http2TestServer(boolean secure,
+ public Http2TestServer(String serverName,
+ boolean secure,
int port,
ExecutorService exec,
SSLContext context)
throws Exception
{
+ this.serverName = serverName;
if (secure) {
server = initSecure(port);
} else {
@@ -165,6 +186,10 @@
return se;
}
+ public String serverName() {
+ return serverName;
+ }
+
/**
* Starts a thread which waits for incoming connections.
*/
--- a/jdk/test/java/net/httpclient/http2/server/Http2TestServerConnection.java Wed Jun 28 17:57:20 2017 -0700
+++ b/jdk/test/java/net/httpclient/http2/server/Http2TestServerConnection.java Thu Jun 29 11:10:30 2017 +0100
@@ -25,12 +25,13 @@
import java.io.BufferedOutputStream;
import java.io.Closeable;
import java.io.IOException;
+import java.io.UncheckedIOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.URI;
-import javax.net.ssl.SSLSession;
-import javax.net.ssl.SSLSocket;
+import java.net.InetAddress;
+import javax.net.ssl.*;
import java.net.URISyntaxException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
@@ -79,6 +80,9 @@
final static byte[] clientPreface = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n".getBytes();
Http2TestServerConnection(Http2TestServer server, Socket socket) throws IOException {
+ if (socket instanceof SSLSocket) {
+ handshake(server.serverName(), (SSLSocket)socket);
+ }
System.err.println("TestServer: New connection from " + socket);
this.server = server;
this.streams = Collections.synchronizedMap(new HashMap<>());
@@ -92,6 +96,42 @@
os = new BufferedOutputStream(socket.getOutputStream());
}
+ private static boolean compareIPAddrs(InetAddress addr1, String host) {
+ try {
+ InetAddress addr2 = InetAddress.getByName(host);
+ return addr1.equals(addr2);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ private static void handshake(String name, SSLSocket sock) throws IOException {
+ if (name == null) {
+ // no name set. No need to check
+ return;
+ } else if (name.equals("127.0.0.1")) {
+ name = "localhost";
+ }
+ final String fname = name;
+ final InetAddress addr1 = InetAddress.getByName(name);
+ SSLParameters params = sock.getSSLParameters();
+ SNIMatcher matcher = new SNIMatcher(StandardConstants.SNI_HOST_NAME) {
+ public boolean matches (SNIServerName n) {
+ String host = ((SNIHostName)n).getAsciiName();
+ if (host.equals("127.0.0.1"))
+ host = "localhost";
+ boolean cmp = host.equalsIgnoreCase(fname);
+ if (cmp)
+ return true;
+ return compareIPAddrs(addr1, host);
+ }
+ };
+ List<SNIMatcher> list = List.of(matcher);
+ params.setSNIMatchers(list);
+ sock.setSSLParameters(params);
+ sock.getSession(); // blocks until handshake done
+ }
+
void close() {
streams.forEach((i, q) -> {
q.close();