http-client-branch: Add a test for CONTINUATION frame handling http-client-branch
authorchegar
Thu, 16 Nov 2017 10:01:34 +0000
branchhttp-client-branch
changeset 55817 2aec8081d2ad
parent 55816 70738768515a
child 55818 725576a6821e
http-client-branch: Add a test for CONTINUATION frame handling
test/jdk/java/net/httpclient/http2/BasicTest.java
test/jdk/java/net/httpclient/http2/ContinuationFrameTest.java
test/jdk/java/net/httpclient/http2/server/EchoHandler.java
test/jdk/java/net/httpclient/http2/server/Http2TestExchange.java
test/jdk/java/net/httpclient/http2/server/Http2TestExchangeImpl.java
test/jdk/java/net/httpclient/http2/server/Http2TestExchangeSupplier.java
test/jdk/java/net/httpclient/http2/server/Http2TestServer.java
test/jdk/java/net/httpclient/http2/server/Http2TestServerConnection.java
--- a/test/jdk/java/net/httpclient/http2/BasicTest.java	Wed Nov 15 11:39:54 2017 +0000
+++ b/test/jdk/java/net/httpclient/http2/BasicTest.java	Thu Nov 16 10:01:34 2017 +0000
@@ -83,7 +83,7 @@
         }
     }
 
