# HG changeset patch # User dfuchs # Date 1517845869 0 # Node ID 66a9c3185028974cf368e87c905f59678529685b # Parent ffaea9a1eed5d4c1fbdfc1632584dad5eff63a93 http-client-branch: add tests with an HTTP/2 server for custom basic and digest authentication diff -r ffaea9a1eed5 -r 66a9c3185028 src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpRequestImpl.java --- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpRequestImpl.java Mon Feb 05 12:32:20 2018 +0000 +++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpRequestImpl.java Mon Feb 05 15:51:09 2018 +0000 @@ -48,14 +48,14 @@ private final HttpHeaders userHeaders; private final HttpHeadersImpl systemHeaders; private final URI uri; - private Proxy proxy; - private InetSocketAddress authority; // only used when URI not specified + private volatile Proxy proxy; // ensure safe publishing + private final InetSocketAddress authority; // only used when URI not specified private final String method; final BodyPublisher requestPublisher; final boolean secure; final boolean expectContinue; - private boolean isWebSocket; - private AccessControlContext acc; + private volatile boolean isWebSocket; + private volatile AccessControlContext acc; private final Duration timeout; // may be null private final Optional version; @@ -84,6 +84,7 @@ this.requestPublisher = builder.bodyPublisher(); // may be null this.timeout = builder.timeout(); this.version = builder.version(); + this.authority = null; } /** @@ -119,6 +120,7 @@ } this.timeout = request.timeout().orElse(null); this.version = request.version(); + this.authority = null; } /** Creates a HttpRequestImpl using fields of an existing request impl. */ @@ -137,6 +139,7 @@ this.acc = other.acc; this.timeout = other.timeout; this.version = other.version(); + this.authority = null; } /* used for creating CONNECT requests */ @@ -205,6 +208,7 @@ this.acc = parent.acc; this.timeout = parent.timeout; this.version = parent.version; + this.authority = null; } @Override diff -r ffaea9a1eed5 -r 66a9c3185028 test/jdk/java/net/httpclient/CancelledResponse.java --- a/test/jdk/java/net/httpclient/CancelledResponse.java Mon Feb 05 12:32:20 2018 +0000 +++ b/test/jdk/java/net/httpclient/CancelledResponse.java Mon Feb 05 15:51:09 2018 +0000 @@ -26,7 +26,6 @@ import jdk.incubator.http.HttpHeaders; import jdk.incubator.http.HttpRequest; import jdk.incubator.http.HttpResponse; -import jdk.jshell.spi.ExecutionControl; import jdk.testlibrary.SimpleSSLContext; import javax.net.ServerSocketFactory; diff -r ffaea9a1eed5 -r 66a9c3185028 test/jdk/java/net/httpclient/DigestEchoClient.java --- a/test/jdk/java/net/httpclient/DigestEchoClient.java Mon Feb 05 12:32:20 2018 +0000 +++ b/test/jdk/java/net/httpclient/DigestEchoClient.java Mon Feb 05 15:51:09 2018 +0000 @@ -61,9 +61,13 @@ * @summary this test verifies that a client may provides authorization * headers directly when connecting with a server. * @bug 8087112 - * @library /lib/testlibrary - * @build jdk.testlibrary.SimpleSSLContext DigestEchoServer DigestEchoClient - * @modules jdk.incubator.httpclient + * @library /lib/testlibrary http2/server + * @build jdk.testlibrary.SimpleSSLContext HttpServerAdapters DigestEchoServer DigestEchoClient + * @modules jdk.incubator.httpclient/jdk.incubator.http.internal.common + * jdk.incubator.httpclient/jdk.incubator.http.internal.frame + * jdk.incubator.httpclient/jdk.incubator.http.internal.hpack + * java.logging + * java.base/sun.net.www.http * java.base/sun.net.www * java.base/sun.net * @run main/othervm DigestEchoClient @@ -94,43 +98,49 @@ final String protocolScheme; final String key; final DigestEchoServer server; + final Version serverVersion; private EchoServers(DigestEchoServer server, + Version version, String protocolScheme, DigestEchoServer.HttpAuthType authType, DigestEchoServer.HttpAuthSchemeType authScheme) { this.authType = authType; this.authScheme = authScheme; this.protocolScheme = protocolScheme; - this.key = key(protocolScheme, authType, authScheme); + this.key = key(version, protocolScheme, authType, authScheme); this.server = server; + this.serverVersion = version; } - static String key(String protocolScheme, + static String key(Version version, + String protocolScheme, DigestEchoServer.HttpAuthType authType, DigestEchoServer.HttpAuthSchemeType authScheme) { - return String.format("%s:%s:%s", protocolScheme, authType, authScheme); + return String.format("%s:%s:%s:%s", version, protocolScheme, authType, authScheme); } - private static EchoServers create(String protocolScheme, + private static EchoServers create(Version version, + String protocolScheme, DigestEchoServer.HttpAuthType authType, DigestEchoServer.HttpAuthSchemeType authScheme) { try { serverCount.incrementAndGet(); DigestEchoServer server = - DigestEchoServer.create(protocolScheme, authType, authScheme); - return new EchoServers(server, protocolScheme, authType, authScheme); + DigestEchoServer.create(version, protocolScheme, authType, authScheme); + return new EchoServers(server, version, protocolScheme, authType, authScheme); } catch (IOException x) { throw new UncheckedIOException(x); } } - public static DigestEchoServer of(String protocolScheme, + public static DigestEchoServer of(Version version, + String protocolScheme, DigestEchoServer.HttpAuthType authType, DigestEchoServer.HttpAuthSchemeType authScheme) { - String key = key(protocolScheme, authType, authScheme); + String key = key(version, protocolScheme, authType, authScheme); return servers.computeIfAbsent(key, (k) -> - create(protocolScheme, authType, authScheme)).server; + create(version, protocolScheme, authType, authScheme)).server; } public static void stop() { @@ -214,6 +224,27 @@ return builder.build(); } + public static List serverVersions(Version clientVersion) { + if (clientVersion == Version.HTTP_1_1) { + return List.of(clientVersion); + } else { + return List.of(Version.values()); + } + } + + public static List clientVersions() { + return List.of(Version.values()); + } + + public static List expectContinue(Version serverVersion) { + if (serverVersion == Version.HTTP_1_1) { + return BOOLEANS; + } else { + // our test HTTP/2 server does not support Expect: 100-Continue + return List.of(Boolean.FALSE); + } + } + public static void main(String[] args) throws Exception { boolean useSSL = false; EnumSet types = @@ -239,12 +270,15 @@ DigestEchoClient dec = new DigestEchoClient(useSSL, authScheme, authType); - for (Version version : HttpClient.Version.values()) { - for (boolean expectContinue : BOOLEANS) { - for (boolean async : BOOLEANS) { - for (boolean preemptive : BOOLEANS) { - dec.testBasic(version, async, - expectContinue, preemptive); + for (Version clientVersion : clientVersions()) { + for (Version serverVersion : serverVersions(clientVersion)) { + for (boolean expectContinue : expectContinue(serverVersion)) { + for (boolean async : BOOLEANS) { + for (boolean preemptive : BOOLEANS) { + dec.testBasic(clientVersion, + serverVersion, async, + expectContinue, preemptive); + } } } } @@ -256,15 +290,22 @@ DigestEchoClient dec = new DigestEchoClient(useSSL, authScheme, authType); - for (Version version : HttpClient.Version.values()) { - for (boolean expectContinue : BOOLEANS) { - for (boolean async : BOOLEANS) { - dec.testDigest(version, async, expectContinue); + for (Version clientVersion : clientVersions()) { + for (Version serverVersion : serverVersions(clientVersion)) { + for (boolean expectContinue : expectContinue(serverVersion)) { + for (boolean async : BOOLEANS) { + dec.testDigest(clientVersion, serverVersion, + async, expectContinue); + } } } } } } + } catch(Throwable t) { + System.out.println("Unexpected exception: exiting: " + t); + t.printStackTrace(); + throw t; } finally { EchoServers.stop(); System.out.println(" ---------------------------------------------------------- "); @@ -316,7 +357,7 @@ final static AtomicLong basics = new AtomicLong(); final static AtomicLong basicCount = new AtomicLong(); // @Test - void testBasic(HttpClient.Version version, boolean async, + void testBasic(Version clientVersion, Version serverVersion, boolean async, boolean expectContinue, boolean preemptive) throws Exception { @@ -325,12 +366,15 @@ // headers ourselves if (!preemptive && !addHeaders) return; - out.println(format("*** testBasic: version: %s, async: %s, useSSL: %s, " + + out.println(format("*** testBasic: client: %s, server: %s, async: %s, useSSL: %s, " + "authScheme: %s, authType: %s, expectContinue: %s preemptive: %s***", - version, async, useSSL, authScheme, authType, expectContinue, preemptive)); + clientVersion, serverVersion, async, useSSL, authScheme, authType, + expectContinue, preemptive)); - DigestEchoServer server = EchoServers.of(useSSL ? "https" : "http", authType, authScheme); - URI uri = DigestEchoServer.uri(useSSL ? "https" : "http", server.getServerAddress(), "/foo/"); + DigestEchoServer server = EchoServers.of(serverVersion, + useSSL ? "https" : "http", authType, authScheme); + URI uri = DigestEchoServer.uri(useSSL ? "https" : "http", + server.getServerAddress(), "/foo/"); HttpClient client = newHttpClient(server); HttpResponse r; @@ -344,7 +388,7 @@ assert lines.size() == i + 1; String body = lines.stream().collect(Collectors.joining("\r\n")); HttpRequest.BodyPublisher reqBody = HttpRequest.BodyPublisher.fromString(body); - HttpRequest.Builder builder = HttpRequest.newBuilder(uri).version(version) + HttpRequest.Builder builder = HttpRequest.newBuilder(uri).version(clientVersion) .POST(reqBody).expectContinue(expectContinue); boolean isTunnel = isProxy(authType) && useSSL; if (addHeaders) { @@ -396,7 +440,9 @@ if (addHeaders && !preemptive && (i==0 || isSchemeDisabled())) { assert resp.statusCode() == 401 || resp.statusCode() == 407; - request = HttpRequest.newBuilder(uri).version(version) + System.out.println(String.format("%s received: adding header %s: %s", + resp.statusCode(), authorizationKey(authType), auth)); + request = HttpRequest.newBuilder(uri).version(clientVersion) .POST(reqBody).header(authorizationKey(authType), auth).build(); if (async) { resp = client.sendAsync(request, asLines()).join(); @@ -419,8 +465,8 @@ System.out.println("Scheme enabled for [" + authType + ", " + authScheme + ", " + (useSSL ? "HTTPS" : "HTTP") - + "]: Expecting 200"); - assert resp.statusCode() == 200; + + "]: Expecting 200, response is: " + resp); + assert resp.statusCode() == 200 : "200 expected, received " + resp; respLines = resp.body().collect(Collectors.toList()); } } finally { @@ -451,15 +497,19 @@ final static AtomicLong digests = new AtomicLong(); final static AtomicLong digestCount = new AtomicLong(); // @Test - void testDigest(HttpClient.Version version, boolean async, boolean expectContinue) + void testDigest(Version clientVersion, Version serverVersion, + boolean async, boolean expectContinue) throws Exception { - out.println(format("*** testDigest: version: %s, async: %s, useSSL: %s, " + + out.println(format("*** testDigest: client: %s, server: %s, async: %s, useSSL: %s, " + "authScheme: %s, authType: %s, expectContinue: %s ***", - version, async, useSSL, authScheme, authType, expectContinue)); - DigestEchoServer server = EchoServers.of(useSSL ? "https" : "http", authType, authScheme); + clientVersion, serverVersion, async, useSSL, + authScheme, authType, expectContinue)); + DigestEchoServer server = EchoServers.of(serverVersion, + useSSL ? "https" : "http", authType, authScheme); - URI uri = DigestEchoServer.uri(useSSL ? "https" : "http", server.getServerAddress(), "/foo/"); + URI uri = DigestEchoServer.uri(useSSL ? "https" : "http", + server.getServerAddress(), "/foo/"); HttpClient client = newHttpClient(server); HttpResponse r; @@ -476,7 +526,7 @@ String body = lines.stream().collect(Collectors.joining("\r\n")); HttpRequest.BodyPublisher reqBody = HttpRequest.BodyPublisher.fromString(body); HttpRequest.Builder reqBuilder = HttpRequest - .newBuilder(uri).version(version).POST(reqBody) + .newBuilder(uri).version(clientVersion).POST(reqBody) .expectContinue(expectContinue); boolean isTunnel = isProxy(authType) && useSSL; @@ -530,7 +580,7 @@ .create(authenticate.substring("Digest ".length())); String auth = digestResponse(uri, digestMethod, challenge, cnonceStr); try { - request = HttpRequest.newBuilder(uri).version(version) + request = HttpRequest.newBuilder(uri).version(clientVersion) .POST(reqBody).header(authorizationKey(authType), auth).build(); } catch (IllegalArgumentException x) { throw x; @@ -560,9 +610,9 @@ } } finally { long stop = System.nanoTime(); - synchronized (basicCount) { - long n = basicCount.getAndIncrement(); - basics.set((basics.get() * n + (stop - start)) / (n + 1)); + synchronized (digestCount) { + long n = digestCount.getAndIncrement(); + digests.set((digests.get() * n + (stop - start)) / (n + 1)); } } if (!lines.equals(respLines)) { diff -r ffaea9a1eed5 -r 66a9c3185028 test/jdk/java/net/httpclient/DigestEchoClientSSL.java --- a/test/jdk/java/net/httpclient/DigestEchoClientSSL.java Mon Feb 05 12:32:20 2018 +0000 +++ b/test/jdk/java/net/httpclient/DigestEchoClientSSL.java Mon Feb 05 15:51:09 2018 +0000 @@ -26,9 +26,13 @@ * @bug 8087112 * @summary this test verifies that a client may provides authorization * headers directly when connecting with a server over SSL. - * @library /lib/testlibrary + * @library /lib/testlibrary http2/server * @build jdk.testlibrary.SimpleSSLContext DigestEchoServer DigestEchoClient DigestEchoClientSSL - * @modules jdk.incubator.httpclient + * @modules jdk.incubator.httpclient/jdk.incubator.http.internal.common + * jdk.incubator.httpclient/jdk.incubator.http.internal.frame + * jdk.incubator.httpclient/jdk.incubator.http.internal.hpack + * java.logging + * java.base/sun.net.www.http * java.base/sun.net.www * java.base/sun.net * @run main/othervm DigestEchoClientSSL SSL diff -r ffaea9a1eed5 -r 66a9c3185028 test/jdk/java/net/httpclient/DigestEchoServer.java --- a/test/jdk/java/net/httpclient/DigestEchoServer.java Mon Feb 05 12:32:20 2018 +0000 +++ b/test/jdk/java/net/httpclient/DigestEchoServer.java Mon Feb 05 15:51:09 2018 +0000 @@ -71,6 +71,7 @@ import java.util.stream.Stream; import javax.net.ssl.SSLContext; import sun.net.www.HeaderParser; +import jdk.incubator.http.HttpClient.Version; /** * A simple HTTP server that supports Basic or Digest authentication. @@ -79,7 +80,7 @@ * a test implementation implemented only for tests purposes. * @author danielfuchs */ -public class DigestEchoServer { +public class DigestEchoServer implements HttpServerAdapters { public static final boolean DEBUG = Boolean.parseBoolean(System.getProperty("test.debug", "false")); @@ -141,12 +142,16 @@ } - final HttpServer serverImpl; // this server endpoint - final DigestEchoServer redirect; // the target server where to redirect 3xx - final HttpHandler delegate; // unused + final HttpTestServer serverImpl; // this server endpoint + final DigestEchoServer redirect; // the target server where to redirect 3xx + final HttpTestHandler delegate; // unused + final String key; - private DigestEchoServer(HttpServer server, DigestEchoServer target, - HttpHandler delegate) { + private DigestEchoServer(String key, + HttpTestServer server, + DigestEchoServer target, + HttpTestHandler delegate) { + this.key = key; this.serverImpl = server; this.redirect = target; this.delegate = delegate; @@ -155,7 +160,8 @@ public static void main(String[] args) throws IOException { - DigestEchoServer server = create(DEFAULT_PROTOCOL_TYPE, + DigestEchoServer server = create(Version.HTTP_1_1, + DEFAULT_PROTOCOL_TYPE, DEFAULT_HTTP_AUTH_TYPE, AUTHENTICATOR, DEFAULT_SCHEME_TYPE); @@ -169,56 +175,61 @@ } } - private static String toString(Headers headers) { + private static String toString(HttpTestHeaders headers) { return headers.entrySet().stream() .map((e) -> e.getKey() + ": " + e.getValue()) .collect(Collectors.joining("\n")); } - public static DigestEchoServer create(String protocol, + public static DigestEchoServer create(Version version, + String protocol, HttpAuthType authType, HttpAuthSchemeType schemeType) throws IOException { - return create(protocol, authType, AUTHENTICATOR, schemeType); + return create(version, protocol, authType, AUTHENTICATOR, schemeType); } - public static DigestEchoServer create(String protocol, + public static DigestEchoServer create(Version version, + String protocol, HttpAuthType authType, HttpTestAuthenticator auth, HttpAuthSchemeType schemeType) throws IOException { - return create(protocol, authType, auth, schemeType, null); + return create(version, protocol, authType, auth, schemeType, null); } - public static DigestEchoServer create(String protocol, + public static DigestEchoServer create(Version version, + String protocol, HttpAuthType authType, HttpTestAuthenticator auth, HttpAuthSchemeType schemeType, - HttpHandler delegate) + HttpTestHandler delegate) throws IOException { Objects.requireNonNull(authType); Objects.requireNonNull(auth); switch(authType) { // A server that performs Server Digest authentication. - case SERVER: return createServer(protocol, authType, auth, + case SERVER: return createServer(version, protocol, authType, auth, schemeType, delegate, "/"); // A server that pretends to be a Proxy and performs // Proxy Digest authentication. If protocol is HTTPS, // then this will create a HttpsProxyTunnel that will // handle the CONNECT request for tunneling. - case PROXY: return createProxy(protocol, authType, auth, + case PROXY: return createProxy(version, protocol, authType, auth, schemeType, delegate, "/"); // A server that sends 307 redirect to a server that performs // Digest authentication. // Note: 301 doesn't work here because it transforms POST into GET. - case SERVER307: return createServerAndRedirect(protocol, + case SERVER307: return createServerAndRedirect(version, + protocol, HttpAuthType.SERVER, auth, schemeType, delegate, 307); // A server that sends 305 redirect to a proxy that performs // Digest authentication. // Note: this is not correctly stubbed/implemented in this test. - case PROXY305: return createServerAndRedirect(protocol, + case PROXY305: return createServerAndRedirect(version, + protocol, HttpAuthType.PROXY, auth, schemeType, delegate, 305); @@ -244,8 +255,8 @@ try { for (int i = 1; i <= max; i++) { B bindable = createBindable(); - SocketAddress address = getAddress(bindable); - String key = address.toString(); + InetSocketAddress address = getAddress(bindable); + String key = "127.0.0.1:" + address.getPort(); if (addresses.addIfAbsent(key)) { System.out.println("Socket bound to: " + key + " after " + i + " attempt(s)"); @@ -270,7 +281,7 @@ protected abstract B createBindable() throws IOException; - protected abstract SocketAddress getAddress(B bindable); + protected abstract InetSocketAddress getAddress(B bindable); protected abstract void close(B bindable) throws IOException; } @@ -288,12 +299,12 @@ @Override protected ServerSocket createBindable() throws IOException { - return new ServerSocket(0, 0, InetAddress.getByName("127.0.0.1")); + return new ServerSocket(0); } @Override - protected SocketAddress getAddress(ServerSocket socket) { - return socket.getLocalSocketAddress(); + protected InetSocketAddress getAddress(ServerSocket socket) { + return new InetSocketAddress(socket.getInetAddress(), socket.getLocalPort()); } @Override @@ -303,19 +314,19 @@ } /* - * Used to create HttpServer for a NTLMTestServer. + * Used to create HttpServer */ - private static abstract class WebServerFactory + private static abstract class H1ServerFactory extends SocketBindableFactory { @Override protected S createBindable() throws IOException { S server = newHttpServer(); - server.bind(new InetSocketAddress("127.0.0.1", 0), 0); + server.bind(new InetSocketAddress( 0), 0); return server; } @Override - protected SocketAddress getAddress(S server) { + protected InetSocketAddress getAddress(S server) { return server.getAddress(); } @@ -330,8 +341,68 @@ protected abstract S newHttpServer() throws IOException; } - private static final class HttpServerFactory extends WebServerFactory { - private static final HttpServerFactory instance = new HttpServerFactory(); + /* + * Used to create Http2TestServer + */ + private static abstract class H2ServerFactory + extends SocketBindableFactory { + @Override + protected S createBindable() throws IOException { + final S server; + try { + server = newHttpServer(); + } catch (IOException io) { + throw io; + } catch (Exception x) { + throw new IOException(x); + } + return server; + } + + @Override + protected InetSocketAddress getAddress(S server) { + return server.getAddress(); + } + + @Override + protected void close(S server) throws IOException { + server.stop(); + } + + /* + * Returns a HttpServer or a HttpsServer in different subclasses. + */ + protected abstract S newHttpServer() throws Exception; + } + + private static final class Http2ServerFactory extends H2ServerFactory { + private static final Http2ServerFactory instance = new Http2ServerFactory(); + + static Http2TestServer create() throws IOException { + return instance.createInternal(); + } + + @Override + protected Http2TestServer newHttpServer() throws Exception { + return new Http2TestServer("127.0.0.1", false, 0); + } + } + + private static final class Https2ServerFactory extends H2ServerFactory { + private static final Https2ServerFactory instance = new Https2ServerFactory(); + + static Http2TestServer create() throws IOException { + return instance.createInternal(); + } + + @Override + protected Http2TestServer newHttpServer() throws Exception { + return new Http2TestServer("127.0.0.1", true, 0); + } + } + + private static final class Http1ServerFactory extends H1ServerFactory { + private static final Http1ServerFactory instance = new Http1ServerFactory(); static HttpServer create() throws IOException { return instance.createInternal(); @@ -343,8 +414,8 @@ } } - private static final class HttpsServerFactory extends WebServerFactory { - private static final HttpsServerFactory instance = new HttpsServerFactory(); + private static final class Https1ServerFactory extends H1ServerFactory { + private static final Https1ServerFactory instance = new Https1ServerFactory(); static HttpsServer create() throws IOException { return instance.createInternal(); @@ -356,12 +427,37 @@ } } - static HttpServer createHttpServer(String protocol) throws IOException { + static Http2TestServer createHttp2Server(String protocol) throws IOException { + final Http2TestServer server; + if ("http".equalsIgnoreCase(protocol)) { + server = Http2ServerFactory.create(); + } else if ("https".equalsIgnoreCase(protocol)) { + server = Https2ServerFactory.create(); + } else { + throw new InternalError("unsupported protocol: " + protocol); + } + return server; + } + + static HttpTestServer createHttpServer(Version version, String protocol) + throws IOException + { + switch(version) { + case HTTP_1_1: + return HttpTestServer.of(createHttp1Server(protocol)); + case HTTP_2: + return HttpTestServer.of(createHttp2Server(protocol)); + default: + throw new InternalError("Unexpected version: " + version); + } + } + + static HttpServer createHttp1Server(String protocol) throws IOException { final HttpServer server; if ("http".equalsIgnoreCase(protocol)) { - server = HttpServerFactory.create(); + server = Http1ServerFactory.create(); } else if ("https".equalsIgnoreCase(protocol)) { - server = configure(HttpsServerFactory.create()); + server = configure(Https1ServerFactory.create()); } else { throw new InternalError("unsupported protocol: " + protocol); } @@ -379,7 +475,7 @@ } - static void setContextAuthenticator(HttpContext ctxt, + static void setContextAuthenticator(HttpTestContext ctxt, HttpTestAuthenticator auth) { final String realm = auth.getRealm(); com.sun.net.httpserver.Authenticator authenticator = @@ -393,44 +489,59 @@ ctxt.setAuthenticator(authenticator); } - public static DigestEchoServer createServer(String protocol, + public static DigestEchoServer createServer(Version version, + String protocol, HttpAuthType authType, HttpTestAuthenticator auth, HttpAuthSchemeType schemeType, - HttpHandler delegate, + HttpTestHandler delegate, String path) throws IOException { Objects.requireNonNull(authType); Objects.requireNonNull(auth); - HttpServer impl = createHttpServer(protocol); - final DigestEchoServer server = new DigestEchoServer(impl, null, delegate); - final HttpHandler hh = server.createHandler(schemeType, auth, authType, false); - HttpContext ctxt = impl.createContext(path, hh); - server.configureAuthentication(ctxt, schemeType, auth, authType); + HttpTestServer impl = createHttpServer(version, protocol); + String key = String.format("DigestEchoServer[PID=%s,PORT=%s]:%s:%s:%s:%s", + ProcessHandle.current().pid(), + impl.getAddress().getPort(), + version, protocol, authType, schemeType); + final DigestEchoServer server = new DigestEchoServer(key, impl, null, delegate); + final HttpTestHandler handler = + server.createHandler(schemeType, auth, authType, false); + HttpTestContext context = impl.addHandler(handler, path); + server.configureAuthentication(context, schemeType, auth, authType); impl.start(); return server; } - public static DigestEchoServer createProxy(String protocol, + public static DigestEchoServer createProxy(Version version, + String protocol, HttpAuthType authType, HttpTestAuthenticator auth, HttpAuthSchemeType schemeType, - HttpHandler delegate, + HttpTestHandler delegate, String path) throws IOException { Objects.requireNonNull(authType); Objects.requireNonNull(auth); - HttpServer impl = createHttpServer(protocol); + if (version == Version.HTTP_2 && protocol.equalsIgnoreCase("http")) { + System.out.println("WARNING: can't use HTTP/1.1 proxy with unsecure HTTP/2 server"); + version = Version.HTTP_1_1; + } + HttpTestServer impl = createHttpServer(version, protocol); + String key = String.format("DigestEchoServer[PID=%s,PORT=%s]:%s:%s:%s:%s", + ProcessHandle.current().pid(), + impl.getAddress().getPort(), + version, protocol, authType, schemeType); final DigestEchoServer server = "https".equalsIgnoreCase(protocol) - ? new HttpsProxyTunnel(impl, null, delegate) - : new DigestEchoServer(impl, null, delegate); + ? new HttpsProxyTunnel(key, impl, null, delegate) + : new DigestEchoServer(key, impl, null, delegate); - final HttpHandler hh = server.createHandler(HttpAuthSchemeType.NONE, - null, HttpAuthType.SERVER, + final HttpTestHandler hh = server.createHandler(HttpAuthSchemeType.NONE, + null, HttpAuthType.SERVER, server instanceof HttpsProxyTunnel); - HttpContext ctxt = impl.createContext(path, hh); + HttpTestContext ctxt = impl.addHandler(hh, path); server.configureAuthentication(ctxt, schemeType, auth, authType); impl.start(); @@ -438,11 +549,12 @@ } public static DigestEchoServer createServerAndRedirect( + Version version, String protocol, HttpAuthType targetAuthType, HttpTestAuthenticator auth, HttpAuthSchemeType schemeType, - HttpHandler targetDelegate, + HttpTestHandler targetDelegate, int code300) throws IOException { Objects.requireNonNull(targetAuthType); @@ -456,42 +568,53 @@ : protocol; DigestEchoServer redirectTarget = (targetAuthType == HttpAuthType.PROXY) - ? createProxy(protocol, targetAuthType, + ? createProxy(version, protocol, targetAuthType, auth, schemeType, targetDelegate, "/") - : createServer(targetProtocol, targetAuthType, + : createServer(version, targetProtocol, targetAuthType, auth, schemeType, targetDelegate, "/"); - HttpServer impl = createHttpServer(protocol); + HttpTestServer impl = createHttpServer(version, protocol); + String key = String.format("RedirectingServer[PID=%s,PORT=%s]:%s:%s:%s:%s", + ProcessHandle.current().pid(), + impl.getAddress().getPort(), + version, protocol, + HttpAuthType.SERVER, code300) + + "->" + redirectTarget.key; final DigestEchoServer redirectingServer = - new DigestEchoServer(impl, redirectTarget, null); + new DigestEchoServer(key, impl, redirectTarget, null); InetSocketAddress redirectAddr = redirectTarget.getAddress(); URL locationURL = url(targetProtocol, redirectAddr, "/"); - final HttpHandler hh = redirectingServer.create300Handler(locationURL, + final HttpTestHandler hh = redirectingServer.create300Handler(key, locationURL, HttpAuthType.SERVER, code300); - impl.createContext("/", hh); + impl.addHandler(hh,"/"); impl.start(); return redirectingServer; } public InetSocketAddress getAddress() { - return serverImpl.getAddress(); + return new InetSocketAddress("127.0.0.1", + serverImpl.getAddress().getPort()); } public InetSocketAddress getServerAddress() { - return serverImpl.getAddress(); + return new InetSocketAddress("127.0.0.1", + serverImpl.getAddress().getPort()); } public InetSocketAddress getProxyAddress() { - return serverImpl.getAddress(); + return new InetSocketAddress("127.0.0.1", + serverImpl.getAddress().getPort()); } + public Version getServerVersion() { return serverImpl.getVersion(); } + public void stop() { - serverImpl.stop(0); + serverImpl.stop(); if (redirect != null) { redirect.stop(); } } - protected void writeResponse(HttpExchange he) throws IOException { + protected void writeResponse(HttpTestExchange he) throws IOException { if (delegate == null) { he.sendResponseHeaders(HttpURLConnection.HTTP_OK, 0); he.getResponseBody().write(he.getRequestBody().readAllBytes()); @@ -500,25 +623,25 @@ } } - private HttpHandler createHandler(HttpAuthSchemeType schemeType, + private HttpTestHandler createHandler(HttpAuthSchemeType schemeType, HttpTestAuthenticator auth, HttpAuthType authType, boolean tunelled) { - return new HttpNoAuthHandler(authType, tunelled); + return new HttpNoAuthHandler(key, authType, tunelled); } - void configureAuthentication(HttpContext ctxt, - HttpAuthSchemeType schemeType, - HttpTestAuthenticator auth, - HttpAuthType authType) { + void configureAuthentication(HttpTestContext ctxt, + HttpAuthSchemeType schemeType, + HttpTestAuthenticator auth, + HttpAuthType authType) { switch(schemeType) { case DIGEST: // DIGEST authentication is handled by the handler. - ctxt.getFilters().add(new HttpDigestFilter(auth, authType)); + ctxt.addFilter(new HttpDigestFilter(key, auth, authType)); break; case BASIC: // BASIC authentication is handled by the filter. - ctxt.getFilters().add(new HttpBasicFilter(auth, authType)); + ctxt.addFilter(new HttpBasicFilter(key, auth, authType)); break; case BASICSERVER: switch(authType) { @@ -526,35 +649,41 @@ // HttpServer can't support Proxy-type authentication // => we do as if BASIC had been specified, and we will // handle authentication in the handler. - ctxt.getFilters().add(new HttpBasicFilter(auth, authType)); + ctxt.addFilter(new HttpBasicFilter(key, auth, authType)); break; case SERVER: case SERVER307: - // Basic authentication is handled by HttpServer - // directly => the filter should not perform - // authentication again. - setContextAuthenticator(ctxt, auth); - ctxt.getFilters().add(new HttpNoAuthFilter(authType)); + if (ctxt.getVersion() == Version.HTTP_1_1) { + // Basic authentication is handled by HttpServer + // directly => the filter should not perform + // authentication again. + setContextAuthenticator(ctxt, auth); + ctxt.addFilter(new HttpNoAuthFilter(key, authType)); + } else { + ctxt.addFilter(new HttpBasicFilter(key, auth, authType)); + } break; default: - throw new InternalError("Invalid combination scheme=" + throw new InternalError(key + ": Invalid combination scheme=" + schemeType + " authType=" + authType); } case NONE: // No authentication at all. - ctxt.getFilters().add(new HttpNoAuthFilter(authType)); + ctxt.addFilter(new HttpNoAuthFilter(key, authType)); break; default: - throw new InternalError("No such scheme: " + schemeType); + throw new InternalError(key + ": No such scheme: " + schemeType); } } - private HttpHandler create300Handler(URL proxyURL, - HttpAuthType type, int code300) throws MalformedURLException { - return new Http3xxHandler(proxyURL, type, code300); + private HttpTestHandler create300Handler(String key, URL proxyURL, + HttpAuthType type, int code300) + throws MalformedURLException + { + return new Http3xxHandler(key, proxyURL, type, code300); } // Abstract HTTP filter class. - private abstract static class AbstractHttpFilter extends Filter { + private abstract static class AbstractHttpFilter extends HttpTestFilter { final HttpAuthType authType; final String type; @@ -586,9 +715,9 @@ return authType == HttpAuthType.PROXY ? "Proxy-Connection" : "Connection"; } - protected abstract boolean isAuthentified(HttpExchange he) throws IOException; - protected abstract void requestAuthentication(HttpExchange he) throws IOException; - protected void accept(HttpExchange he, Chain chain) throws IOException { + protected abstract boolean isAuthentified(HttpTestExchange he) throws IOException; + protected abstract void requestAuthentication(HttpTestExchange he) throws IOException; + protected void accept(HttpTestExchange he, HttpChain chain) throws IOException { chain.doFilter(he); } @@ -597,7 +726,7 @@ return "Filter for " + type; } @Override - public void doFilter(HttpExchange he, Chain chain) throws IOException { + public void doFilter(HttpTestExchange he, HttpChain chain) throws IOException { try { System.out.println(type + ": Got " + he.getRequestMethod() + ": " + he.getRequestURI() @@ -771,20 +900,25 @@ } - private class HttpNoAuthFilter extends AbstractHttpFilter { + private static class HttpNoAuthFilter extends AbstractHttpFilter { - public HttpNoAuthFilter(HttpAuthType authType) { - super(authType, authType == HttpAuthType.SERVER - ? "NoAuth Server" : "NoAuth Proxy"); + static String type(String key, HttpAuthType authType) { + String type = authType == HttpAuthType.SERVER + ? "NoAuth Server Filter" : "NoAuth Proxy Filter"; + return "["+type+"]:"+key; + } + + public HttpNoAuthFilter(String key, HttpAuthType authType) { + super(authType, type(key, authType)); } @Override - protected boolean isAuthentified(HttpExchange he) throws IOException { + protected boolean isAuthentified(HttpTestExchange he) throws IOException { return true; } @Override - protected void requestAuthentication(HttpExchange he) throws IOException { + protected void requestAuthentication(HttpTestExchange he) throws IOException { throw new InternalError("Should not com here"); } @@ -796,26 +930,32 @@ } // An HTTP Filter that performs Basic authentication - private class HttpBasicFilter extends AbstractHttpFilter { + private static class HttpBasicFilter extends AbstractHttpFilter { + + static String type(String key, HttpAuthType authType) { + String type = authType == HttpAuthType.SERVER + ? "Basic Server Filter" : "Basic Proxy Filter"; + return "["+type+"]:"+key; + } private final HttpTestAuthenticator auth; - public HttpBasicFilter(HttpTestAuthenticator auth, HttpAuthType authType) { - super(authType, authType == HttpAuthType.SERVER - ? "Basic Server" : "Basic Proxy"); + public HttpBasicFilter(String key, HttpTestAuthenticator auth, + HttpAuthType authType) { + super(authType, type(key, authType)); this.auth = auth; } @Override - protected void requestAuthentication(HttpExchange he) + protected void requestAuthentication(HttpTestExchange he) throws IOException { - he.getResponseHeaders().add(getAuthenticate(), + he.getResponseHeaders().addHeader(getAuthenticate(), "Basic realm=\"" + auth.getRealm() + "\""); System.out.println(type + ": Requesting Basic Authentication " - + he.getResponseHeaders().getFirst(getAuthenticate())); + + he.getResponseHeaders().firstValue(getAuthenticate())); } @Override - protected boolean isAuthentified(HttpExchange he) { + protected boolean isAuthentified(HttpTestExchange he) { if (he.getRequestHeaders().containsKey(getAuthorization())) { List authorization = he.getRequestHeaders().get(getAuthorization()); @@ -854,7 +994,7 @@ @Override public String description() { - return "Filter for " + type; + return "Filter for BASIC authentication: " + type; } } @@ -863,7 +1003,13 @@ // An HTTP Filter that performs Digest authentication // WARNING: This is not a full fledged implementation of DIGEST. // It does contain bugs and inaccuracy. - private class HttpDigestFilter extends AbstractHttpFilter { + private static class HttpDigestFilter extends AbstractHttpFilter { + + static String type(String key, HttpAuthType authType) { + String type = authType == HttpAuthType.SERVER + ? "Digest Server Filter" : "Digest Proxy Filter"; + return "["+type+"]:"+key; + } // This is a very basic DIGEST - used only for the purpose of testing // the client implementation. Therefore we can get away with never @@ -872,9 +1018,8 @@ private final HttpTestAuthenticator auth; private final byte[] nonce; private final String ns; - public HttpDigestFilter(HttpTestAuthenticator auth, HttpAuthType authType) { - super(authType, authType == HttpAuthType.SERVER - ? "Digest Server" : "Digest Proxy"); + public HttpDigestFilter(String key, HttpTestAuthenticator auth, HttpAuthType authType) { + super(authType, type(key, authType)); this.auth = auth; nonce = new byte[16]; new Random(Instant.now().toEpochMilli()).nextBytes(nonce); @@ -882,18 +1027,20 @@ } @Override - protected void requestAuthentication(HttpExchange he) + protected void requestAuthentication(HttpTestExchange he) throws IOException { - he.getResponseHeaders().add(getAuthenticate(), + he.getResponseHeaders().addHeader(getAuthenticate(), "Digest realm=\"" + auth.getRealm() + "\"," + "\r\n qop=\"auth\"," + "\r\n nonce=\"" + ns +"\""); System.out.println(type + ": Requesting Digest Authentication " - + he.getResponseHeaders().getFirst(getAuthenticate())); + + he.getResponseHeaders() + .firstValue(getAuthenticate()) + .orElse("null")); } @Override - protected boolean isAuthentified(HttpExchange he) { + protected boolean isAuthentified(HttpTestExchange he) { if (he.getRequestHeaders().containsKey(getAuthorization())) { List authorization = he.getRequestHeaders().get(getAuthorization()); for (String a : authorization) { @@ -967,12 +1114,12 @@ @Override public String description() { - return "Filter for DIGEST authentication"; + return "Filter for DIGEST authentication: " + type; } } // Abstract HTTP handler class. - private abstract static class AbstractHttpHandler implements HttpHandler { + private abstract static class AbstractHttpHandler implements HttpTestHandler { final HttpAuthType authType; final String type; @@ -986,7 +1133,7 @@ } @Override - public void handle(HttpExchange he) throws IOException { + public void handle(HttpTestExchange he) throws IOException { try { sendResponse(he); } catch (RuntimeException | Error | IOException t) { @@ -999,22 +1146,28 @@ } } - protected abstract void sendResponse(HttpExchange he) throws IOException; + protected abstract void sendResponse(HttpTestExchange he) throws IOException; + + } + static String stype(String type, String key, HttpAuthType authType, boolean tunnelled) { + type = type + (authType == HttpAuthType.SERVER + ? " Server" : " Proxy") + + (tunnelled ? " Tunnelled" : ""); + return "["+type+"]:"+key; } private class HttpNoAuthHandler extends AbstractHttpHandler { // true if this server is behind a proxy tunnel. final boolean tunnelled; - public HttpNoAuthHandler(HttpAuthType authType, boolean tunnelled) { - super(authType, authType == HttpAuthType.SERVER - ? "NoAuth Server" : "NoAuth Proxy"); + public HttpNoAuthHandler(String key, HttpAuthType authType, boolean tunnelled) { + super(authType, stype("NoAuth", key, authType, tunnelled)); this.tunnelled = tunnelled; } @Override - protected void sendResponse(HttpExchange he) throws IOException { + protected void sendResponse(HttpTestExchange he) throws IOException { if (DEBUG) { System.out.println(type + ": headers are: " + DigestEchoServer.toString(he.getRequestHeaders())); @@ -1045,8 +1198,8 @@ private final URL redirectTargetURL; private final int code3XX; - public Http3xxHandler(URL proxyURL, HttpAuthType authType, int code300) { - super(authType, "Server" + code300); + public Http3xxHandler(String key, URL proxyURL, HttpAuthType authType, int code300) { + super(authType, stype("Server" + code300, key, authType, false)); this.redirectTargetURL = proxyURL; this.code3XX = code300; } @@ -1056,14 +1209,14 @@ } @Override - public void sendResponse(HttpExchange he) throws IOException { + public void sendResponse(HttpTestExchange he) throws IOException { System.out.println(type + ": Got " + he.getRequestMethod() + ": " + he.getRequestURI() + "\n" + DigestEchoServer.toString(he.getRequestHeaders())); System.out.println(type + ": Redirecting to " + (authType == HttpAuthType.PROXY305 ? "proxy" : "server")); - he.getResponseHeaders().add(getLocation(), + he.getResponseHeaders().addHeader(getLocation(), redirectTargetURL.toExternalForm().toString()); he.sendResponseHeaders(get3XX(), 0); System.out.println(type + ": Sent back " + get3XX() + " " @@ -1096,8 +1249,10 @@ final HttpTestAuthenticator authenticator; private final byte[] nonce; private final String ns; + private final String key; - ProxyAuthorization(HttpAuthSchemeType schemeType, HttpTestAuthenticator auth) { + ProxyAuthorization(String key, HttpAuthSchemeType schemeType, HttpTestAuthenticator auth) { + this.key = key; this.schemeType = schemeType; this.authenticator = auth; nonce = new byte[16]; @@ -1129,7 +1284,7 @@ return "Proxy-Authenticate: BASIC " + "realm=\"" + authenticator.getRealm() +"\""; } - System.out.println(now() + " Proxy basic authentication success"); + System.out.println(now() + key + " Proxy basic authentication success"); return null; } @@ -1165,7 +1320,7 @@ boolean validate(String reqMethod, DigestResponse dg) { - String type = now() + this.getClass().getSimpleName(); + String type = now() + this.getClass().getSimpleName() + ":" + key; if (!"MD5".equalsIgnoreCase(dg.getAlgorithm("MD5"))) { System.out.println(type + ": Unsupported algorithm " + dg.algorithm); @@ -1268,13 +1423,19 @@ = new CopyOnWriteArrayList<>(); volatile ProxyAuthorization authorization; volatile boolean stopped; - public HttpsProxyTunnel(HttpServer server, DigestEchoServer target, - HttpHandler delegate) + public HttpsProxyTunnel(String key, HttpTestServer server, DigestEchoServer target, + HttpTestHandler delegate) throws IOException { - super(server, target, delegate); + this(key, server, target, delegate, ServerSocketFactory.create()); + } + private HttpsProxyTunnel(String key, HttpTestServer server, DigestEchoServer target, + HttpTestHandler delegate, ServerSocket ss) + throws IOException { + super("HttpsProxyTunnel:" + ss.getLocalPort() + ":" + key, + server, target, delegate); System.out.flush(); System.err.println("WARNING: HttpsProxyTunnel is an experimental test class"); - ss = ServerSocketFactory.create(); + this.ss = ss; start(); } @@ -1297,12 +1458,12 @@ @Override - void configureAuthentication(HttpContext ctxt, + void configureAuthentication(HttpTestContext ctxt, HttpAuthSchemeType schemeType, HttpTestAuthenticator auth, HttpAuthType authType) { if (authType == HttpAuthType.PROXY || authType == HttpAuthType.PROXY305) { - authorization = new ProxyAuthorization(schemeType, auth); + authorization = new ProxyAuthorization(key, schemeType, auth); } else { super.configureAuthentication(ctxt, schemeType, auth, authType); } @@ -1352,7 +1513,8 @@ return getAddress(); } public InetSocketAddress getServerAddress() { - return serverImpl.getAddress(); + return new InetSocketAddress("127.0.0.1", + serverImpl.getAddress().getPort()); } diff -r ffaea9a1eed5 -r 66a9c3185028 test/jdk/java/net/httpclient/HttpServerAdapters.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jdk/java/net/httpclient/HttpServerAdapters.java Mon Feb 05 15:51:09 2018 +0000 @@ -0,0 +1,508 @@ +/* + * 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. + */ + +import com.sun.net.httpserver.Filter; +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 jdk.incubator.http.HttpClient.Version; +import jdk.incubator.http.internal.common.HttpHeadersImpl; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintStream; +import java.io.UncheckedIOException; +import java.math.BigInteger; +import java.net.InetSocketAddress; +import java.net.URI; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * Defines an adaptation layers so that a test server handlers and filters + * can be implemented independently of the underlying server version. + *

