http-client-branch: add tests with an HTTP/2 server for custom basic and digest authentication http-client-branch
authordfuchs
Mon, 05 Feb 2018 15:51:09 +0000
branchhttp-client-branch
changeset 56070 66a9c3185028
parent 56069 ffaea9a1eed5
child 56071 3353cb42b1b4
http-client-branch: add tests with an HTTP/2 server for custom basic and digest authentication
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpRequestImpl.java
test/jdk/java/net/httpclient/CancelledResponse.java
test/jdk/java/net/httpclient/DigestEchoClient.java
test/jdk/java/net/httpclient/DigestEchoClientSSL.java
test/jdk/java/net/httpclient/DigestEchoServer.java
test/jdk/java/net/httpclient/HttpServerAdapters.java
test/jdk/java/net/httpclient/LineBodyHandlerTest.java
test/jdk/java/net/httpclient/MockServer.java
test/jdk/java/net/httpclient/ProxyAuthDisabledSchemes.java
test/jdk/java/net/httpclient/ProxyAuthDisabledSchemesSSL.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<HttpClient.Version> 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
--- 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;
--- 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<Version> serverVersions(Version clientVersion) {
+        if (clientVersion == Version.HTTP_1_1) {
+            return List.of(clientVersion);
+        } else {
+            return List.of(Version.values());
+        }
+    }
+
+    public static List<Version> clientVersions() {
+        return List.of(Version.values());
+    }
+
+    public static List<Boolean> 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<DigestEchoServer.HttpAuthType> 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<String> 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<String> 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)) {
--- 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
--- 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<S extends HttpServer>
+    private static abstract class H1ServerFactory<S extends HttpServer>
             extends SocketBindableFactory<S> {
         @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<HttpServer> {
-        private static final HttpServerFactory instance = new HttpServerFactory();
+    /*
+     * Used to create Http2TestServer
+     */
+    private static abstract class H2ServerFactory<S extends Http2TestServer>
+            extends SocketBindableFactory<S> {
+        @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<Http2TestServer> {
+        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<Http2TestServer> {
+        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<HttpServer> {
+        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<HttpsServer> {
-        private static final HttpsServerFactory instance = new HttpsServerFactory();
+    private static final class Https1ServerFactory extends H1ServerFactory<HttpsServer> {
+        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<String> 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<String> 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());
         }
 
 
--- /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.
+ * <p>
+ * For instance:
+ * <pre>{@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";
+ *
+ *  }</pre>
+ */
+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<String> firstValue(String name);
+        public abstract void addHeader(String name, String value);
+        public abstract Set<String> keySet();
+        public abstract Set<Map.Entry<String, List<String>>> entrySet();
+        public abstract List<String> 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<String> 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<String> keySet() { return headers.keySet(); }
+            @Override
+            public Set<Map.Entry<String, List<String>>> entrySet() {
+                return headers.entrySet();
+            }
+            @Override
+            public List<String> 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<String> firstValue(String name) {
+                return headers.firstValue(name);
+            }
+            @Override
+            public void addHeader(String name, String value) {
+                headers.addHeader(name, value);
+            }
+            public Set<String> keySet() { return headers.map().keySet(); }
+            @Override
+            public Set<Map.Entry<String, List<String>>> entrySet() {
+                return headers.map().entrySet();
+            }
+            @Override
+            public List<String> 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<HttpTestFilter> 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<HttpTestFilter> iter;
+            HttpTestHandler handler;
+            Http2Chain(List<HttpTestFilter> 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<HttpTestFilter> 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; }
+        }
+    }
+
+}
--- 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()));
--- 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() {
--- 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
--- 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