/* * Copyright (c) 2019, 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. */ import com.sun.net.httpserver.HttpsServer; import jdk.httpclient.test.lib.common.TestServerConfigurator; import jdk.test.lib.net.SimpleSSLContext; import javax.net.ssl.SSLContext; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Proxy; import java.net.ProxySelector; import java.net.SocketAddress; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.time.Duration; import java.util.List; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.ExecutorService; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import jdk.httpclient.test.lib.common.HttpServerAdapters; import static java.net.http.HttpClient.Version.HTTP_1_1; import static java.net.http.HttpClient.Version.HTTP_2; /** * @test * @bug 8231449 * @summary This test verifies that the HttpClient works correctly when the server * sends large amount of data. Note that this test will pass even without * the fix for JDK-8231449, which is unfortunate. * @library /test/lib /test/jdk/java/net/httpclient/lib * @build jdk.httpclient.test.lib.common.HttpServerAdapters * jdk.test.lib.net.SimpleSSLContext DigestEchoServer * jdk.httpclient.test.lib.common.TestServerConfigurator * @run main/othervm -Dtest.requiresHost=true * -Djdk.httpclient.HttpClient.log=headers * -Djdk.internal.httpclient.debug=true * LargeResponseTest * */ public class LargeResponseTest implements HttpServerAdapters { static final byte[] DATA; static { DATA = new byte[64 * 1024]; int len = 'z' - 'a'; for (int i=0; i < DATA.length; i++) { DATA[i] = (byte) ('a' + (i % len)); } } static final SSLContext context; static { try { context = new SimpleSSLContext().get(); SSLContext.setDefault(context); } catch (Exception x) { throw new ExceptionInInitializerError(x); } } final AtomicLong requestCounter = new AtomicLong(); final AtomicLong responseCounter = new AtomicLong(); HttpTestServer http1Server; HttpTestServer http2Server; HttpTestServer https1Server; HttpTestServer https2Server; DigestEchoServer.TunnelingProxy proxy; URI http1URI; URI https1URI; URI http2URI; URI https2URI; InetSocketAddress proxyAddress; ProxySelector proxySelector; HttpClient client; List> futures = new CopyOnWriteArrayList<>(); Set pending = new CopyOnWriteArraySet<>(); final ExecutorService executor = new ThreadPoolExecutor(12, 60, 10, TimeUnit.SECONDS, new LinkedBlockingQueue<>()); final ExecutorService clientexec = new ThreadPoolExecutor(6, 12, 1, TimeUnit.SECONDS, new LinkedBlockingQueue<>()); public HttpClient newHttpClient(ProxySelector ps) { HttpClient.Builder builder = HttpClient .newBuilder() .sslContext(context) .executor(clientexec) .proxy(ps); return builder.build(); } public void setUp() throws Exception { try { InetSocketAddress sa = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0); // HTTP/1.1 http1Server = HttpTestServer.create(HTTP_1_1, null, executor); http1Server.addHandler(new HttpTestLargeHandler(), "/LargeResponseTest/http1/"); http1Server.start(); http1URI = new URI("http://" + http1Server.serverAuthority() + "/LargeResponseTest/http1/"); // HTTPS/1.1 HttpsServer sserver1 = HttpsServer.create(sa, 100); sserver1.setExecutor(executor); sserver1.setHttpsConfigurator(new TestServerConfigurator(sa.getAddress(), context)); https1Server = HttpTestServer.of(sserver1); https1Server.addHandler(new HttpTestLargeHandler(), "/LargeResponseTest/https1/"); https1Server.start(); https1URI = new URI("https://" + https1Server.serverAuthority() + "/LargeResponseTest/https1/"); // HTTP/2.0 http2Server = HttpTestServer.create(HTTP_2); http2Server.addHandler(new HttpTestLargeHandler(), "/LargeResponseTest/http2/"); http2Server.start(); http2URI = new URI("http://" + http2Server.serverAuthority() + "/LargeResponseTest/http2/"); // HTTPS/2.0 https2Server = HttpTestServer.create(HTTP_2, SSLContext.getDefault()); https2Server.addHandler(new HttpTestLargeHandler(), "/LargeResponseTest/https2/"); https2Server.start(); https2URI = new URI("https://" + https2Server.serverAuthority() + "/LargeResponseTest/https2/"); proxy = DigestEchoServer.createHttpsProxyTunnel( DigestEchoServer.HttpAuthSchemeType.NONE); proxyAddress = proxy.getProxyAddress(); proxySelector = new HttpProxySelector(proxyAddress); client = newHttpClient(proxySelector); System.out.println("Setup: done"); } catch (Exception x) { tearDown(); throw x; } catch (Error e) { tearDown(); throw e; } } public static void main(String[] args) throws Exception { LargeResponseTest test = new LargeResponseTest(); test.setUp(); long start = System.nanoTime(); try { test.run(args); } finally { try { long elapsed = System.nanoTime() - start; System.out.println("*** Elapsed: " + Duration.ofNanos(elapsed)); } finally { test.tearDown(); } } } public void run(String... args) throws Exception { List serverURIs = List.of(http1URI, http2URI, https1URI, https2URI); for (int i=0; i<5; i++) { for (URI base : serverURIs) { if (base.getScheme().equalsIgnoreCase("https")) { URI proxy = i % 1 == 0 ? base.resolve(URI.create("proxy/foo?n="+requestCounter.incrementAndGet())) : base.resolve(URI.create("direct/foo?n="+requestCounter.incrementAndGet())); test(proxy); } } for (URI base : serverURIs) { URI direct = base.resolve(URI.create("direct/foo?n="+requestCounter.incrementAndGet())); test(direct); } } CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join(); } public void test(URI uri) throws Exception { System.out.println("Testing with " + uri); pending.add(uri); HttpRequest request = HttpRequest.newBuilder(uri).build(); CompletableFuture> resp = client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) .whenComplete((r, t) -> this.requestCompleted(request, r, t)); futures.add(resp); } private void requestCompleted(HttpRequest request, HttpResponse r, Throwable t) { responseCounter.incrementAndGet(); pending.remove(request.uri()); System.out.println(request + " -> " + (t == null ? r : t) + " [still pending: " + (requestCounter.get() - responseCounter.get()) +"]"); if (pending.size() < 10 && requestCounter.get() > 10) { pending.forEach(u -> System.out.println("\tpending: " + u)); } } public void tearDown() { proxy = stop(proxy, DigestEchoServer.TunnelingProxy::stop); http1Server = stop(http1Server, HttpTestServer::stop); https1Server = stop(https1Server, HttpTestServer::stop); http2Server = stop(http2Server, HttpTestServer::stop); https2Server = stop(https2Server, HttpTestServer::stop); client = null; try { executor.awaitTermination(2000, TimeUnit.MILLISECONDS); } catch (Throwable x) { } finally { executor.shutdownNow(); } try { clientexec.awaitTermination(2000, TimeUnit.MILLISECONDS); } catch (Throwable x) { } finally { clientexec.shutdownNow(); } System.out.println("Teardown: done"); } private interface Stoppable { public void stop(T service) throws Exception; } static T stop(T service, Stoppable stop) { try { if (service != null) stop.stop(service); } catch (Throwable x) { }; return null; } static class HttpProxySelector extends ProxySelector { private static final List NO_PROXY = List.of(Proxy.NO_PROXY); private final List proxyList; HttpProxySelector(InetSocketAddress proxyAddress) { proxyList = List.of(new Proxy(Proxy.Type.HTTP, proxyAddress)); } @Override public List select(URI uri) { // our proxy only supports tunneling if (uri.getScheme().equalsIgnoreCase("https")) { if (uri.getPath().contains("/proxy/")) { return proxyList; } } return NO_PROXY; } @Override public void connectFailed(URI uri, SocketAddress sa, IOException ioe) { System.err.println("Connection to proxy failed: " + ioe); System.err.println("Proxy: " + sa); System.err.println("\tURI: " + uri); ioe.printStackTrace(); } } public static class HttpTestLargeHandler implements HttpTestHandler { @Override public void handle(HttpTestExchange t) throws IOException { try (InputStream is = t.getRequestBody(); OutputStream os = t.getResponseBody()) { byte[] bytes = is.readAllBytes(); assert bytes.length == 0; URI u = t.getRequestURI(); long responseID = Long.parseLong(u.getQuery().substring(2)); System.out.println("Server " + t.getRequestURI() + " sending response " + responseID); t.sendResponseHeaders(200, DATA.length * 3); for (int i=0; i<3; i++) { os.write(DATA); } System.out.println("\tresp:" + responseID + ": done"); } } } }