1 /* 2 * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 /* 25 * @test 26 * @summary Test for CONTINUATION frame handling 27 * @modules java.base/sun.net.www.http 28 * jdk.incubator.httpclient/jdk.incubator.http.internal.common 29 * jdk.incubator.httpclient/jdk.incubator.http.internal.frame 30 * jdk.incubator.httpclient/jdk.incubator.http.internal.hpack 31 * @library /lib/testlibrary server 32 * @build Http2TestServer 33 * @build jdk.testlibrary.SimpleSSLContext 34 * @run testng/othervm ContinuationFrameTest 35 */ 36 37 import java.io.IOException; 38 import java.io.InputStream; 39 import java.io.OutputStream; 40 import java.net.URI; 41 import java.nio.ByteBuffer; 42 import java.util.ArrayList; 43 import java.util.List; 44 import java.util.function.BiFunction; 45 import javax.net.ssl.SSLContext; 46 import javax.net.ssl.SSLSession; 47 import jdk.incubator.http.HttpClient; 48 import jdk.incubator.http.HttpRequest; 49 import jdk.incubator.http.HttpResponse; 50 import jdk.incubator.http.internal.common.HttpHeadersImpl; 51 import jdk.incubator.http.internal.frame.ContinuationFrame; 52 import jdk.incubator.http.internal.frame.HeaderFrame; 53 import jdk.incubator.http.internal.frame.HeadersFrame; 54 import jdk.incubator.http.internal.frame.Http2Frame; 55 import jdk.testlibrary.SimpleSSLContext; 56 import org.testng.annotations.AfterTest; 57 import org.testng.annotations.BeforeTest; 58 import org.testng.annotations.DataProvider; 59 import org.testng.annotations.Test; 60 import static java.lang.System.out; 61 import static jdk.incubator.http.HttpClient.Version.HTTP_2; 62 import static jdk.incubator.http.HttpRequest.BodyPublisher.fromString; 63 import static jdk.incubator.http.HttpResponse.BodyHandler.asString; 64 import static org.testng.Assert.assertEquals; 65 import static org.testng.Assert.assertTrue; 66 67 public class ContinuationFrameTest { 68 69 SSLContext sslContext; 70 Http2TestServer http2TestServer; // HTTP/2 ( h2c ) 71 Http2TestServer https2TestServer; // HTTP/2 ( h2 ) 72 String http2URI; 73 String https2URI; 74 75 /** 76 * A function that returns a list of 1) a HEADERS frame ( with an empty 77 * payload ), and 2) a CONTINUATION frame with the actual headers. 78 */ 79 static BiFunction<Integer,List<ByteBuffer>,List<Http2Frame>> oneContinuation = 80 (Integer streamid, List<ByteBuffer> encodedHeaders) -> { 81 List<ByteBuffer> empty = List.of(ByteBuffer.wrap(new byte[0])); 82 HeadersFrame hf = new HeadersFrame(streamid, 0, empty); 83 ContinuationFrame cf = new ContinuationFrame(streamid, 84 HeaderFrame.END_HEADERS, 85 encodedHeaders); 86 return List.of(hf, cf); 87 }; 88 89 /** 90 * A function that returns a list of a HEADERS frame followed by a number of 91 * CONTINUATION frames. Each frame contains just a single byte of payload. 92 */ 93 static BiFunction<Integer,List<ByteBuffer>,List<Http2Frame>> byteAtATime = 94 (Integer streamid, List<ByteBuffer> encodedHeaders) -> { 95 assert encodedHeaders.get(0).hasRemaining(); 96 List<Http2Frame> frames = new ArrayList<>(); 97 ByteBuffer hb = ByteBuffer.wrap(new byte[] {encodedHeaders.get(0).get()}); 98 HeadersFrame hf = new HeadersFrame(streamid, 0, hb); 99 frames.add(hf); 100 for (ByteBuffer bb : encodedHeaders) { 101 while (bb.hasRemaining()) { 102 List<ByteBuffer> data = List.of(ByteBuffer.wrap(new byte[] {bb.get()})); 103 ContinuationFrame cf = new ContinuationFrame(streamid, 0, data); 104 frames.add(cf); 105 } 106 } 107 frames.get(frames.size() - 1).setFlag(HeaderFrame.END_HEADERS); 108 return frames; 109 }; 110 111 @DataProvider(name = "variants") 112 public Object[][] variants() { 113 return new Object[][] { 114 { http2URI, false, oneContinuation }, 115 { https2URI, false, oneContinuation }, 116 { http2URI, true, oneContinuation }, 117 { https2URI, true, oneContinuation }, 118 119 { http2URI, false, byteAtATime }, 120 { https2URI, false, byteAtATime }, 121 { http2URI, true, byteAtATime }, 122 { https2URI, true, byteAtATime }, 123 }; 124 } 125 126 static final int ITERATION_COUNT = 20; 127 128 @Test(dataProvider = "variants") 129 void test(String uri, 130 boolean sameClient, 131 BiFunction<Integer,List<ByteBuffer>,List<Http2Frame>> headerFramesSupplier) 132 throws Exception 133 { 134 CFTHttp2TestExchange.setHeaderFrameSupplier(headerFramesSupplier); 135 136 HttpClient client = null; 137 for (int i=0; i< ITERATION_COUNT; i++) { 138 if (!sameClient || client == null) 139 client = HttpClient.newBuilder().sslContext(sslContext).build(); 140 141 HttpRequest request = HttpRequest.newBuilder(URI.create(uri)) 142 .POST(fromString("Hello there!")) 143 .build(); 144 HttpResponse<String> resp; 145 if (i % 2 == 0) { 146 resp = client.send(request, asString()); 147 } else { 148 resp = client.sendAsync(request, asString()).join(); 149 } 150 151 out.println("Got response: " + resp); 152 out.println("Got body: " + resp.body()); 153 assertTrue(resp.statusCode() == 200, 154 "Expected 200, got:" + resp.statusCode()); 155 assertEquals(resp.body(), "Hello there!"); 156 assertEquals(resp.version(), HTTP_2); 157 } 158 } 159 160 @BeforeTest 161 public void setup() throws Exception { 162 sslContext = new SimpleSSLContext().get(); 163 if (sslContext == null) 164 throw new AssertionError("Unexpected null sslContext"); 165 166 http2TestServer = new Http2TestServer("127.0.0.1", false, 0); 167 http2TestServer.addHandler(new Http2EchoHandler(), "/http2/echo"); 168 int port = http2TestServer.getAddress().getPort(); 169 http2URI = "http://127.0.0.1:" + port + "/http2/echo"; 170 171 https2TestServer = new Http2TestServer("127.0.0.1", true, 0); 172 https2TestServer.addHandler(new Http2EchoHandler(), "/https2/echo"); 173 port = https2TestServer.getAddress().getPort(); 174 https2URI = "https://127.0.0.1:" + port + "/https2/echo"; 175 176 // Override the default exchange supplier with a custom one to enable 177 // particular test scenarios 178 http2TestServer.setExchangeSupplier(CFTHttp2TestExchange::new); 179 https2TestServer.setExchangeSupplier(CFTHttp2TestExchange::new); 180 181 http2TestServer.start(); 182 https2TestServer.start(); 183 } 184 185 @AfterTest 186 public void teardown() throws Exception { 187 http2TestServer.stop(); 188 https2TestServer.stop(); 189 } 190 191 static class Http2EchoHandler implements Http2Handler { 192 @Override 193 public void handle(Http2TestExchange t) throws IOException { 194 try (InputStream is = t.getRequestBody(); 195 OutputStream os = t.getResponseBody()) { 196 byte[] bytes = is.readAllBytes(); 197 t.getResponseHeaders().addHeader("just some", "noise"); 198 t.getResponseHeaders().addHeader("to add ", "payload in "); 199 t.getResponseHeaders().addHeader("the header", "frames"); 200 t.sendResponseHeaders(200, bytes.length); 201 os.write(bytes); 202 } 203 } 204 } 205 206 // A custom Http2TestExchangeImpl that overrides sendResponseHeaders to 207 // allow headers to be sent with a number of CONTINUATION frames. 208 static class CFTHttp2TestExchange extends Http2TestExchangeImpl { 209 static volatile BiFunction<Integer,List<ByteBuffer>,List<Http2Frame>> headerFrameSupplier; 210 211 static void setHeaderFrameSupplier(BiFunction<Integer,List<ByteBuffer>,List<Http2Frame>> hfs) { 212 headerFrameSupplier = hfs; 213 } 214 215 CFTHttp2TestExchange(int streamid, String method, HttpHeadersImpl reqheaders, 216 HttpHeadersImpl rspheaders, URI uri, InputStream is, 217 SSLSession sslSession, BodyOutputStream os, 218 Http2TestServerConnection conn, boolean pushAllowed) { 219 super(streamid, method, reqheaders, rspheaders, uri, is, sslSession, 220 os, conn, pushAllowed); 221 222 } 223 224 @Override 225 public void sendResponseHeaders(int rCode, long responseLength) throws IOException { 226 this.responseLength = responseLength; 227 if (responseLength > 0 || responseLength < 0) { 228 long clen = responseLength > 0 ? responseLength : 0; 229 rspheaders.setHeader("Content-length", Long.toString(clen)); 230 } 231 rspheaders.setHeader(":status", Integer.toString(rCode)); 232 233 List<ByteBuffer> encodeHeaders = conn.encodeHeaders(rspheaders); 234 List<Http2Frame> headerFrames = headerFrameSupplier.apply(streamid, encodeHeaders); 235 assert headerFrames.size() > 0; // there must always be at least 1 236 237 if (responseLength < 0) { 238 headerFrames.get(headerFrames.size() -1).setFlag(HeadersFrame.END_STREAM); 239 os.closeInternal(); 240 } 241 242 for (Http2Frame f : headerFrames) 243 conn.outputQ.put(f); 244 245 os.goodToGo(); 246 System.err.println("Sent response headers " + rCode); 247 } 248 } 249 }