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