/* * 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 * @bug 8196389 * @summary Should HttpClient support SETTINGS_MAX_CONCURRENT_STREAMS from the server * @library /test/lib /test/jdk/java/net/httpclient/lib * @build jdk.httpclient.test.lib.http2.Http2TestServer jdk.test.lib.net.SimpleSSLContext * @run testng/othervm MaxStreams */ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.URI; import java.util.List; import java.util.LinkedList; import java.util.Properties; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executors; import java.util.concurrent.ExecutorService; import javax.net.ssl.SSLContext; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.net.http.HttpResponse.BodyHandlers; import jdk.httpclient.test.lib.http2.Http2TestServer; import jdk.httpclient.test.lib.http2.Http2TestExchange; import jdk.httpclient.test.lib.http2.Http2Handler; import jdk.test.lib.net.SimpleSSLContext; import org.testng.annotations.AfterTest; import org.testng.annotations.BeforeTest; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.fail; public class MaxStreams { Http2TestServer http2TestServer; // HTTP/2 ( h2c ) Http2TestServer https2TestServer; // HTTP/2 ( h2 ) final Http2FixedHandler handler = new Http2FixedHandler(); SSLContext ctx; String http2FixedURI; String https2FixedURI; ExecutorService exec; // we send an initial warm up request, then MAX_STREAMS+1 requests // in parallel. The last of them should hit the limit. // Then we wait for all the responses and send a further request // which should succeed. The server should see (and respond to) // MAX_STREAMS+2 requests per test run. static final int MAX_STREAMS = 10; static final String RESPONSE = "Hello world"; @DataProvider(name = "uris") public Object[][] variants() { return new Object[][]{ {http2FixedURI}, {https2FixedURI}, {http2FixedURI}, {https2FixedURI} }; } @Test(dataProvider = "uris") void testAsString(String uri) throws Exception { CountDownLatch latch = new CountDownLatch(1); handler.setLatch(latch); HttpClient client = HttpClient.newBuilder().sslContext(ctx).build(); List>> responses = new LinkedList<>(); HttpRequest request = HttpRequest.newBuilder(URI.create(uri)) .version(HttpClient.Version.HTTP_2) .GET() .build(); // send warmup to ensure we only have one Http2Connection System.err.println("Sending warmup request"); HttpResponse warmup = client.send(request, BodyHandlers.ofString()); if (warmup.statusCode() != 200 || !warmup.body().equals(RESPONSE)) throw new RuntimeException(); for (int i=0;i[0])).join(); } catch (Exception ee) { System.err.println("Expected exception 1 " + ee); } latch.countDown(); // check the first MAX_STREAMS requests succeeded try { System.err.println("Waiting for second exception"); CompletableFuture.allOf(responses.toArray(new CompletableFuture[0])).join(); System.err.println("Did not get Expected exception 2 "); } catch (Exception ee) { System.err.println("Expected exception 2 " + ee); } int count = 0; int failures = 0; for (CompletableFuture> cf : responses) { HttpResponse r = null; try { count++; r = cf.join(); if (r.statusCode() != 200 || !r.body().equals(RESPONSE)) throw new RuntimeException(); } catch (Throwable t) { failures++; System.err.printf("Failure %d at count %d\n", failures, count); System.err.println(t); t.printStackTrace(); } } if (failures != 1) { String msg = "Expected 1 failure. Got " + failures; throw new RuntimeException(msg); } System.err.println("Sending last request"); // make sure it succeeds now as number of streams == 0 now HttpResponse warmdown = client.send(request, BodyHandlers.ofString()); if (warmdown.statusCode() != 200 || !warmdown.body().equals(RESPONSE)) throw new RuntimeException(); System.err.println("Test OK"); } @BeforeTest public void setup() throws Exception { ctx = (new SimpleSSLContext()).get(); exec = Executors.newCachedThreadPool(); InetSocketAddress sa = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0); Properties props = new Properties(); props.setProperty("http2server.settings.max_concurrent_streams", Integer.toString(MAX_STREAMS)); http2TestServer = new Http2TestServer("localhost", false, 0, exec, 10, props, null); http2TestServer.addHandler(handler, "/http2/fixed"); http2FixedURI = "http://" + http2TestServer.serverAuthority()+ "/http2/fixed"; http2TestServer.start(); https2TestServer = new Http2TestServer("localhost", true, 0, exec, 10, props, ctx); https2TestServer.addHandler(handler, "/https2/fixed"); https2FixedURI = "https://" + https2TestServer.serverAuthority()+ "/https2/fixed"; https2TestServer.start(); } @AfterTest public void teardown() throws Exception { System.err.println("Stopping test server now"); http2TestServer.stop(); } class Http2FixedHandler implements Http2Handler { final AtomicInteger counter = new AtomicInteger(0); volatile CountDownLatch latch; synchronized void setLatch(CountDownLatch latch) { this.latch = latch; } synchronized CountDownLatch getLatch() { return latch; } @Override public void handle(Http2TestExchange t) throws IOException { try (InputStream is = t.getRequestBody(); OutputStream os = t.getResponseBody()) { is.readAllBytes(); int c = counter.getAndIncrement(); if (c > 0 && c <= MAX_STREAMS) { // Wait for latch. try { // don't send any replies until all requests are sent System.err.println("Latch await"); getLatch().await(); System.err.println("Latch resume"); } catch (InterruptedException ee) {} } else if (c == MAX_STREAMS + 1) { // client issues MAX_STREAMS + 3 requests in total // but server should only see MAX_STREAMS + 2 in total. One is rejected by client // counter c captured before increment so final value is MAX_STREAMS + 1 System.err.println("Counter reset"); counter.set(0); } t.sendResponseHeaders(200, RESPONSE.length()); os.write(RESPONSE.getBytes()); } } } }