/* * Copyright (c) 2018, 2023, 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 * @library /test/lib /test/jdk/java/net/httpclient/lib * @build jdk.test.lib.net.SimpleSSLContext jdk.httpclient.test.lib.http2.Http2TestServer * @run testng/othervm * -Djdk.internal.httpclient.debug=true * -Djdk.httpclient.HttpClient.log=errors,requests,responses,trace * ImplicitPushCancel */ import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpHeaders; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.net.http.HttpResponse.BodyHandlers; import java.net.http.HttpResponse.PushPromiseHandler; import java.util.Collections; import java.util.Map; import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import jdk.httpclient.test.lib.http2.Http2TestServer; import jdk.httpclient.test.lib.http2.Http2TestExchange; import jdk.httpclient.test.lib.http2.Http2Handler; import org.testng.annotations.AfterTest; import org.testng.annotations.BeforeTest; import org.testng.annotations.Test; import static java.nio.charset.StandardCharsets.UTF_8; import static org.testng.Assert.assertEquals; public class ImplicitPushCancel { static Map PUSH_PROMISES = Map.of( "/x/y/z/1", "the first push promise body", "/x/y/z/2", "the second push promise body", "/x/y/z/3", "the third push promise body", "/x/y/z/4", "the fourth push promise body", "/x/y/z/5", "the fifth push promise body", "/x/y/z/6", "the sixth push promise body", "/x/y/z/7", "the seventh push promise body", "/x/y/z/8", "the eight push promise body", "/x/y/z/9", "the ninth push promise body" ); static final String MAIN_RESPONSE_BODY = "the main response body"; Http2TestServer server; URI uri; @BeforeTest public void setup() throws Exception { server = new Http2TestServer(false, 0); Http2Handler handler = new ServerPushHandler(MAIN_RESPONSE_BODY, PUSH_PROMISES); server.addHandler(handler, "/"); server.start(); int port = server.getAddress().getPort(); System.err.println("Server listening on port " + port); uri = new URI("http://localhost:" + port + "/foo/a/b/c"); } @AfterTest public void teardown() { server.stop(); } static final HttpResponse assert200ResponseCode(HttpResponse response) { assertEquals(response.statusCode(), 200); return response; } /* * With a handler not capable of accepting push promises, then all push * promises should be rejected / cancelled, without interfering with the * main response. */ @Test public void test() throws Exception { HttpClient client = HttpClient.newHttpClient(); client.sendAsync(HttpRequest.newBuilder(uri).build(), BodyHandlers.ofString()) .thenApply(ImplicitPushCancel::assert200ResponseCode) .thenApply(HttpResponse::body) .thenAccept(body -> body.equals(MAIN_RESPONSE_BODY)) .join(); ConcurrentMap>> promises = new ConcurrentHashMap<>(); PushPromiseHandler pph = PushPromiseHandler .of((r) -> BodyHandlers.ofString(), promises); HttpResponse main = client.sendAsync( HttpRequest.newBuilder(uri).build(), BodyHandlers.ofString(), pph) .join(); promises.entrySet().stream().forEach(e -> System.out.println(e.getKey() + ":" + e.getValue().join().body())); promises.putIfAbsent(main.request(), CompletableFuture.completedFuture(main)); promises.entrySet().stream().forEach(entry -> { HttpRequest request = entry.getKey(); HttpResponse response = entry.getValue().join(); assertEquals(response.statusCode(), 200); if (PUSH_PROMISES.containsKey(request.uri().getPath())) { assertEquals(response.body(), PUSH_PROMISES.get(request.uri().getPath())); } else { assertEquals(response.body(), MAIN_RESPONSE_BODY); } } ); } // --- server push handler --- static class ServerPushHandler implements Http2Handler { private final String mainResponseBody; private final Map promises; public ServerPushHandler(String mainResponseBody, Map promises) throws Exception { Objects.requireNonNull(promises); this.mainResponseBody = mainResponseBody; this.promises = promises; } public void handle(Http2TestExchange exchange) throws IOException { System.err.println("Server: handle " + exchange); try (InputStream is = exchange.getRequestBody()) { is.readAllBytes(); } if (exchange.serverPushAllowed()) { pushPromises(exchange); } // response data for the main response try (OutputStream os = exchange.getResponseBody()) { byte[] bytes = mainResponseBody.getBytes(UTF_8); exchange.sendResponseHeaders(200, bytes.length); os.write(bytes); } } private void pushPromises(Http2TestExchange exchange) throws IOException { URI requestURI = exchange.getRequestURI(); for (Map.Entry promise : promises.entrySet()) { URI uri = requestURI.resolve(promise.getKey()); InputStream is = new ByteArrayInputStream(promise.getValue().getBytes(UTF_8)); HttpHeaders headers = HttpHeaders.of(Collections.emptyMap(), (x, y) -> true); exchange.serverPush(uri, headers, is); } System.err.println("Server: All pushes sent"); } } }