1 /*
  2  * Copyright (c) 2019, 2023, 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 import com.sun.net.httpserver.HttpsServer;
 24 import jdk.httpclient.test.lib.common.TestServerConfigurator;
 25 import jdk.test.lib.net.SimpleSSLContext;
 26 
 27 import javax.net.ssl.SSLContext;
 28 import java.io.IOException;
 29 import java.io.InputStream;
 30 import java.io.OutputStream;
 31 import java.net.InetAddress;
 32 import java.net.InetSocketAddress;
 33 import java.net.Proxy;
 34 import java.net.ProxySelector;
 35 import java.net.SocketAddress;
 36 import java.net.URI;
 37 import java.net.http.HttpClient;
 38 import java.net.http.HttpRequest;
 39 import java.net.http.HttpResponse;
 40 import java.time.Duration;
 41 import java.util.List;
 42 import java.util.Set;
 43 import java.util.concurrent.CompletableFuture;
 44 import java.util.concurrent.CopyOnWriteArrayList;
 45 import java.util.concurrent.CopyOnWriteArraySet;
 46 import java.util.concurrent.ExecutorService;
 47 import java.util.concurrent.LinkedBlockingQueue;
 48 import java.util.concurrent.ThreadPoolExecutor;
 49 import java.util.concurrent.TimeUnit;
 50 import java.util.concurrent.atomic.AtomicLong;
 51 import jdk.httpclient.test.lib.common.HttpServerAdapters;
 52 import static java.net.http.HttpClient.Version.HTTP_1_1;
 53 import static java.net.http.HttpClient.Version.HTTP_2;
 54 
 55 /**
 56  * @test
 57  * @bug 8231449
 58  * @summary This test verifies that the HttpClient works correctly when the server
 59  *          sends large amount of data. Note that this test will pass even without
 60  *          the fix for JDK-8231449, which is unfortunate.
 61  * @library /test/lib /test/jdk/java/net/httpclient/lib
 62  * @build jdk.httpclient.test.lib.common.HttpServerAdapters
 63  *        jdk.test.lib.net.SimpleSSLContext DigestEchoServer
 64  *        jdk.httpclient.test.lib.common.TestServerConfigurator
 65  * @run main/othervm -Dtest.requiresHost=true
 66  *                   -Djdk.httpclient.HttpClient.log=headers
 67  *                   -Djdk.internal.httpclient.debug=true
 68  *                   LargeResponseTest
 69  *
 70  */
 71 public class LargeResponseTest implements HttpServerAdapters {
 72     static final byte[] DATA;
 73     static {
 74         DATA = new byte[64 * 1024];
 75         int len = 'z' - 'a';
 76         for (int i=0; i < DATA.length; i++) {
 77             DATA[i] = (byte) ('a' + (i % len));
 78         }
 79     }
 80 
 81     static final SSLContext context;
 82     static {
 83         try {
 84             context = new SimpleSSLContext().get();
 85             SSLContext.setDefault(context);
 86         } catch (Exception x) {
 87             throw new ExceptionInInitializerError(x);
 88         }
 89     }
 90 
 91     final AtomicLong requestCounter = new AtomicLong();
 92     final AtomicLong responseCounter = new AtomicLong();
 93     HttpTestServer http1Server;
 94     HttpTestServer http2Server;
 95     HttpTestServer https1Server;
 96     HttpTestServer https2Server;
 97     DigestEchoServer.TunnelingProxy proxy;
 98 
 99     URI http1URI;
100     URI https1URI;
101     URI http2URI;
102     URI https2URI;
103     InetSocketAddress proxyAddress;
104     ProxySelector proxySelector;
105     HttpClient client;
106     List<CompletableFuture<?>>  futures = new CopyOnWriteArrayList<>();
107     Set<URI> pending = new CopyOnWriteArraySet<>();
108 
109     final ExecutorService executor = new ThreadPoolExecutor(12, 60, 10,
110             TimeUnit.SECONDS, new LinkedBlockingQueue<>());
111     final ExecutorService clientexec = new ThreadPoolExecutor(6, 12, 1,
112             TimeUnit.SECONDS, new LinkedBlockingQueue<>());
113 
114     public HttpClient newHttpClient(ProxySelector ps) {
115         HttpClient.Builder builder = HttpClient
116                 .newBuilder()
117                 .sslContext(context)
118                 .executor(clientexec)
119                 .proxy(ps);
120         return builder.build();
121     }
122 
123     public void setUp() throws Exception {
124         try {
125             InetSocketAddress sa = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0);
126 
127             // HTTP/1.1
128             http1Server = HttpTestServer.create(HTTP_1_1, null, executor);
129             http1Server.addHandler(new HttpTestLargeHandler(), "/LargeResponseTest/http1/");
130             http1Server.start();
131             http1URI = new URI("http://" + http1Server.serverAuthority() + "/LargeResponseTest/http1/");
132 
133 
134             // HTTPS/1.1
135             HttpsServer sserver1 = HttpsServer.create(sa, 100);
136             sserver1.setExecutor(executor);
137             sserver1.setHttpsConfigurator(new TestServerConfigurator(sa.getAddress(), context));
138             https1Server = HttpTestServer.of(sserver1);
139             https1Server.addHandler(new HttpTestLargeHandler(), "/LargeResponseTest/https1/");
140             https1Server.start();
141             https1URI = new URI("https://" + https1Server.serverAuthority() + "/LargeResponseTest/https1/");
142 
143             // HTTP/2.0
144             http2Server = HttpTestServer.create(HTTP_2);
145             http2Server.addHandler(new HttpTestLargeHandler(), "/LargeResponseTest/http2/");
146             http2Server.start();
147             http2URI = new URI("http://" + http2Server.serverAuthority() + "/LargeResponseTest/http2/");
148 
149             // HTTPS/2.0
150             https2Server = HttpTestServer.create(HTTP_2, SSLContext.getDefault());
151             https2Server.addHandler(new HttpTestLargeHandler(), "/LargeResponseTest/https2/");
152             https2Server.start();
153             https2URI = new URI("https://" + https2Server.serverAuthority() + "/LargeResponseTest/https2/");
154 
155             proxy = DigestEchoServer.createHttpsProxyTunnel(
156                     DigestEchoServer.HttpAuthSchemeType.NONE);
157             proxyAddress = proxy.getProxyAddress();
158             proxySelector = new HttpProxySelector(proxyAddress);
159             client = newHttpClient(proxySelector);
160             System.out.println("Setup: done");
161         } catch (Exception x) {
162             tearDown(); throw x;
163         } catch (Error e) {
164             tearDown(); throw e;
165         }
166     }
167 
168     public static void main(String[] args) throws Exception {
169         LargeResponseTest test = new LargeResponseTest();
170         test.setUp();
171         long start = System.nanoTime();
172         try {
173             test.run(args);
174         } finally {
175             try {
176                 long elapsed = System.nanoTime() - start;
177                 System.out.println("*** Elapsed: " + Duration.ofNanos(elapsed));
178             } finally {
179                 test.tearDown();
180             }
181         }
182     }
183 
184     public void run(String... args) throws Exception {
185         List<URI> serverURIs = List.of(http1URI, http2URI, https1URI, https2URI);
186         for (int i=0; i<5; i++) {
187             for (URI base : serverURIs) {
188                 if (base.getScheme().equalsIgnoreCase("https")) {
189                     URI proxy = i % 1 == 0 ? base.resolve(URI.create("proxy/foo?n="+requestCounter.incrementAndGet()))
190                     : base.resolve(URI.create("direct/foo?n="+requestCounter.incrementAndGet()));
191                     test(proxy);
192                 }
193             }
194             for (URI base : serverURIs) {
195                 URI direct = base.resolve(URI.create("direct/foo?n="+requestCounter.incrementAndGet()));
196                 test(direct);
197             }
198         }
199         CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
200     }
201 
202     public void test(URI uri) throws Exception {
203         System.out.println("Testing with " + uri);
204         pending.add(uri);
205         HttpRequest request = HttpRequest.newBuilder(uri).build();
206         CompletableFuture<HttpResponse<String>> resp =
207                 client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
208                 .whenComplete((r, t) -> this.requestCompleted(request, r, t));
209         futures.add(resp);
210     }
211 
212     private void requestCompleted(HttpRequest request, HttpResponse<?> r, Throwable t) {
213         responseCounter.incrementAndGet();
214         pending.remove(request.uri());
215         System.out.println(request + " -> " + (t == null ? r : t)
216                 + " [still pending: " + (requestCounter.get() - responseCounter.get()) +"]");
217         if (pending.size() < 10 && requestCounter.get() > 10) {
218             pending.forEach(u -> System.out.println("\tpending: " + u));
219         }
220     }
221 
222     public void tearDown() {
223         proxy = stop(proxy, DigestEchoServer.TunnelingProxy::stop);
224         http1Server = stop(http1Server, HttpTestServer::stop);
225         https1Server = stop(https1Server, HttpTestServer::stop);
226         http2Server = stop(http2Server, HttpTestServer::stop);
227         https2Server = stop(https2Server, HttpTestServer::stop);
228         client = null;
229         try {
230             executor.awaitTermination(2000, TimeUnit.MILLISECONDS);
231         } catch (Throwable x) {
232         } finally {
233             executor.shutdownNow();
234         }
235         try {
236             clientexec.awaitTermination(2000, TimeUnit.MILLISECONDS);
237         } catch (Throwable x) {
238         } finally {
239             clientexec.shutdownNow();
240         }
241         System.out.println("Teardown: done");
242     }
243 
244     private interface Stoppable<T> { public void stop(T service) throws Exception; }
245 
246     static <T>  T stop(T service, Stoppable<T> stop) {
247         try { if (service != null) stop.stop(service); } catch (Throwable x) { };
248         return null;
249     }
250 
251     static class HttpProxySelector extends ProxySelector {
252         private static final List<Proxy> NO_PROXY = List.of(Proxy.NO_PROXY);
253         private final List<Proxy> proxyList;
254         HttpProxySelector(InetSocketAddress proxyAddress) {
255             proxyList = List.of(new Proxy(Proxy.Type.HTTP, proxyAddress));
256         }
257 
258         @Override
259         public List<Proxy> select(URI uri) {
260             // our proxy only supports tunneling
261             if (uri.getScheme().equalsIgnoreCase("https")) {
262                 if (uri.getPath().contains("/proxy/")) {
263                     return proxyList;
264                 }
265             }
266             return NO_PROXY;
267         }
268 
269         @Override
270         public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
271             System.err.println("Connection to proxy failed: " + ioe);
272             System.err.println("Proxy: " + sa);
273             System.err.println("\tURI: " + uri);
274             ioe.printStackTrace();
275         }
276     }
277 
278     public static class HttpTestLargeHandler implements HttpTestHandler {
279         @Override
280         public void handle(HttpTestExchange t) throws IOException {
281             try (InputStream is = t.getRequestBody();
282                  OutputStream os = t.getResponseBody()) {
283                 byte[] bytes = is.readAllBytes();
284                 assert bytes.length == 0;
285                 URI u = t.getRequestURI();
286                 long responseID = Long.parseLong(u.getQuery().substring(2));
287                 System.out.println("Server " + t.getRequestURI() + " sending response " + responseID);
288                 t.sendResponseHeaders(200, DATA.length * 3);
289                 for (int i=0; i<3; i++) {
290                     os.write(DATA);
291                 }
292                 System.out.println("\tresp:" + responseID + ": done");
293             }
294         }
295     }
296 
297 }