/
githubmirror
/
panama-vector
Обзор
Документация
Войти
/
githubmirror
/
panama-vector
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
test/jdk/java/net/httpclient/http2/ContinuationFrameTest.java
319 строк
13 KB
Daisuke Yamazaki
8372325: Refactor tests under jdk/java/net/httpclient to use ${test.main.class}
09 апр 2026, 13:51
09 апр 2026, 13:51
261011a
Код
Авторство
О чём код?
/* * Copyright (c) 2017, 2026, 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 * @library /test/lib /test/jdk/java/net/httpclient/lib * @build jdk.httpclient.test.lib.http2.Http2TestServer jdk.test.lib.net.SimpleSSLContext * @compile ../ReferenceTracker.java * @run junit/othervm ${test.main.class} */ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URI; import java.net.http.HttpHeaders; 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 java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpRequest.BodyPublishers; import java.net.http.HttpResponse; import java.net.http.HttpResponse.BodyHandlers; import jdk.internal.net.http.common.HttpHeadersBuilder; import jdk.internal.net.http.frame.ContinuationFrame; import jdk.internal.net.http.frame.HeaderFrame; import jdk.internal.net.http.frame.HeadersFrame; import jdk.internal.net.http.frame.Http2Frame; import jdk.httpclient.test.lib.http2.Http2TestServer; import jdk.httpclient.test.lib.http2.Http2TestExchange; import jdk.httpclient.test.lib.http2.Http2TestExchangeImpl; import jdk.httpclient.test.lib.http2.Http2Handler; import jdk.httpclient.test.lib.http2.BodyOutputStream; import jdk.httpclient.test.lib.http2.Http2TestServerConnection; import jdk.test.lib.net.SimpleSSLContext; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import static java.lang.System.out; import static java.net.http.HttpClient.Version.HTTP_2; import static org.junit.jupiter.api.Assertions.assertEquals; public class ContinuationFrameTest { private static final SSLContext sslContext = SimpleSSLContext.findSSLContext(); private static Http2TestServer http2TestServer; // HTTP/2 ( h2c ) private static Http2TestServer https2TestServer; // HTTP/2 ( h2 ) private static String http2URI; private static String https2URI; private static String noBodyhttp2URI; private static String noBodyhttps2URI; private final static ReferenceTracker TRACKER = ReferenceTracker.INSTANCE; /** * 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 1) a HEADERS frame with END_STREAM * ( and with an empty payload ), and 2) two CONTINUATION frames,the first * is empty and the second contains headers and the END_HEADERS flag */ static BiFunction<Integer,List<ByteBuffer>,List<Http2Frame>> twoContinuation = (Integer streamid, List<ByteBuffer> encodedHeaders) -> { List<ByteBuffer> empty = List.of(ByteBuffer.wrap(new byte[0])); HeadersFrame hf = new HeadersFrame(streamid, HeaderFrame.END_STREAM, empty); ContinuationFrame cf = new ContinuationFrame(streamid, 0,empty); ContinuationFrame cf1 = new ContinuationFrame(streamid, HeaderFrame.END_HEADERS, encodedHeaders); return List.of(hf, cf, cf1); }; /** * 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; }; static Object[][] variants() { return new Object[][] { { http2URI, false, oneContinuation }, { https2URI, false, oneContinuation }, { http2URI, true, oneContinuation }, { https2URI, true, oneContinuation }, { noBodyhttp2URI, false, twoContinuation }, { noBodyhttp2URI, true, twoContinuation }, { noBodyhttps2URI, false, twoContinuation }, { noBodyhttps2URI, true, twoContinuation }, { http2URI, false, byteAtATime }, { https2URI, false, byteAtATime }, { http2URI, true, byteAtATime }, { https2URI, true, byteAtATime }, }; } static final int ITERATION_COUNT = 20; static HttpClient sharedClient; HttpClient httpClient(boolean shared) { if (!shared || sharedClient == null) { var client = HttpClient.newBuilder() .proxy(HttpClient.Builder.NO_PROXY) .sslContext(sslContext) .build(); if (sharedClient == null) { sharedClient = client; } TRACKER.track(client); return client; } return sharedClient; } @ParameterizedTest @MethodSource("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(sameClient); } HttpRequest request = HttpRequest.newBuilder(URI.create(uri)) .POST(BodyPublishers.ofString("Hello there!")) .build(); HttpResponse<String> resp; if (i % 2 == 0) { resp = client.send(request, BodyHandlers.ofString()); } else { resp = client.sendAsync(request, BodyHandlers.ofString()).join(); } if(uri.contains("nobody")) { out.println("Got response: " + resp); assertEquals(204, resp.statusCode(), "Expected 204, got:" + resp.statusCode()); assertEquals(HTTP_2, resp.version()); continue; } out.println("Got response: " + resp); out.println("Got body: " + resp.body()); assertEquals(200, resp.statusCode(), "Expected 200, got:" + resp.statusCode()); assertEquals("Hello there!", resp.body()); assertEquals(HTTP_2, resp.version()); } } @BeforeAll static void setup() throws Exception { http2TestServer = new Http2TestServer("localhost", false, 0); http2TestServer.addHandler(new Http2EchoHandler(), "/http2/echo"); http2TestServer.addHandler(new Http2NoBodyHandler(), "/http2/nobody"); int port = http2TestServer.getAddress().getPort(); http2URI = "http://localhost:" + port + "/http2/echo"; noBodyhttp2URI = "http://localhost:" + port + "/http2/nobody"; https2TestServer = new Http2TestServer("localhost", true, sslContext); https2TestServer.addHandler(new Http2EchoHandler(), "/https2/echo"); https2TestServer.addHandler(new Http2NoBodyHandler(), "/https2/nobody"); port = https2TestServer.getAddress().getPort(); https2URI = "https://localhost:" + port + "/https2/echo"; noBodyhttps2URI = "https://localhost:" + port + "/https2/nobody"; // 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(); } @AfterAll static void teardown() throws Exception { sharedClient = null; AssertionError fail = TRACKER.check(500); try { http2TestServer.stop(); https2TestServer.stop(); } finally { if (fail != null) { throw fail; } } } 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("justSome", "Noise"); t.getResponseHeaders().addHeader("toAdd", "payload in"); t.getResponseHeaders().addHeader("theHeader", "Frames"); t.sendResponseHeaders(200, bytes.length); os.write(bytes); } } } static class Http2NoBodyHandler implements Http2Handler { @Override public void handle(Http2TestExchange t) throws IOException { try (InputStream is = t.getRequestBody(); OutputStream os = t.getResponseBody()) { byte[] bytes = is.readAllBytes(); t.sendResponseHeaders(204, -1); } } } // 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, HttpHeaders reqheaders, HttpHeadersBuilder rspheadersBuilder, URI uri, InputStream is, SSLSession sslSession, BodyOutputStream os, Http2TestServerConnection conn, boolean pushAllowed) { super(streamid, method, reqheaders, rspheadersBuilder, uri, is, sslSession, os, conn, pushAllowed); } @Override public void sendResponseHeaders(int rCode, long responseLength) throws IOException { this.responseLength = responseLength; if (responseLength != 0 && rCode != 204) { long clen = responseLength > 0 ? responseLength : 0; rspheadersBuilder.setHeader("Content-length", Long.toString(clen)); } rspheadersBuilder.setHeader(":status", Integer.toString(rCode)); HttpHeaders headers = rspheadersBuilder.build(); List<ByteBuffer> encodeHeaders = conn.encodeHeaders(headers); List<Http2Frame> headerFrames = headerFrameSupplier.apply(streamid, encodeHeaders); assert headerFrames.size() > 0; // there must always be at least 1 if(headerFrames.get(0).getFlag(HeaderFrame.END_STREAM)) os.markClosed(); for (Http2Frame f : headerFrames) { conn.addToOutputQ(f); } os.goodToGo(); System.err.println("Sent response headers " + rCode); } } }