1 /*
2  * Copyright (c) 2018, 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 
24 /*
25  * @test
26  * @summary Verify that dependent synchronous actions added before the CF
27  *          completes are executed either asynchronously in an executor when the
28  *          CF later completes, or in the user thread that joins.
29  * @library /test/lib http2/server
30  * @build jdk.test.lib.net.SimpleSSLContext HttpServerAdapters DependentActionsTest
31  * @modules java.base/sun.net.www.http
32  *          java.net.http/jdk.internal.net.http.common
33  *          java.net.http/jdk.internal.net.http.frame
34  *          java.net.http/jdk.internal.net.http.hpack
35  * @run testng/othervm -Djdk.internal.httpclient.debug=true DependentActionsTest
36  * @run testng/othervm/java.security.policy=dependent.policy
37   *        -Djdk.internal.httpclient.debug=true DependentActionsTest
38  */
39 
40 import java.io.BufferedReader;
41 import java.io.InputStreamReader;
42 import java.lang.StackWalker.StackFrame;
43 import com.sun.net.httpserver.HttpServer;
44 import com.sun.net.httpserver.HttpsConfigurator;
45 import com.sun.net.httpserver.HttpsServer;
46 import jdk.test.lib.net.SimpleSSLContext;
47 import org.testng.annotations.AfterTest;
48 import org.testng.annotations.AfterClass;
49 import org.testng.annotations.BeforeTest;
50 import org.testng.annotations.DataProvider;
51 import org.testng.annotations.Test;
52 
53 import javax.net.ssl.SSLContext;
54 import java.io.IOException;
55 import java.io.InputStream;
56 import java.io.OutputStream;
57 import java.net.InetAddress;
58 import java.net.InetSocketAddress;
59 import java.net.URI;
60 import java.net.http.HttpClient;
61 import java.net.http.HttpHeaders;
62 import java.net.http.HttpRequest;
63 import java.net.http.HttpResponse;
64 import java.net.http.HttpResponse.BodyHandler;
65 import java.net.http.HttpResponse.BodyHandlers;
66 import java.net.http.HttpResponse.BodySubscriber;
67 import java.nio.ByteBuffer;
68 import java.nio.charset.StandardCharsets;
69 import java.util.EnumSet;
70 import java.util.List;
71 import java.util.Optional;
72 import java.util.concurrent.CompletableFuture;
73 import java.util.concurrent.CompletionException;
74 import java.util.concurrent.CompletionStage;
75 import java.util.concurrent.ConcurrentHashMap;
76 import java.util.concurrent.ConcurrentMap;
77 import java.util.concurrent.Executor;
78 import java.util.concurrent.Executors;
79 import java.util.concurrent.Flow;
80 import java.util.concurrent.Semaphore;
81 import java.util.concurrent.atomic.AtomicBoolean;
82 import java.util.concurrent.atomic.AtomicLong;
83 import java.util.concurrent.atomic.AtomicReference;
84 import java.util.function.Consumer;
85 import java.util.function.Predicate;
86 import java.util.function.Supplier;
87 import java.util.stream.Collectors;
88 import java.util.stream.Stream;
89 
90 import static java.lang.System.out;
91 import static java.lang.String.format;
92 import static java.util.stream.Collectors.toList;
93 import static org.testng.Assert.assertEquals;
94 import static org.testng.Assert.assertTrue;
95 
96 public class DependentActionsTest implements HttpServerAdapters {
97 
98     SSLContext sslContext;
99     HttpTestServer httpTestServer;    // HTTP/1.1    [ 4 servers ]
100     HttpTestServer httpsTestServer;   // HTTPS/1.1
101     HttpTestServer http2TestServer;   // HTTP/2 ( h2c )
102     HttpTestServer https2TestServer;  // HTTP/2 ( h2  )
103     String httpURI_fixed;
104     String httpURI_chunk;
105     String httpsURI_fixed;
106     String httpsURI_chunk;
107     String http2URI_fixed;
108     String http2URI_chunk;
109     String https2URI_fixed;
110     String https2URI_chunk;
111 
112     static final StackWalker WALKER =
113             StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE);
114 
115     static final int ITERATION_COUNT = 1;
116     // a shared executor helps reduce the amount of threads created by the test
117     static final Executor executor = new TestExecutor(Executors.newCachedThreadPool());
118     static final ConcurrentMap<String, Throwable> FAILURES = new ConcurrentHashMap<>();
119     static volatile boolean tasksFailed;
120     static final AtomicLong serverCount = new AtomicLong();
121     static final AtomicLong clientCount = new AtomicLong();
122     static final long start = System.nanoTime();
now()123     public static String now() {
124         long now = System.nanoTime() - start;
125         long secs = now / 1000_000_000;
126         long mill = (now % 1000_000_000) / 1000_000;
127         long nan = now % 1000_000;
128         return String.format("[%d s, %d ms, %d ns] ", secs, mill, nan);
129     }
130 
131     private volatile HttpClient sharedClient;
132 
133     static class TestExecutor implements Executor {
134         final AtomicLong tasks = new AtomicLong();
135         Executor executor;
TestExecutor(Executor executor)136         TestExecutor(Executor executor) {
137             this.executor = executor;
138         }
139 
140         @Override
execute(Runnable command)141         public void execute(Runnable command) {
142             long id = tasks.incrementAndGet();
143             executor.execute(() -> {
144                 try {
145                     command.run();
146                 } catch (Throwable t) {
147                     tasksFailed = true;
148                     System.out.printf(now() + "Task %s failed: %s%n", id, t);
149                     System.err.printf(now() + "Task %s failed: %s%n", id, t);
150                     FAILURES.putIfAbsent("Task " + id, t);
151                     throw t;
152                 }
153             });
154         }
155     }
156 
157     @AfterClass
printFailedTests()158     static final void printFailedTests() {
159         out.println("\n=========================");
160         try {
161             out.printf("%n%sCreated %d servers and %d clients%n",
162                     now(), serverCount.get(), clientCount.get());
163             if (FAILURES.isEmpty()) return;
164             out.println("Failed tests: ");
165             FAILURES.entrySet().forEach((e) -> {
166                 out.printf("\t%s: %s%n", e.getKey(), e.getValue());
167                 e.getValue().printStackTrace(out);
168                 e.getValue().printStackTrace();
169             });
170             if (tasksFailed) {
171                 System.out.println("WARNING: Some tasks failed");
172             }
173         } finally {
174             out.println("\n=========================\n");
175         }
176     }
177 
uris()178     private String[] uris() {
179         return new String[] {
180                 httpURI_fixed,
181                 httpURI_chunk,
182                 httpsURI_fixed,
183                 httpsURI_chunk,
184                 http2URI_fixed,
185                 http2URI_chunk,
186                 https2URI_fixed,
187                 https2URI_chunk,
188         };
189     }
190 
191     static final class SemaphoreStallerSupplier
192             implements Supplier<SemaphoreStaller> {
193         @Override
get()194         public SemaphoreStaller get() {
195             return new SemaphoreStaller();
196         }
197         @Override
toString()198         public String toString() {
199             return "SemaphoreStaller";
200         }
201     }
202 
203     @DataProvider(name = "noStalls")
noThrows()204     public Object[][] noThrows() {
205         String[] uris = uris();
206         Object[][] result = new Object[uris.length * 2][];
207         int i = 0;
208         for (boolean sameClient : List.of(false, true)) {
209             for (String uri: uris()) {
210                 result[i++] = new Object[] {uri, sameClient};
211             }
212         }
213         assert i == uris.length * 2;
214         return result;
215     }
216 
217     @DataProvider(name = "variants")
variants()218     public Object[][] variants() {
219         String[] uris = uris();
220         Object[][] result = new Object[uris.length * 2][];
221         int i = 0;
222         Supplier<? extends Staller> s = new SemaphoreStallerSupplier();
223         for (Supplier<? extends Staller> staller : List.of(s)) {
224             for (boolean sameClient : List.of(false, true)) {
225                 for (String uri : uris()) {
226                     result[i++] = new Object[]{uri, sameClient, staller};
227                 }
228             }
229         }
230         assert i == uris.length * 2;
231         return result;
232     }
233 
makeNewClient()234     private HttpClient makeNewClient() {
235         clientCount.incrementAndGet();
236         return HttpClient.newBuilder()
237                 .executor(executor)
238                 .sslContext(sslContext)
239                 .build();
240     }
241 
newHttpClient(boolean share)242     HttpClient newHttpClient(boolean share) {
243         if (!share) return makeNewClient();
244         HttpClient shared = sharedClient;
245         if (shared != null) return shared;
246         synchronized (this) {
247             shared = sharedClient;
248             if (shared == null) {
249                 shared = sharedClient = makeNewClient();
250             }
251             return shared;
252         }
253     }
254 
255     @Test(dataProvider = "noStalls")
testNoStalls(String uri, boolean sameClient)256     public void testNoStalls(String uri, boolean sameClient)
257             throws Exception {
258         HttpClient client = null;
259         out.printf("%ntestNoStalls(%s, %b)%n", uri, sameClient);
260         for (int i=0; i< ITERATION_COUNT; i++) {
261             if (!sameClient || client == null)
262                 client = newHttpClient(sameClient);
263 
264             HttpRequest req = HttpRequest.newBuilder(URI.create(uri))
265                     .build();
266             BodyHandler<String> handler =
267                     new StallingBodyHandler((w) -> {},
268                             BodyHandlers.ofString());
269             HttpResponse<String> response = client.send(req, handler);
270             String body = response.body();
271             assertEquals(URI.create(body).getPath(), URI.create(uri).getPath());
272         }
273     }
274 
275     @Test(dataProvider = "variants")
testAsStringAsync(String uri, boolean sameClient, Supplier<Staller> s)276     public void testAsStringAsync(String uri,
277                                   boolean sameClient,
278                                   Supplier<Staller> s)
279             throws Exception
280     {
281         Staller staller = s.get();
282         String test = format("testAsStringAsync(%s, %b, %s)",
283                 uri, sameClient, staller);
284         testDependent(test, uri, sameClient, BodyHandlers::ofString,
285                 this::finish, this::extractString, staller);
286     }
287 
288     @Test(dataProvider = "variants")
testAsLinesAsync(String uri, boolean sameClient, Supplier<Staller> s)289     public void testAsLinesAsync(String uri,
290                                  boolean sameClient,
291                                  Supplier<Staller> s)
292             throws Exception
293     {
294         Staller staller = s.get();
295         String test = format("testAsLinesAsync(%s, %b, %s)",
296                 uri, sameClient, staller);
297         testDependent(test, uri, sameClient, BodyHandlers::ofLines,
298                 this::finish, this::extractStream, staller);
299     }
300 
301     @Test(dataProvider = "variants")
testAsInputStreamAsync(String uri, boolean sameClient, Supplier<Staller> s)302     public void testAsInputStreamAsync(String uri,
303                                        boolean sameClient,
304                                        Supplier<Staller> s)
305             throws Exception
306     {
307         Staller staller = s.get();
308         String test = format("testAsInputStreamAsync(%s, %b, %s)",
309                 uri, sameClient, staller);
310         testDependent(test, uri, sameClient, BodyHandlers::ofInputStream,
311                 this::finish, this::extractInputStream, staller);
312     }
313 
testDependent(String name, String uri, boolean sameClient, Supplier<BodyHandler<T>> handlers, Finisher finisher, Extractor extractor, Staller staller)314     private <T,U> void testDependent(String name, String uri, boolean sameClient,
315                                      Supplier<BodyHandler<T>> handlers,
316                                      Finisher finisher,
317                                      Extractor extractor,
318                                      Staller staller)
319             throws Exception
320     {
321         out.printf("%n%s%s%n", now(), name);
322         try {
323             testDependent(uri, sameClient, handlers, finisher, extractor, staller);
324         } catch (Error | Exception x) {
325             FAILURES.putIfAbsent(name, x);
326             throw x;
327         }
328     }
329 
testDependent(String uri, boolean sameClient, Supplier<BodyHandler<T>> handlers, Finisher finisher, Extractor extractor, Staller staller)330     private <T,U> void testDependent(String uri, boolean sameClient,
331                                      Supplier<BodyHandler<T>> handlers,
332                                      Finisher finisher,
333                                      Extractor extractor,
334                                      Staller staller)
335             throws Exception
336     {
337         HttpClient client = null;
338         for (Where where : EnumSet.of(Where.BODY_HANDLER)) {
339             if (!sameClient || client == null)
340                 client = newHttpClient(sameClient);
341 
342             HttpRequest req = HttpRequest.
343                     newBuilder(URI.create(uri))
344                     .build();
345             BodyHandler<T> handler =
346                     new StallingBodyHandler(where.select(staller), handlers.get());
347             System.out.println("try stalling in " + where);
348             staller.acquire();
349             assert staller.willStall();
350             CompletableFuture<HttpResponse<T>> responseCF = client.sendAsync(req, handler);
351             assert !responseCF.isDone();
352             finisher.finish(where, responseCF, staller, extractor);
353         }
354     }
355 
356     enum Where {
357         BODY_HANDLER, ON_SUBSCRIBE, ON_NEXT, ON_COMPLETE, ON_ERROR, GET_BODY, BODY_CF;
select(Consumer<Where> consumer)358         public Consumer<Where> select(Consumer<Where> consumer) {
359             return new Consumer<Where>() {
360                 @Override
361                 public void accept(Where where) {
362                     if (Where.this == where) {
363                         consumer.accept(where);
364                     }
365                 }
366             };
367         }
368     }
369 
370     interface Extractor<T> {
371         public List<String> extract(HttpResponse<T> resp);
372     }
373 
374     final List<String> extractString(HttpResponse<String> resp) {
375         return List.of(resp.body());
376     }
377 
378     final List<String> extractStream(HttpResponse<Stream<String>> resp) {
379         return resp.body().collect(toList());
380     }
381 
382     final List<String> extractInputStream(HttpResponse<InputStream> resp) {
383         try (InputStream is = resp.body()) {
384             return new BufferedReader(new InputStreamReader(is))
385                     .lines().collect(toList());
386         } catch (IOException x) {
387             throw new CompletionException(x);
388         }
389     }
390 
391     interface Finisher<T> {
392         public void finish(Where w,
393                            CompletableFuture<HttpResponse<T>> cf,
394                            Staller staller,
395                            Extractor extractor);
396     }
397 
398     Optional<StackFrame> findFrame(Stream<StackFrame> s, String name) {
399         return s.filter((f) -> f.getClassName().contains(name))
400                 .filter((f) -> f.getDeclaringClass().getModule().equals(HttpClient.class.getModule()))
401                 .findFirst();
402     }
403 
404     static final Predicate<StackFrame> DAT = sfe ->
405             sfe.getClassName().startsWith("DependentActionsTest");
406     static final Predicate<StackFrame> JUC = sfe ->
407             sfe.getClassName().startsWith("java.util.concurrent");
408     static final Predicate<StackFrame> JLT = sfe ->
409             sfe.getClassName().startsWith("java.lang.Thread");
410     static final Predicate<StackFrame> NotDATorJUCorJLT = Predicate.not(DAT.or(JUC).or(JLT));
411 
412 
413     <T> void checkThreadAndStack(Thread thread,
414                                  AtomicReference<RuntimeException> failed,
415                                  T result,
416                                  Throwable error) {
417         //failed.set(new RuntimeException("Dependant action was executed in " + thread));
418         List<StackFrame> otherFrames = WALKER.walk(s -> s.filter(NotDATorJUCorJLT).collect(toList()));
419         if (!otherFrames.isEmpty()) {
420             System.out.println("Found unexpected trace: ");
421             otherFrames.forEach(f -> System.out.printf("\t%s%n", f));
422             failed.set(new RuntimeException("Dependant action has unexpected frame in " +
423                        Thread.currentThread() + ": " + otherFrames.get(0)));
424 
425         }
426     }
427 
428     <T> void finish(Where w, CompletableFuture<HttpResponse<T>> cf,
429                     Staller staller,
430                     Extractor<T> extractor) {
431         Thread thread = Thread.currentThread();
432         AtomicReference<RuntimeException> failed = new AtomicReference<>();
433         CompletableFuture<HttpResponse<T>> done = cf.whenComplete(
434                 (r,t) -> checkThreadAndStack(thread, failed, r, t));
435         assert !cf.isDone();
436         try {
437             Thread.sleep(100);
438         } catch (Throwable t) {/* don't care */}
439         assert !cf.isDone();
440         staller.release();
441         try {
442             HttpResponse<T> response = done.join();
443             List<String> result = extractor.extract(response);
444             RuntimeException error = failed.get();
445             if (error != null) {
446                 throw new RuntimeException("Test failed in "
447                         + w + ": " + response, error);
448             }
449             assertEquals(result, List.of(response.request().uri().getPath()));
450         } finally {
451             staller.reset();
452         }
453     }
454 
455     interface Staller extends Consumer<Where> {
456         void release();
457         void acquire();
458         void reset();
459         boolean willStall();
460     }
461 
462     static final class SemaphoreStaller implements Staller {
463         final Semaphore sem = new Semaphore(1);
464         @Override
465         public void accept(Where where) {
466             System.out.println("Acquiring semaphore in "
467                     + where + " permits=" + sem.availablePermits());
468             sem.acquireUninterruptibly();
469             System.out.println("Semaphored acquired in " + where);
470         }
471 
472         @Override
473         public void release() {
474             System.out.println("Releasing semaphore: permits="
475                     + sem.availablePermits());
476             sem.release();
477         }
478 
479         @Override
480         public void acquire() {
481             sem.acquireUninterruptibly();
482             System.out.println("Semaphored acquired");
483         }
484 
485         @Override
486         public void reset() {
487             System.out.println("Reseting semaphore: permits="
488                     + sem.availablePermits());
489             sem.drainPermits();
490             sem.release();
491             System.out.println("Semaphore reset: permits="
492                     + sem.availablePermits());
493         }
494 
495         @Override
496         public boolean willStall() {
497             return sem.availablePermits() <= 0;
498         }
499 
500         @Override
501         public String toString() {
502             return "SemaphoreStaller";
503         }
504     }
505 
506     static final class StallingBodyHandler<T> implements BodyHandler<T> {
507         final Consumer<Where> stalling;
508         final BodyHandler<T> bodyHandler;
509         StallingBodyHandler(Consumer<Where> stalling, BodyHandler<T> bodyHandler) {
510             this.stalling = stalling;
511             this.bodyHandler = bodyHandler;
512         }
513         @Override
514         public BodySubscriber<T> apply(HttpResponse.ResponseInfo rinfo) {
515             stalling.accept(Where.BODY_HANDLER);
516             BodySubscriber<T> subscriber = bodyHandler.apply(rinfo);
517             return new StallingBodySubscriber(stalling, subscriber);
518         }
519     }
520 
521     static final class StallingBodySubscriber<T> implements BodySubscriber<T> {
522         private final BodySubscriber<T> subscriber;
523         volatile boolean onSubscribeCalled;
524         final Consumer<Where> stalling;
525         StallingBodySubscriber(Consumer<Where> stalling, BodySubscriber<T> subscriber) {
526             this.stalling = stalling;
527             this.subscriber = subscriber;
528         }
529 
530         @Override
531         public void onSubscribe(Flow.Subscription subscription) {
532             //out.println("onSubscribe ");
533             onSubscribeCalled = true;
534             stalling.accept(Where.ON_SUBSCRIBE);
535             subscriber.onSubscribe(subscription);
536         }
537 
538         @Override
539         public void onNext(List<ByteBuffer> item) {
540             // out.println("onNext " + item);
541             assertTrue(onSubscribeCalled);
542             stalling.accept(Where.ON_NEXT);
543             subscriber.onNext(item);
544         }
545 
546         @Override
547         public void onError(Throwable throwable) {
548             //out.println("onError");
549             assertTrue(onSubscribeCalled);
550             stalling.accept(Where.ON_ERROR);
551             subscriber.onError(throwable);
552         }
553 
554         @Override
555         public void onComplete() {
556             //out.println("onComplete");
557             assertTrue(onSubscribeCalled, "onComplete called before onSubscribe");
558             stalling.accept(Where.ON_COMPLETE);
559             subscriber.onComplete();
560         }
561 
562         @Override
563         public CompletionStage<T> getBody() {
564             stalling.accept(Where.GET_BODY);
565             try {
566                 stalling.accept(Where.BODY_CF);
567             } catch (Throwable t) {
568                 return CompletableFuture.failedFuture(t);
569             }
570             return subscriber.getBody();
571         }
572     }
573 
574 
575     @BeforeTest
576     public void setup() throws Exception {
577         sslContext = new SimpleSSLContext().get();
578         if (sslContext == null)
579             throw new AssertionError("Unexpected null sslContext");
580 
581         // HTTP/1.1
582         HttpTestHandler h1_fixedLengthHandler = new HTTP_FixedLengthHandler();
583         HttpTestHandler h1_chunkHandler = new HTTP_ChunkedHandler();
584         InetSocketAddress sa = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0);
585         httpTestServer = HttpTestServer.of(HttpServer.create(sa, 0));
586         httpTestServer.addHandler(h1_fixedLengthHandler, "/http1/fixed");
587         httpTestServer.addHandler(h1_chunkHandler, "/http1/chunk");
588         httpURI_fixed = "http://" + httpTestServer.serverAuthority() + "/http1/fixed/x";
589         httpURI_chunk = "http://" + httpTestServer.serverAuthority() + "/http1/chunk/x";
590 
591         HttpsServer httpsServer = HttpsServer.create(sa, 0);
592         httpsServer.setHttpsConfigurator(new HttpsConfigurator(sslContext));
593         httpsTestServer = HttpTestServer.of(httpsServer);
594         httpsTestServer.addHandler(h1_fixedLengthHandler, "/https1/fixed");
595         httpsTestServer.addHandler(h1_chunkHandler, "/https1/chunk");
596         httpsURI_fixed = "https://" + httpsTestServer.serverAuthority() + "/https1/fixed/x";
597         httpsURI_chunk = "https://" + httpsTestServer.serverAuthority() + "/https1/chunk/x";
598 
599         // HTTP/2
600         HttpTestHandler h2_fixedLengthHandler = new HTTP_FixedLengthHandler();
601         HttpTestHandler h2_chunkedHandler = new HTTP_ChunkedHandler();
602 
603         http2TestServer = HttpTestServer.of(new Http2TestServer("localhost", false, 0));
604         http2TestServer.addHandler(h2_fixedLengthHandler, "/http2/fixed");
605         http2TestServer.addHandler(h2_chunkedHandler, "/http2/chunk");
606         http2URI_fixed = "http://" + http2TestServer.serverAuthority() + "/http2/fixed/x";
607         http2URI_chunk = "http://" + http2TestServer.serverAuthority() + "/http2/chunk/x";
608 
609         https2TestServer = HttpTestServer.of(new Http2TestServer("localhost", true, sslContext));
610         https2TestServer.addHandler(h2_fixedLengthHandler, "/https2/fixed");
611         https2TestServer.addHandler(h2_chunkedHandler, "/https2/chunk");
612         https2URI_fixed = "https://" + https2TestServer.serverAuthority() + "/https2/fixed/x";
613         https2URI_chunk = "https://" + https2TestServer.serverAuthority() + "/https2/chunk/x";
614 
615         serverCount.addAndGet(4);
616         httpTestServer.start();
617         httpsTestServer.start();
618         http2TestServer.start();
619         https2TestServer.start();
620     }
621 
622     @AfterTest
623     public void teardown() throws Exception {
624         sharedClient = null;
625         httpTestServer.stop();
626         httpsTestServer.stop();
627         http2TestServer.stop();
628         https2TestServer.stop();
629     }
630 
631     static class HTTP_FixedLengthHandler implements HttpTestHandler {
632         @Override
633         public void handle(HttpTestExchange t) throws IOException {
634             out.println("HTTP_FixedLengthHandler received request to " + t.getRequestURI());
635             try (InputStream is = t.getRequestBody()) {
636                 is.readAllBytes();
637             }
638             byte[] resp = t.getRequestURI().getPath().getBytes(StandardCharsets.UTF_8);
639             t.sendResponseHeaders(200, resp.length);  //fixed content length
640             try (OutputStream os = t.getResponseBody()) {
641                 os.write(resp);
642             }
643         }
644     }
645 
646     static class HTTP_ChunkedHandler implements HttpTestHandler {
647         @Override
648         public void handle(HttpTestExchange t) throws IOException {
649             out.println("HTTP_ChunkedHandler received request to " + t.getRequestURI());
650             byte[] resp = t.getRequestURI().getPath().toString().getBytes(StandardCharsets.UTF_8);
651             try (InputStream is = t.getRequestBody()) {
652                 is.readAllBytes();
653             }
654             t.sendResponseHeaders(200, -1); // chunked/variable
655             try (OutputStream os = t.getResponseBody()) {
656                 os.write(resp);
657             }
658         }
659     }
660 }
661