+ * For instance: + *

{@code
+ *
+ *  URI http1URI, http2URI;
+ *
+ *  InetSocketAddress sa = new InetSocketAddress("localhost", 0);
+ *  HttpTestServer server1 = HttpTestServer.of(HttpServer.create(sa, 0));
+ *  HttpTestContext context = server.addHandler(new HttpTestEchoHandler(), "/http1/echo");
+ *  http2URI = "http://127.0.0.1:" + server1.getAddress().getPort() + "/http1/echo";
+ *
+ *  Http2TestServer http2TestServer = new Http2TestServer("127.0.0.1", false, 0);
+ *  HttpTestServer server2 = HttpTestServer.of(http2TestServer);
+ *  server2.addHandler(new HttpTestEchoHandler(), "/http2/echo");
+ *  http1URI = "http://127.0.0.1:" + server2.getAddress().getPort() + "/http2/echo";
+ *
+ *  }
+ */ +public interface HttpServerAdapters { + + static void uncheckedWrite(ByteArrayOutputStream baos, byte[] ba) { + try { + baos.write(ba); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + static void printBytes(PrintStream out, String prefix, byte[] bytes) { + int padding = 4 + 4 - (bytes.length % 4); + padding = padding > 4 ? padding - 4 : 4; + byte[] bigbytes = new byte[bytes.length + padding]; + System.arraycopy(bytes, 0, bigbytes, padding, bytes.length); + out.println(prefix + bytes.length + " " + + new BigInteger(bigbytes).toString(16)); + } + + /** + * A version agnostic adapter class for HTTP Headers. + */ + public static abstract class HttpTestHeaders { + public abstract Optional firstValue(String name); + public abstract void addHeader(String name, String value); + public abstract Set keySet(); + public abstract Set>> entrySet(); + public abstract List get(String name); + public abstract boolean containsKey(String name); + + public static HttpTestHeaders of(Headers headers) { + return new Http1TestHeaders(headers); + } + public static HttpTestHeaders of(HttpHeadersImpl headers) { + return new Http2TestHeaders(headers); + } + + private final static class Http1TestHeaders extends HttpTestHeaders { + private final Headers headers; + Http1TestHeaders(Headers h) { this.headers = h; } + @Override + public Optional firstValue(String name) { + if (headers.containsKey(name)) { + return Optional.ofNullable(headers.getFirst(name)); + } + return Optional.empty(); + } + @Override + public void addHeader(String name, String value) { + headers.add(name, value); + } + + @Override + public Set keySet() { return headers.keySet(); } + @Override + public Set>> entrySet() { + return headers.entrySet(); + } + @Override + public List get(String name) { + return headers.get(name); + } + @Override + public boolean containsKey(String name) { + return headers.containsKey(name); + } + } + private final static class Http2TestHeaders extends HttpTestHeaders { + private final HttpHeadersImpl headers; + Http2TestHeaders(HttpHeadersImpl h) { this.headers = h; } + @Override + public Optional firstValue(String name) { + return headers.firstValue(name); + } + @Override + public void addHeader(String name, String value) { + headers.addHeader(name, value); + } + public Set keySet() { return headers.map().keySet(); } + @Override + public Set>> entrySet() { + return headers.map().entrySet(); + } + @Override + public List get(String name) { + return headers.allValues(name); + } + @Override + public boolean containsKey(String name) { + return headers.firstValue(name).isPresent(); + } + } + } + + /** + * A version agnostic adapter class for HTTP Server Exchange. + */ + public static abstract class HttpTestExchange { + public abstract Version getServerVersion(); + public abstract Version getExchangeVersion(); + public abstract InputStream getRequestBody(); + public abstract OutputStream getResponseBody(); + public abstract HttpTestHeaders getRequestHeaders(); + public abstract HttpTestHeaders getResponseHeaders(); + public abstract void sendResponseHeaders(int code, int contentLength) throws IOException; + public abstract URI getRequestURI(); + public abstract String getRequestMethod(); + public abstract void close(); + + public static HttpTestExchange of(HttpExchange exchange) { + return new Http1TestExchange(exchange); + } + public static HttpTestExchange of(Http2TestExchange exchange) { + return new Http2TestExchangeImpl(exchange); + } + + abstract void doFilter(Filter.Chain chain) throws IOException; + + // implementations... + private static final class Http1TestExchange extends HttpTestExchange { + private final HttpExchange exchange; + Http1TestExchange(HttpExchange exch) { + this.exchange = exch; + } + @Override + public Version getServerVersion() { return Version.HTTP_1_1; } + @Override + public Version getExchangeVersion() { return Version.HTTP_1_1; } + @Override + public InputStream getRequestBody() { + return exchange.getRequestBody(); + } + @Override + public OutputStream getResponseBody() { + return exchange.getResponseBody(); + } + @Override + public HttpTestHeaders getRequestHeaders() { + return HttpTestHeaders.of(exchange.getRequestHeaders()); + } + @Override + public HttpTestHeaders getResponseHeaders() { + return HttpTestHeaders.of(exchange.getResponseHeaders()); + } + @Override + public void sendResponseHeaders(int code, int contentLength) throws IOException { + if (contentLength == 0) contentLength = -1; + if (contentLength < 0) contentLength = 0; + exchange.sendResponseHeaders(code, contentLength); + } + @Override + void doFilter(Filter.Chain chain) throws IOException { + chain.doFilter(exchange); + } + @Override + public void close() { exchange.close(); } + @Override + public URI getRequestURI() { return exchange.getRequestURI(); } + @Override + public String getRequestMethod() { return exchange.getRequestMethod(); } + @Override + public String toString() { + return this.getClass().getSimpleName() + ": " + exchange.toString(); + } + } + private static final class Http2TestExchangeImpl extends HttpTestExchange { + private final Http2TestExchange exchange; + Http2TestExchangeImpl(Http2TestExchange exch) { + this.exchange = exch; + } + @Override + public Version getServerVersion() { return Version.HTTP_2; } + @Override + public Version getExchangeVersion() { return Version.HTTP_2; } + @Override + public InputStream getRequestBody() { + return exchange.getRequestBody(); + } + @Override + public OutputStream getResponseBody() { + return exchange.getResponseBody(); + } + @Override + public HttpTestHeaders getRequestHeaders() { + return HttpTestHeaders.of(exchange.getRequestHeaders()); + } + @Override + public HttpTestHeaders getResponseHeaders() { + return HttpTestHeaders.of(exchange.getResponseHeaders()); + } + @Override + public void sendResponseHeaders(int code, int contentLength) throws IOException { + exchange.sendResponseHeaders(code, contentLength); + } + void doFilter(Filter.Chain filter) throws IOException { + throw new IOException("cannot use HTTP/1.1 filter with HTTP/2 server"); + } + @Override + public void close() { exchange.close();} + @Override + public URI getRequestURI() { return exchange.getRequestURI(); } + @Override + public String getRequestMethod() { return exchange.getRequestMethod(); } + @Override + public String toString() { + return this.getClass().getSimpleName() + ": " + exchange.toString(); + } + } + + } + + + /** + * A version agnostic adapter class for HTTP Server Handlers. + */ + public interface HttpTestHandler { + void handle(HttpTestExchange t) throws IOException; + + default HttpHandler toHttpHandler() { + return (t) -> handle(HttpTestExchange.of(t)); + } + default Http2Handler toHttp2Handler() { + return (t) -> handle(HttpTestExchange.of(t)); + } + } + + public static class HttpTestEchoHandler implements HttpTestHandler { + @Override + public void handle(HttpTestExchange t) throws IOException { + try (InputStream is = t.getRequestBody(); + OutputStream os = t.getResponseBody()) { + byte[] bytes = is.readAllBytes(); + printBytes(System.out,"Bytes: ", bytes); + if (t.getRequestHeaders().firstValue("Content-type").isPresent()) { + t.getResponseHeaders().addHeader("Content-type", + t.getRequestHeaders().firstValue("Content-type").get()); + } + t.sendResponseHeaders(200, bytes.length); + os.write(bytes); + } + } + } + + /** + * A version agnostic adapter class for HTTP Server Filter Chains. + */ + public abstract class HttpChain { + + public abstract void doFilter(HttpTestExchange exchange) throws IOException; + public static HttpChain of(Filter.Chain chain) { + return new Http1Chain(chain); + } + + public static HttpChain of(List filters, HttpTestHandler handler) { + return new Http2Chain(filters, handler); + } + + private static class Http1Chain extends HttpChain { + final Filter.Chain chain; + Http1Chain(Filter.Chain chain) { + this.chain = chain; + } + @Override + public void doFilter(HttpTestExchange exchange) throws IOException{ + exchange.doFilter(chain); + } + } + + private static class Http2Chain extends HttpChain { + ListIterator iter; + HttpTestHandler handler; + Http2Chain(List filters, HttpTestHandler handler) { + this.iter = filters.listIterator(); + this.handler = handler; + } + @Override + public void doFilter(HttpTestExchange exchange) throws IOException { + if (iter.hasNext()) { + iter.next().doFilter(exchange, this); + } else { + handler.handle(exchange); + } + } + } + + } + + /** + * A version agnostic adapter class for HTTP Server Filters. + */ + public abstract class HttpTestFilter { + + public abstract String description(); + + public abstract void doFilter(HttpTestExchange exchange, HttpChain chain) throws IOException; + + public Filter toFilter() { + return new Filter() { + @Override + public void doFilter(HttpExchange exchange, Chain chain) throws IOException { + HttpTestFilter.this.doFilter(HttpTestExchange.of(exchange), HttpChain.of(chain)); + } + @Override + public String description() { + return HttpTestFilter.this.description(); + } + }; + } + } + + /** + * A version agnostic adapter class for HTTP Server Context. + */ + public static abstract class HttpTestContext { + public abstract String getPath(); + public abstract void addFilter(HttpTestFilter filter); + public abstract Version getVersion(); + + // will throw UOE if the server is HTTP/2 + public abstract void setAuthenticator(com.sun.net.httpserver.Authenticator authenticator); + } + + /** + * A version agnostic adapter class for HTTP Servers. + */ + public static abstract class HttpTestServer { + public abstract void start(); + public abstract void stop(); + public abstract HttpTestContext addHandler(HttpTestHandler handler, String root); + public abstract InetSocketAddress getAddress(); + public abstract Version getVersion(); + + public static HttpTestServer of(HttpServer server) { + return new Http1TestServer(server); + } + + public static HttpTestServer of(Http2TestServer server) { + return new Http2TestServerImpl(server); + } + + private static class Http1TestServer extends HttpTestServer { + private final HttpServer impl; + Http1TestServer(HttpServer server) { + this.impl = server; + } + @Override + public void start() { impl.start(); } + @Override + public void stop() { impl.stop(0); } + @Override + public HttpTestContext addHandler(HttpTestHandler handler, String path) { + return new Http1TestContext(impl.createContext(path, handler.toHttpHandler())); + } + @Override + public InetSocketAddress getAddress() { + return new InetSocketAddress("127.0.0.1", + impl.getAddress().getPort()); + } + public Version getVersion() { return Version.HTTP_1_1; } + } + + private static class Http1TestContext extends HttpTestContext { + private final HttpContext context; + Http1TestContext(HttpContext ctxt) { + this.context = ctxt; + } + @Override public String getPath() { + return context.getPath(); + } + @Override + public void addFilter(HttpTestFilter filter) { + context.getFilters().add(filter.toFilter()); + } + @Override + public void setAuthenticator(com.sun.net.httpserver.Authenticator authenticator) { + context.setAuthenticator(authenticator); + } + @Override public Version getVersion() { return Version.HTTP_1_1; } + } + + private static class Http2TestServerImpl extends HttpTestServer { + private final Http2TestServer impl; + Http2TestServerImpl(Http2TestServer server) { + this.impl = server; + } + @Override + public void start() { + System.out.println("Http2TestServerImpl: start"); + impl.start(); + } + @Override + public void stop() { + System.out.println("Http2TestServerImpl: stop"); + impl.stop(); + } + @Override + public HttpTestContext addHandler(HttpTestHandler handler, String path) { + System.out.println("Http2TestServerImpl::addHandler " + handler + ", " + path); + Http2TestContext context = new Http2TestContext(handler, path); + impl.addHandler(context.toHttp2Handler(), path); + return context; + } + @Override + public InetSocketAddress getAddress() { + return new InetSocketAddress("127.0.0.1", + impl.getAddress().getPort()); + } + public Version getVersion() { return Version.HTTP_2; } + } + + private static class Http2TestContext + extends HttpTestContext implements HttpTestHandler { + private final HttpTestHandler handler; + private final String path; + private final List filters = new CopyOnWriteArrayList<>(); + Http2TestContext(HttpTestHandler hdl, String path) { + this.handler = hdl; + this.path = path; + } + @Override + public String getPath() { return path; } + @Override + public void addFilter(HttpTestFilter filter) { + System.out.println("Http2TestContext::addFilter " + filter.description()); + filters.add(filter); + } + @Override + public void handle(HttpTestExchange exchange) throws IOException { + System.out.println("Http2TestContext::handle " + exchange); + HttpChain.of(filters, handler).doFilter(exchange); + } + @Override + public void setAuthenticator(com.sun.net.httpserver.Authenticator authenticator) { + throw new UnsupportedOperationException("Can't set HTTP/1.1 authenticator on HTTP/2 context"); + } + @Override public Version getVersion() { return Version.HTTP_2; } + } + } + +} diff -r ffaea9a1eed5 -r 66a9c3185028 test/jdk/java/net/httpclient/LineBodyHandlerTest.java --- a/test/jdk/java/net/httpclient/LineBodyHandlerTest.java Mon Feb 05 12:32:20 2018 +0000 +++ b/test/jdk/java/net/httpclient/LineBodyHandlerTest.java Mon Feb 05 15:51:09 2018 +0000 @@ -81,18 +81,18 @@ * java.logging * jdk.httpserver * @library /lib/testlibrary http2/server - * @build Http2TestServer + * @build Http2TestServer LineBodyHandlerTest HttpServerAdapters * @build jdk.testlibrary.SimpleSSLContext * @run testng/othervm LineBodyHandlerTest */ -public class LineBodyHandlerTest { +public class LineBodyHandlerTest implements HttpServerAdapters { SSLContext sslContext; - HttpServer httpTestServer; // HTTP/1.1 [ 4 servers ] - HttpsServer httpsTestServer; // HTTPS/1.1 - Http2TestServer http2TestServer; // HTTP/2 ( h2c ) - Http2TestServer https2TestServer; // HTTP/2 ( h2 ) + 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; @@ -650,22 +650,25 @@ throw new AssertionError("Unexpected null sslContext"); InetSocketAddress sa = new InetSocketAddress("localhost", 0); - httpTestServer = HttpServer.create(sa, 0); - httpTestServer.createContext("/http1/echo", new Http1EchoHandler()); - httpURI = "http://127.0.0.1:" + httpTestServer.getAddress().getPort() + "/http1/echo"; + httpTestServer = HttpTestServer.of(HttpServer.create(sa, 0)); + httpTestServer.addHandler(new HttpTestEchoHandler(), "/http1/echo"); + int port = httpTestServer.getAddress().getPort(); + httpURI = "http://127.0.0.1:" + port + "/http1/echo"; - httpsTestServer = HttpsServer.create(sa, 0); - httpsTestServer.setHttpsConfigurator(new HttpsConfigurator(sslContext)); - httpsTestServer.createContext("/https1/echo", new Http1EchoHandler()); - httpsURI = "https://127.0.0.1:" + httpsTestServer.getAddress().getPort() + "/https1/echo"; + HttpsServer httpsServer = HttpsServer.create(sa, 0); + httpsServer.setHttpsConfigurator(new HttpsConfigurator(sslContext)); + httpsTestServer = HttpTestServer.of(httpsServer); + httpsTestServer.addHandler(new HttpTestEchoHandler(),"/https1/echo"); + port = httpsTestServer.getAddress().getPort(); + httpsURI = "https://127.0.0.1:" + port + "/https1/echo"; - http2TestServer = new Http2TestServer("127.0.0.1", false, 0); - http2TestServer.addHandler(new Http2EchoHandler(), "/http2/echo"); - int port = http2TestServer.getAddress().getPort(); + http2TestServer = HttpTestServer.of(new Http2TestServer("127.0.0.1", false, 0)); + http2TestServer.addHandler(new HttpTestEchoHandler(), "/http2/echo"); + port = http2TestServer.getAddress().getPort(); http2URI = "http://127.0.0.1:" + port + "/http2/echo"; - https2TestServer = new Http2TestServer("127.0.0.1", true, 0); - https2TestServer.addHandler(new Http2EchoHandler(), "/https2/echo"); + https2TestServer = HttpTestServer.of(new Http2TestServer("127.0.0.1", true, 0)); + https2TestServer.addHandler(new HttpTestEchoHandler(), "/https2/echo"); port = https2TestServer.getAddress().getPort(); https2URI = "https://127.0.0.1:" + port + "/https2/echo"; @@ -677,8 +680,8 @@ @AfterTest public void teardown() throws Exception { - httpTestServer.stop(0); - httpsTestServer.stop(0); + httpTestServer.stop(); + httpsTestServer.stop(); http2TestServer.stop(); https2TestServer.stop(); } @@ -692,40 +695,6 @@ + new BigInteger(bigbytes).toString(16)); } - static class Http1EchoHandler implements HttpHandler { - @Override - public void handle(HttpExchange t) throws IOException { - try (InputStream is = t.getRequestBody(); - OutputStream os = t.getResponseBody()) { - byte[] bytes = is.readAllBytes(); - printBytes(System.out,"Bytes: ", bytes); - if (t.getRequestHeaders().containsKey("Content-type")) { - t.getResponseHeaders().add("Content-type", - t.getRequestHeaders().getFirst("Content-type")); - } - t.sendResponseHeaders(200, bytes.length); - os.write(bytes); - } - } - } - - static class Http2EchoHandler implements Http2Handler { - @Override - public void handle(Http2TestExchange t) throws IOException { - try (InputStream is = t.getRequestBody(); - OutputStream os = t.getResponseBody()) { - byte[] bytes = is.readAllBytes(); - printBytes(System.out,"Bytes: ", bytes); - if (t.getRequestHeaders().firstValue("Content-type").isPresent()) { - t.getResponseHeaders().addHeader("Content-type", - t.getRequestHeaders().firstValue("Content-type").get()); - } - t.sendResponseHeaders(200, bytes.length); - os.write(bytes); - } - } - } - private static void assertNoObtrusion(CompletableFuture cf) { assertThrows(UnsupportedOperationException.class, () -> cf.obtrudeException(new RuntimeException())); diff -r ffaea9a1eed5 -r 66a9c3185028 test/jdk/java/net/httpclient/MockServer.java --- a/test/jdk/java/net/httpclient/MockServer.java Mon Feb 05 12:32:20 2018 +0000 +++ b/test/jdk/java/net/httpclient/MockServer.java Mon Feb 05 15:51:09 2018 +0000 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved. + * 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 @@ -225,7 +225,18 @@ } public void send(String r) throws IOException { - os.write(r.getBytes(StandardCharsets.ISO_8859_1)); + try { + os.write(r.getBytes(StandardCharsets.ISO_8859_1)); + } catch (IOException x) { + IOException suppressed = + new IOException("MockServer[" + + ss.getLocalPort() + +"] Failed while writing bytes: " + + x.getMessage()); + x.addSuppressed(suppressed); + System.err.println("WARNING: " + suppressed); + throw x; + } } public synchronized void close() { diff -r ffaea9a1eed5 -r 66a9c3185028 test/jdk/java/net/httpclient/ProxyAuthDisabledSchemes.java --- a/test/jdk/java/net/httpclient/ProxyAuthDisabledSchemes.java Mon Feb 05 12:32:20 2018 +0000 +++ b/test/jdk/java/net/httpclient/ProxyAuthDisabledSchemes.java Mon Feb 05 15:51:09 2018 +0000 @@ -28,9 +28,13 @@ * it verifies that the client honor the jdk.http.auth.*.disabledSchemes * net properties. * @bug 8087112 - * @library /lib/testlibrary + * @library /lib/testlibrary http2/server * @build jdk.testlibrary.SimpleSSLContext DigestEchoServer DigestEchoClient ProxyAuthDisabledSchemes - * @modules jdk.incubator.httpclient + * @modules jdk.incubator.httpclient/jdk.incubator.http.internal.common + * jdk.incubator.httpclient/jdk.incubator.http.internal.frame + * jdk.incubator.httpclient/jdk.incubator.http.internal.hpack + * java.logging + * java.base/sun.net.www.http * java.base/sun.net.www * java.base/sun.net * @run main/othervm -Djdk.http.auth.proxying.disabledSchemes=Basic,Digest diff -r ffaea9a1eed5 -r 66a9c3185028 test/jdk/java/net/httpclient/ProxyAuthDisabledSchemesSSL.java --- a/test/jdk/java/net/httpclient/ProxyAuthDisabledSchemesSSL.java Mon Feb 05 12:32:20 2018 +0000 +++ b/test/jdk/java/net/httpclient/ProxyAuthDisabledSchemesSSL.java Mon Feb 05 15:51:09 2018 +0000 @@ -28,9 +28,13 @@ * headers directly when connecting with a server over SSL, and * it verifies that the client honor the jdk.http.auth.*.disabledSchemes * net properties. - * @library /lib/testlibrary + * @library /lib/testlibrary http2/server * @build jdk.testlibrary.SimpleSSLContext DigestEchoServer DigestEchoClient ProxyAuthDisabledSchemesSSL - * @modules jdk.incubator.httpclient + * @modules jdk.incubator.httpclient/jdk.incubator.http.internal.common + * jdk.incubator.httpclient/jdk.incubator.http.internal.frame + * jdk.incubator.httpclient/jdk.incubator.http.internal.hpack + * java.logging + * java.base/sun.net.www.http * java.base/sun.net.www * java.base/sun.net * @run main/othervm -Djdk.http.auth.proxying.disabledSchemes=Basic,Digest