-    @Test(timeOut=3000000)
+    @Test
     public static void test() throws Exception {
         try {
             initialize();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/http2/ContinuationFrameTest.java	Thu Nov 16 10:01:34 2017 +0000
@@ -0,0 +1,249 @@
+/*
+ * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @summary Test for CONTINUATION frame handling
+ * @modules java.base/sun.net.www.http
+ *          jdk.incubator.httpclient/jdk.incubator.http.internal.common
+ *          jdk.incubator.httpclient/jdk.incubator.http.internal.frame
+ *          jdk.incubator.httpclient/jdk.incubator.http.internal.hpack
+ * @library /lib/testlibrary server
+ * @build Http2TestServer
+ * @build jdk.testlibrary.SimpleSSLContext
+ * @run testng/othervm ContinuationFrameTest
+ */
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URI;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.BiFunction;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSession;
+import jdk.incubator.http.HttpClient;
+import jdk.incubator.http.HttpRequest;
+import jdk.incubator.http.HttpResponse;
+import jdk.incubator.http.internal.common.HttpHeadersImpl;
+import jdk.incubator.http.internal.frame.ContinuationFrame;
+import jdk.incubator.http.internal.frame.HeaderFrame;
+import jdk.incubator.http.internal.frame.HeadersFrame;
+import jdk.incubator.http.internal.frame.Http2Frame;
+import jdk.testlibrary.SimpleSSLContext;
+import org.testng.annotations.AfterTest;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+import static java.lang.System.out;
+import static jdk.incubator.http.HttpClient.Version.HTTP_2;
+import static jdk.incubator.http.HttpRequest.BodyPublisher.fromString;
+import static jdk.incubator.http.HttpResponse.BodyHandler.asString;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+public class ContinuationFrameTest {
+
+    SSLContext sslContext;
+    Http2TestServer http2TestServer;   // HTTP/2 ( h2c )
+    Http2TestServer https2TestServer;  // HTTP/2 ( h2  )
+    String http2URI;
+    String https2URI;
+
+    /**
+     * A function that returns a list of 1) a HEADERS frame ( with an empty
+     * payload ), and 2) a CONTINUATION frame with the actual headers.
+     */
+    static BiFunction<Integer,List<ByteBuffer>,List<Http2Frame>> oneContinuation =
+        (Integer streamid, List<ByteBuffer> encodedHeaders) -> {
+            List<ByteBuffer> empty =  List.of(ByteBuffer.wrap(new byte[0]));
+            HeadersFrame hf = new HeadersFrame(streamid, 0, empty);
+            ContinuationFrame cf = new ContinuationFrame(streamid,
+                                                         HeaderFrame.END_HEADERS,
+                                                         encodedHeaders);
+            return List.of(hf, cf);
+        };
+
+    /**
+     * A function that returns a list of a HEADERS frame followed by a number of
+     * CONTINUATION frames. Each frame contains just a single byte of payload.
+     */
+    static BiFunction<Integer,List<ByteBuffer>,List<Http2Frame>> byteAtATime =
+        (Integer streamid, List<ByteBuffer> encodedHeaders) -> {
+            assert encodedHeaders.get(0).hasRemaining();
+            List<Http2Frame> frames = new ArrayList<>();
+            ByteBuffer hb = ByteBuffer.wrap(new byte[] {encodedHeaders.get(0).get()});
+            HeadersFrame hf = new HeadersFrame(streamid, 0, hb);
+            frames.add(hf);
+            for (ByteBuffer bb : encodedHeaders) {
+                while (bb.hasRemaining()) {
+                    List<ByteBuffer> data = List.of(ByteBuffer.wrap(new byte[] {bb.get()}));
+                    ContinuationFrame cf = new ContinuationFrame(streamid, 0, data);
+                    frames.add(cf);
+                }
+            }
+            frames.get(frames.size() - 1).setFlag(HeaderFrame.END_HEADERS);
+            return frames;
+        };
+
+    @DataProvider(name = "variants")
+    public Object[][] variants() {
+        return new Object[][] {
+                { http2URI,  false, oneContinuation },
+                { https2URI, false, oneContinuation },
+                { http2URI,  true,  oneContinuation },
+                { https2URI, true,  oneContinuation },
+
+                { http2URI,  false, byteAtATime },
+                { https2URI, false, byteAtATime },
+                { http2URI,  true,  byteAtATime },
+                { https2URI, true,  byteAtATime },
+        };
+    }
+
+    static final int ITERATION_COUNT = 20;
+
+    @Test(dataProvider = "variants")
+    void test(String uri,
+              boolean sameClient,
+              BiFunction<Integer,List<ByteBuffer>,List<Http2Frame>> headerFramesSupplier)
+        throws Exception
+    {
+        CFTHttp2TestExchange.setHeaderFrameSupplier(headerFramesSupplier);
+
+        HttpClient client = null;
+        for (int i=0; i< ITERATION_COUNT; i++) {
+            if (!sameClient || client == null)
+                client = HttpClient.newBuilder().sslContext(sslContext).build();
+
+            HttpRequest request = HttpRequest.newBuilder(URI.create(uri))
+                                             .POST(fromString("Hello there!"))
+                                             .build();
+            HttpResponse<String> resp;
+            if (i % 2 == 0) {
+                resp = client.send(request, asString());
+            } else {
+                resp = client.sendAsync(request, asString()).join();
+            }
+
+            out.println("Got response: " + resp);
+            out.println("Got body: " + resp.body());
+            assertTrue(resp.statusCode() == 200,
+                       "Expected 200, got:" + resp.statusCode());
+            assertEquals(resp.body(), "Hello there!");
+            assertEquals(resp.version(), HTTP_2);
+        }
+    }
+
+    @BeforeTest
+    public void setup() throws Exception {
+        sslContext = new SimpleSSLContext().get();
+        if (sslContext == null)
+            throw new AssertionError("Unexpected null sslContext");
+
+        http2TestServer = new Http2TestServer("127.0.0.1", false, 0);
+        http2TestServer.addHandler(new Http2EchoHandler(), "/http2/echo");
+        int 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");
+        port = https2TestServer.getAddress().getPort();
+        https2URI = "https://127.0.0.1:" + port + "/https2/echo";
+
+        // Override the default exchange supplier with a custom one to enable
+        // particular test scenarios
+        http2TestServer.setExchangeSupplier(CFTHttp2TestExchange::new);
+        https2TestServer.setExchangeSupplier(CFTHttp2TestExchange::new);
+
+        http2TestServer.start();
+        https2TestServer.start();
+    }
+
+    @AfterTest
+    public void teardown() throws Exception {
+        http2TestServer.stop();
+        https2TestServer.stop();
+    }
+
+    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();
+                t.getResponseHeaders().addHeader("just some", "noise");
+                t.getResponseHeaders().addHeader("to add ", "payload in ");
+                t.getResponseHeaders().addHeader("the header", "frames");
+                t.sendResponseHeaders(200, bytes.length);
+                os.write(bytes);
+            }
+        }
+    }
+
+    // A custom Http2TestExchangeImpl that overrides sendResponseHeaders to
+    // allow headers to be sent with a number of CONTINUATION frames.
+    static class CFTHttp2TestExchange extends Http2TestExchangeImpl {
+        static volatile BiFunction<Integer,List<ByteBuffer>,List<Http2Frame>> headerFrameSupplier;
+
+        static void setHeaderFrameSupplier(BiFunction<Integer,List<ByteBuffer>,List<Http2Frame>> hfs) {
+            headerFrameSupplier = hfs;
+        }
+
+        CFTHttp2TestExchange(int streamid, String method, HttpHeadersImpl reqheaders,
+                             HttpHeadersImpl rspheaders, URI uri, InputStream is,
+                             SSLSession sslSession, BodyOutputStream os,
+                             Http2TestServerConnection conn, boolean pushAllowed) {
+            super(streamid, method, reqheaders, rspheaders, uri, is, sslSession,
+                  os, conn, pushAllowed);
+
+        }
+
+        @Override
+        public void sendResponseHeaders(int rCode, long responseLength) throws IOException {
+            this.responseLength = responseLength;
+            if (responseLength > 0 || responseLength < 0) {
+                long clen = responseLength > 0 ? responseLength : 0;
+                rspheaders.setHeader("Content-length", Long.toString(clen));
+            }
+            rspheaders.setHeader(":status", Integer.toString(rCode));
+
+            List<ByteBuffer> encodeHeaders = conn.encodeHeaders(rspheaders);
+            List<Http2Frame> headerFrames = headerFrameSupplier.apply(streamid, encodeHeaders);
+            assert headerFrames.size() > 0;  // there must always be at least 1
+
+            if (responseLength < 0) {
+                headerFrames.get(headerFrames.size() -1).setFlag(HeadersFrame.END_STREAM);
+                os.closeInternal();
+            }
+
+            for (Http2Frame f : headerFrames)
+                conn.outputQ.put(f);
+
+            os.goodToGo();
+            System.err.println("Sent response headers " + rCode);
+        }
+    }
+}
--- a/test/jdk/java/net/httpclient/http2/server/EchoHandler.java	Wed Nov 15 11:39:54 2017 +0000
+++ b/test/jdk/java/net/httpclient/http2/server/EchoHandler.java	Thu Nov 16 10:01:34 2017 +0000
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2005, 2017, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
--- a/test/jdk/java/net/httpclient/http2/server/Http2TestExchange.java	Wed Nov 15 11:39:54 2017 +0000
+++ b/test/jdk/java/net/httpclient/http2/server/Http2TestExchange.java	Thu Nov 16 10:01:34 2017 +0000
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -21,148 +21,43 @@
  * questions.
  */
 
