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