1 /* 2 * Copyright (c) 2017, 2018, 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 * java.net.http/jdk.internal.net.http.common 29 * java.net.http/jdk.internal.net.http.frame 30 * java.net.http/jdk.internal.net.http.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.net.http.HttpHeaders; 42 import java.nio.ByteBuffer; 43 import java.util.ArrayList; 44 import java.util.List; 45 import java.util.function.BiFunction; 46 import javax.net.ssl.SSLContext; 47 import javax.net.ssl.SSLSession; 48 import java.net.http.HttpClient; 49 import java.net.http.HttpRequest; 50 import java.net.http.HttpRequest.BodyPublishers; 51 import java.net.http.HttpResponse; 52 import java.net.http.HttpResponse.BodyHandlers; 53 import jdk.internal.net.http.common.HttpHeadersBuilder; 54 import jdk.internal.net.http.frame.ContinuationFrame; 55 import jdk.internal.net.http.frame.HeaderFrame; 56 import jdk.internal.net.http.frame.HeadersFrame; 57 import jdk.internal.net.http.frame.Http2Frame; 58 import jdk.testlibrary.SimpleSSLContext; 59 import org.testng.annotations.AfterTest; 60 import org.testng.annotations.BeforeTest; 61 import org.testng.annotations.DataProvider; 62 import org.testng.annotations.Test; 63 import static java.lang.System.out; 64 import static java.net.http.HttpClient.Version.HTTP_2; 65 import static org.testng.Assert.assertEquals; 66 import static org.testng.Assert.assertTrue; 67 68 public class ContinuationFrameTest { 69 70 SSLContext sslContext; 71 Http2TestServer http2TestServer; // HTTP/2 ( h2c ) 72 Http2TestServer https2TestServer; // HTTP/2 ( h2 ) 73 String http2URI; 74 String https2URI; 75 76 /** 77 * A function that returns a list of 1) a HEADERS frame ( with an empty 78 * payload ), and 2) a CONTINUATION frame with the actual headers. 79 */ 80 static BiFunction<Integer,List<ByteBuffer>,List<Http2Frame>> oneContinuation = 81 (Integer streamid, List<ByteBuffer> encodedHeaders) -> { 82 List<ByteBuffer> empty = List.of(ByteBuffer.wrap(new byte[0])); 83 HeadersFrame hf = new HeadersFrame(streamid, 0, empty); 84 ContinuationFrame cf = new ContinuationFrame(streamid, 85 HeaderFrame.END_HEADERS, 86 encodedHeaders); 87 return List.of(hf, cf); 88 }; 89 90 /** 91 * A function that returns a list of a HEADERS frame followed by a number of 92 * CONTINUATION frames. Each frame contains just a single byte of payload. 93 */ 94 static BiFunction<Integer,List<ByteBuffer>,List<Http2Frame>> byteAtATime = 95 (Integer streamid, List<ByteBuffer> encodedHeaders) -> { 96 assert encodedHeaders.get(0).hasRemaining(); 97 List<Http2Frame> frames = new ArrayList<>(); 98 ByteBuffer hb = ByteBuffer.wrap(new byte[] {encodedHeaders.get(0).get()}); 99 HeadersFrame hf = new HeadersFrame(streamid, 0, hb); 100 frames.add(hf); 101 for (ByteBuffer bb : encodedHeaders) { 102 while (bb.hasRemaining()) { 103 List<ByteBuffer> data = List.of(ByteBuffer.wrap(new byte[] {bb.get()})); 104 ContinuationFrame cf = new ContinuationFrame(streamid, 0, data); 105 frames.add(cf); 106 } 107 } 108 frames.get(frames.size() - 1).setFlag(HeaderFrame.END_HEADERS); 109 return frames; 110 }; 111 112 @DataProvider(name = "variants") variants()113 public Object[][] variants() { 114 return new Object[][] { 115 { http2URI, false, oneContinuation }, 116 { https2URI, false, oneContinuation }, 117 { http2URI, true, oneContinuation }, 118 { https2URI, true, oneContinuation }, 119 120 { http2URI, false, byteAtATime }, 121 { https2URI, false, byteAtATime }, 122 { http2URI, true, byteAtATime }, 123 { https2URI, true, byteAtATime }, 124 }; 125 } 126 127 static final int ITERATION_COUNT = 20; 128 129 @Test(dataProvider = "variants") test(String uri, boolean sameClient, BiFunction<Integer,List<ByteBuffer>,List<Http2Frame>> headerFramesSupplier)130 void test(String uri, 131 boolean sameClient, 132 BiFunction<Integer,List<ByteBuffer>,List<Http2Frame>> headerFramesSupplier) 133 throws Exception 134 { 135 CFTHttp2TestExchange.setHeaderFrameSupplier(headerFramesSupplier); 136 137 HttpClient client = null; 138 for (int i=0; i< ITERATION_COUNT; i++) { 139 if (!sameClient || client == null) 140 client = HttpClient.newBuilder().sslContext(sslContext).build(); 141 142 HttpRequest request = HttpRequest.newBuilder(URI.create(uri)) 143 .POST(BodyPublishers.ofString("Hello there!")) 144 .build(); 145 HttpResponse<String> resp; 146 if (i % 2 == 0) { 147 resp = client.send(request, BodyHandlers.ofString()); 148 } else { 149 resp = client.sendAsync(request, BodyHandlers.ofString()).join(); 150 } 151 152 out.println("Got response: " + resp); 153 out.println("Got body: " + resp.body()); 154 assertTrue(resp.statusCode() == 200, 155 "Expected 200, got:" + resp.statusCode()); 156 assertEquals(resp.body(), "Hello there!"); 157 assertEquals(resp.version(), HTTP_2); 158 } 159 } 160 161 @BeforeTest setup()162 public void setup() throws Exception { 163 sslContext = new SimpleSSLContext().get(); 164 if (sslContext == null) 165 throw new AssertionError("Unexpected null sslContext"); 166 167 http2TestServer = new Http2TestServer("localhost", false, 0); 168 http2TestServer.addHandler(new Http2EchoHandler(), "/http2/echo"); 169 int port = http2TestServer.getAddress().getPort(); 170 http2URI = "http://localhost:" + port + "/http2/echo"; 171 172 https2TestServer = new Http2TestServer("localhost", true, sslContext); 173 https2TestServer.addHandler(new Http2EchoHandler(), "/https2/echo"); 174 port = https2TestServer.getAddress().getPort(); 175 https2URI = "https://localhost:" + port + "/https2/echo"; 176 177 // Override the default exchange supplier with a custom one to enable 178 // particular test scenarios 179 http2TestServer.setExchangeSupplier(CFTHttp2TestExchange::new); 180 https2TestServer.setExchangeSupplier(CFTHttp2TestExchange::new); 181 182 http2TestServer.start(); 183 https2TestServer.start(); 184 } 185 186 @AfterTest teardown()187 public void teardown() throws Exception { 188 http2TestServer.stop(); 189 https2TestServer.stop(); 190 } 191 192 static class Http2EchoHandler implements Http2Handler { 193 @Override handle(Http2TestExchange t)194 public void handle(Http2TestExchange t) throws IOException { 195 try (InputStream is = t.getRequestBody(); 196 OutputStream os = t.getResponseBody()) { 197 byte[] bytes = is.readAllBytes(); 198 t.getResponseHeaders().addHeader("justSome", "Noise"); 199 t.getResponseHeaders().addHeader("toAdd", "payload in"); 200 t.getResponseHeaders().addHeader("theHeader", "Frames"); 201 t.sendResponseHeaders(200, bytes.length); 202 os.write(bytes); 203 } 204 } 205 } 206 207 // A custom Http2TestExchangeImpl that overrides sendResponseHeaders to 208 // allow headers to be sent with a number of CONTINUATION frames. 209 static class CFTHttp2TestExchange extends Http2TestExchangeImpl { 210 static volatile BiFunction<Integer,List<ByteBuffer>,List<Http2Frame>> headerFrameSupplier; 211 setHeaderFrameSupplier(BiFunction<Integer,List<ByteBuffer>,List<Http2Frame>> hfs)212 static void setHeaderFrameSupplier(BiFunction<Integer,List<ByteBuffer>,List<Http2Frame>> hfs) { 213 headerFrameSupplier = hfs; 214 } 215 CFTHttp2TestExchange(int streamid, String method, HttpHeaders reqheaders, HttpHeadersBuilder rspheadersBuilder, URI uri, InputStream is, SSLSession sslSession, BodyOutputStream os, Http2TestServerConnection conn, boolean pushAllowed)216 CFTHttp2TestExchange(int streamid, String method, HttpHeaders reqheaders, 217 HttpHeadersBuilder rspheadersBuilder, URI uri, InputStream is, 218 SSLSession sslSession, BodyOutputStream os, 219 Http2TestServerConnection conn, boolean pushAllowed) { 220 super(streamid, method, reqheaders, rspheadersBuilder, uri, is, sslSession, 221 os, conn, pushAllowed); 222 223 } 224 225 @Override sendResponseHeaders(int rCode, long responseLength)226 public void sendResponseHeaders(int rCode, long responseLength) throws IOException { 227 this.responseLength = responseLength; 228 if (responseLength > 0 || responseLength < 0) { 229 long clen = responseLength > 0 ? responseLength : 0; 230 rspheadersBuilder.setHeader("Content-length", Long.toString(clen)); 231 } 232 rspheadersBuilder.setHeader(":status", Integer.toString(rCode)); 233 HttpHeaders headers = rspheadersBuilder.build(); 234 235 List<ByteBuffer> encodeHeaders = conn.encodeHeaders(headers); 236 List<Http2Frame> headerFrames = headerFrameSupplier.apply(streamid, encodeHeaders); 237 assert headerFrames.size() > 0; // there must always be at least 1 238 239 if (responseLength < 0) { 240 headerFrames.get(headerFrames.size() -1).setFlag(HeadersFrame.END_STREAM); 241 os.closeInternal(); 242 } 243 244 for (Http2Frame f : headerFrames) 245 conn.outputQ.put(f); 246 247 os.goodToGo(); 248 System.err.println("Sent response headers " + rCode); 249 } 250 } 251 } 252