+import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
-import java.io.IOException;
 import java.net.URI;
 import java.net.InetSocketAddress;
 import javax.net.ssl.SSLSession;
 import jdk.incubator.http.internal.common.HttpHeadersImpl;
-import jdk.incubator.http.internal.frame.HeaderFrame;
-import jdk.incubator.http.internal.frame.HeadersFrame;
 
-public class Http2TestExchange {
+public interface Http2TestExchange {
 
-    final HttpHeadersImpl reqheaders;
-    final HttpHeadersImpl rspheaders;
-    final URI uri;
-    final String method;
-    final InputStream is;
-    final BodyOutputStream os;
-    final SSLSession sslSession;
-    final int streamid;
-    final boolean pushAllowed;
-    final Http2TestServerConnection conn;
-    final Http2TestServer server;
+    HttpHeadersImpl getRequestHeaders();
 
-    int responseCode = -1;
-    long responseLength;
+    HttpHeadersImpl getResponseHeaders();
 
-    Http2TestExchange(int streamid,
-                      String method,
-                      HttpHeadersImpl reqheaders,
-                      HttpHeadersImpl rspheaders,
-                      URI uri,
-                      InputStream is,
-                      SSLSession sslSession,
-                      BodyOutputStream os,
-                      Http2TestServerConnection conn,
-                      boolean pushAllowed) {
-        this.reqheaders = reqheaders;
-        this.rspheaders = rspheaders;
-        this.uri = uri;
-        this.method = method;
-        this.is = is;
-        this.streamid = streamid;
-        this.os = os;
-        this.sslSession = sslSession;
-        this.pushAllowed = pushAllowed;
-        this.conn = conn;
-        this.server = conn.server;
-    }
+    URI getRequestURI();
+
+    String getRequestMethod();
 
-    public HttpHeadersImpl getRequestHeaders() {
-        return reqheaders;
-    }
-
-    public HttpHeadersImpl getResponseHeaders() {
-        return rspheaders;
-    }
+    SSLSession getSSLSession();
 
-    public URI getRequestURI() {
-        return uri;
-    }
+    void close();
 
-    public String getRequestMethod() {
-        return method;
-    }
-
-    public SSLSession getSSLSession() {
-        return sslSession;
-    }
+    InputStream getRequestBody();
 
-    public void close() {
-        try {
-            is.close();
-            os.close();
-        } catch (IOException e) {
-            System.err.println("TestServer: HttpExchange.close exception: " + e);
-            e.printStackTrace();
-        }
-    }
+    OutputStream getResponseBody();
+
+    void sendResponseHeaders(int rCode, long responseLength) throws IOException;
 
-    public InputStream getRequestBody() {
-        return is;
-    }
-
-    public OutputStream getResponseBody() {
-        return os;
-    }
+    InetSocketAddress getRemoteAddress();
 
-    public void sendResponseHeaders(int rCode, long responseLength) throws IOException {
-        this.responseLength = responseLength;
-        if (responseLength > 0 || responseLength < 0) {
-                long clen = responseLength > 0 ? responseLength : 0;
-            rspheaders.setHeader("Content-length", Long.toString(clen));
-        }
-
-        rspheaders.setHeader(":status", Integer.toString(rCode));
+    int getResponseCode();
 
-        Http2TestServerConnection.ResponseHeaders response
-                = new Http2TestServerConnection.ResponseHeaders(rspheaders);
-        response.streamid(streamid);
-        response.setFlag(HeaderFrame.END_HEADERS);
-        if (responseLength < 0) {
-            response.setFlag(HeadersFrame.END_STREAM);
-            os.closeInternal();
-        }
-        conn.outputQ.put(response);
-        os.goodToGo();
-        System.err.println("Sent response headers " + rCode);
-    }
+    InetSocketAddress getLocalAddress();
 
-    public InetSocketAddress getRemoteAddress() {
-        return (InetSocketAddress) conn.socket.getRemoteSocketAddress();
-    }
-
-    public int getResponseCode() {
-        return responseCode;
-    }
+    String getProtocol();
 
-    public InetSocketAddress getLocalAddress() {
-        return server.getAddress();
-    }
-
-    public String getProtocol() {
-        return "HTTP/2";
-    }
-
-    public boolean serverPushAllowed() {
-        return pushAllowed;
-    }
+    boolean serverPushAllowed();
 
-    public void serverPush(URI uri, HttpHeadersImpl headers, InputStream content) {
-        OutgoingPushPromise pp = new OutgoingPushPromise(
-                streamid, uri, headers, content);
-        headers.setHeader(":method", "GET");
-        headers.setHeader(":scheme", uri.getScheme());
-        headers.setHeader(":authority", uri.getAuthority());
-        headers.setHeader(":path", uri.getPath());
-        try {
-            conn.outputQ.put(pp);
-            // writeLoop will spin up thread to read the InputStream
-        } catch (IOException ex) {
-            System.err.println("TestServer: pushPromise exception: " + ex);
-        }
-    }
+    void serverPush(URI uri, HttpHeadersImpl headers, InputStream content);
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/http2/server/Http2TestExchangeImpl.java	Thu Nov 16 10:01:34 2017 +0000
@@ -0,0 +1,185 @@
+/*
+ * Copyright (c) 2016, 2017, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.IOException;
+import java.net.URI;
+import java.net.InetSocketAddress;
+import javax.net.ssl.SSLSession;
+import jdk.incubator.http.internal.common.HttpHeadersImpl;
+import jdk.incubator.http.internal.frame.HeaderFrame;
+import jdk.incubator.http.internal.frame.HeadersFrame;
+
+public class Http2TestExchangeImpl implements Http2TestExchange {
+
+    final HttpHeadersImpl reqheaders;
+    final HttpHeadersImpl rspheaders;
+    final URI uri;
+    final String method;
+    final InputStream is;
+    final BodyOutputStream os;
+    final SSLSession sslSession;
+    final int streamid;
+    final boolean pushAllowed;
+    final Http2TestServerConnection conn;
+    final Http2TestServer server;
+
+    int responseCode = -1;
+    long responseLength;
+
+    Http2TestExchangeImpl(int streamid,
+                          String method,
+                          HttpHeadersImpl reqheaders,
+                          HttpHeadersImpl rspheaders,
+                          URI uri,
+                          InputStream is,
+                          SSLSession sslSession,
+                          BodyOutputStream os,
+                          Http2TestServerConnection conn,
+                          boolean pushAllowed) {
+        this.reqheaders = reqheaders;
+        this.rspheaders = rspheaders;
+        this.uri = uri;
+        this.method = method;
+        this.is = is;
+        this.streamid = streamid;
+        this.os = os;
+        this.sslSession = sslSession;
+        this.pushAllowed = pushAllowed;
+        this.conn = conn;
+        this.server = conn.server;
+    }
+
+    @Override
+    public HttpHeadersImpl getRequestHeaders() {
+        return reqheaders;
+    }
+
+    @Override
+    public HttpHeadersImpl getResponseHeaders() {
+        return rspheaders;
+    }
+
+    @Override
+    public URI getRequestURI() {
+        return uri;
+    }
+
+    @Override
+    public String getRequestMethod() {
+        return method;
+    }
+
+    @Override
+    public SSLSession getSSLSession() {
+        return sslSession;
+    }
+
+    @Override
+    public void close() {
+        try {
+            is.close();
+            os.close();
+        } catch (IOException e) {
+            System.err.println("TestServer: HttpExchange.close exception: " + e);
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    public InputStream getRequestBody() {
+        return is;
+    }
+
+    @Override
+    public OutputStream getResponseBody() {
+        return os;
+    }
+
+    @Override
+    public void sendResponseHeaders(int rCode, long responseLength) throws IOException {
+        this.responseLength = responseLength;
+        if (responseLength > 0 || responseLength < 0) {
+                long clen = responseLength > 0 ? responseLength : 0;
+            rspheaders.setHeader("Content-length", Long.toString(clen));
+        }
+
+        rspheaders.setHeader(":status", Integer.toString(rCode));
+
+        Http2TestServerConnection.ResponseHeaders response
+                = new Http2TestServerConnection.ResponseHeaders(rspheaders);
+        response.streamid(streamid);
+        response.setFlag(HeaderFrame.END_HEADERS);
+
+
+        if (responseLength < 0) {
+            response.setFlag(HeadersFrame.END_STREAM);
+            os.closeInternal();
+        }
+        conn.outputQ.put(response);
+        os.goodToGo();
+        System.err.println("Sent response headers " + rCode);
+    }
+
+    @Override
+    public InetSocketAddress getRemoteAddress() {
+        return (InetSocketAddress) conn.socket.getRemoteSocketAddress();
+    }
+
+    @Override
+    public int getResponseCode() {
+        return responseCode;
+    }
+
+    @Override
+    public InetSocketAddress getLocalAddress() {
+        return server.getAddress();
+    }
+
+    @Override
+    public String getProtocol() {
+        return "HTTP/2";
+    }
+
+    @Override
+    public boolean serverPushAllowed() {
+        return pushAllowed;
+    }
+
+    @Override
+    public void serverPush(URI uri, HttpHeadersImpl headers, InputStream content) {
+        OutgoingPushPromise pp = new OutgoingPushPromise(
+                streamid, uri, headers, content);
+        headers.setHeader(":method", "GET");
+        headers.setHeader(":scheme", uri.getScheme());
+        headers.setHeader(":authority", uri.getAuthority());
+        headers.setHeader(":path", uri.getPath());
+        try {
+            conn.outputQ.put(pp);
+            // writeLoop will spin up thread to read the InputStream
+        } catch (IOException ex) {
+            System.err.println("TestServer: pushPromise exception: " + ex);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/http2/server/Http2TestExchangeSupplier.java	Thu Nov 16 10:01:34 2017 +0000
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import javax.net.ssl.SSLSession;
+import java.io.InputStream;
+import java.net.URI;
+import jdk.incubator.http.internal.common.HttpHeadersImpl;
+
+/**
+ * A supplier of Http2TestExchanges. If the default Http2TestExchange impl is
+ * not sufficient, then a supplier may be set on an Http2TestServer through its
+ * {@link Http2TestServer#setExchangeSupplier(Http2TestExchangeSupplier)}.
+ *
+ * Useful for testing scenarios where non-standard or specific server behaviour
+ * is required, either direct control over the frames sent, "bad" behaviour, or
+ * something else.
+ */
+public interface Http2TestExchangeSupplier {
+
+    Http2TestExchange get(int streamid,
+                          String method,
+                          HttpHeadersImpl reqheaders,
+                          HttpHeadersImpl rspheaders,
+                          URI uri,
+                          InputStream is,
+                          SSLSession sslSession,
+                          BodyOutputStream os,
+                          Http2TestServerConnection conn,
+                          boolean pushAllowed);
+
+    static Http2TestExchangeSupplier ofDefault() {
+        return Http2TestExchangeImpl::new;
+    }
+}
--- a/test/jdk/java/net/httpclient/http2/server/Http2TestServer.java	Wed Nov 15 11:39:54 2017 +0000
+++ b/test/jdk/java/net/httpclient/http2/server/Http2TestServer.java	Thu Nov 16 10:01:34 2017 +0000
@@ -131,6 +131,18 @@
         handlers.put(path, handler);
     }
 
+    volatile Http2TestExchangeSupplier exchangeSupplier = Http2TestExchangeSupplier.ofDefault();
+
+    /**
+     * Sets an explicit exchange handler to be used for all future connections.
+     * Useful for testing scenarios where non-standard or specific server
+     * behaviour is required, either direct control over the frames sent, "bad"
+     * behaviour, or something else.
+     */
+    public void setExchangeSupplier(Http2TestExchangeSupplier exchangeSupplier) {
+        this.exchangeSupplier = exchangeSupplier;
+    }
+
     Http2Handler getHandlerFor(String path) {
         if (path == null || path.equals(""))
             path = "/";
@@ -199,7 +211,8 @@
                 while (!stopping) {
                     Socket socket = server.accept();
                     InetSocketAddress addr = (InetSocketAddress) socket.getRemoteSocketAddress();
-                    Http2TestServerConnection c = new Http2TestServerConnection(this, socket);
+                    Http2TestServerConnection c =
+                            new Http2TestServerConnection(this, socket, exchangeSupplier);
                     connections.put(addr, c);
                     try {
                         c.run();
--- a/test/jdk/java/net/httpclient/http2/server/Http2TestServerConnection.java	Wed Nov 15 11:39:54 2017 +0000
+++ b/test/jdk/java/net/httpclient/http2/server/Http2TestServerConnection.java	Thu Nov 16 10:01:34 2017 +0000
@@ -69,6 +69,7 @@
     final Queue<Http2Frame> outputQ;
     volatile int nextstream;
     final Socket socket;
+    final Http2TestExchangeSupplier exchangeSupplier;
     final InputStream is;
     final OutputStream os;
     volatile Encoder hpackOut;
@@ -85,12 +86,17 @@
 
     final static byte[] clientPreface = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n".getBytes();
 
-    Http2TestServerConnection(Http2TestServer server, Socket socket) throws IOException {
+    Http2TestServerConnection(Http2TestServer server,
+                              Socket socket,
+                              Http2TestExchangeSupplier exchangeSupplier)
+        throws IOException
+    {
         if (socket instanceof SSLSocket) {
             handshake(server.serverName(), (SSLSocket)socket);
         }
         System.err.println("TestServer: New connection from " + socket);
         this.server = server;
+        this.exchangeSupplier = exchangeSupplier;
         this.streams = Collections.synchronizedMap(new HashMap<>());
         this.outputQ = new Queue<>();
         this.socket = socket;
@@ -451,7 +457,7 @@
             String us = scheme + "://" + authority + path;
             URI uri = new URI(us);
             boolean pushAllowed = clientSettings.getParameter(SettingsFrame.ENABLE_PUSH) == 1;
-            Http2TestExchange exchange = new Http2TestExchange(streamid, method,
+            Http2TestExchange exchange = exchangeSupplier.get(streamid, method,
                     headers, rspheaders, uri, bis, getSSLSession(),
                     bos, this, pushAllowed);