Path: blob/master/test/jdk/java/net/httpclient/AbstractThrowingSubscribers.java
66644 views
/*1* Copyright (c) 2018, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation.7*8* This code is distributed in the hope that it will be useful, but WITHOUT9* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or10* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License11* version 2 for more details (a copy is included in the LICENSE file that12* accompanied this code).13*14* You should have received a copy of the GNU General Public License version15* 2 along with this work; if not, write to the Free Software Foundation,16* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.17*18* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA19* or visit www.oracle.com if you need additional information or have any20* questions.21*/2223import com.sun.net.httpserver.HttpServer;24import com.sun.net.httpserver.HttpsConfigurator;25import com.sun.net.httpserver.HttpsServer;26import jdk.test.lib.net.SimpleSSLContext;27import org.testng.ITestContext;28import org.testng.ITestResult;29import org.testng.SkipException;30import org.testng.annotations.AfterTest;31import org.testng.annotations.AfterClass;32import org.testng.annotations.BeforeMethod;33import org.testng.annotations.BeforeTest;34import org.testng.annotations.DataProvider;35import org.testng.annotations.Test;3637import javax.net.ssl.SSLContext;38import java.io.BufferedReader;39import java.io.IOException;40import java.io.InputStream;41import java.io.InputStreamReader;42import java.io.OutputStream;43import java.io.UncheckedIOException;44import java.net.InetAddress;45import java.net.InetSocketAddress;46import java.net.URI;47import java.net.http.HttpClient;48import java.net.http.HttpHeaders;49import java.net.http.HttpRequest;50import java.net.http.HttpResponse;51import java.net.http.HttpResponse.BodyHandler;52import java.net.http.HttpResponse.BodyHandlers;53import java.net.http.HttpResponse.BodySubscriber;54import java.nio.ByteBuffer;55import java.nio.charset.StandardCharsets;56import java.util.Arrays;57import java.util.EnumSet;58import java.util.List;59import java.util.concurrent.CompletableFuture;60import java.util.concurrent.CompletionStage;61import java.util.concurrent.ConcurrentHashMap;62import java.util.concurrent.ConcurrentMap;63import java.util.concurrent.Executor;64import java.util.concurrent.Executors;65import java.util.concurrent.Flow;66import java.util.concurrent.atomic.AtomicLong;67import java.util.concurrent.atomic.AtomicReference;68import java.util.function.Consumer;69import java.util.function.Predicate;70import java.util.function.Supplier;71import java.util.stream.Collectors;72import java.util.stream.Stream;7374import static java.lang.System.out;75import static java.lang.String.format;76import static java.nio.charset.StandardCharsets.UTF_8;77import static org.testng.Assert.assertEquals;78import static org.testng.Assert.assertTrue;7980public abstract class AbstractThrowingSubscribers implements HttpServerAdapters {8182SSLContext sslContext;83HttpTestServer httpTestServer; // HTTP/1.1 [ 4 servers ]84HttpTestServer httpsTestServer; // HTTPS/1.185HttpTestServer http2TestServer; // HTTP/2 ( h2c )86HttpTestServer https2TestServer; // HTTP/2 ( h2 )87String httpURI_fixed;88String httpURI_chunk;89String httpsURI_fixed;90String httpsURI_chunk;91String http2URI_fixed;92String http2URI_chunk;93String https2URI_fixed;94String https2URI_chunk;9596static final int ITERATION_COUNT = 1;97// a shared executor helps reduce the amount of threads created by the test98static final Executor executor = new TestExecutor(Executors.newCachedThreadPool());99static final ConcurrentMap<String, Throwable> FAILURES = new ConcurrentHashMap<>();100static volatile boolean tasksFailed;101static final AtomicLong serverCount = new AtomicLong();102static final AtomicLong clientCount = new AtomicLong();103static final long start = System.nanoTime();104public static String now() {105long now = System.nanoTime() - start;106long secs = now / 1000_000_000;107long mill = (now % 1000_000_000) / 1000_000;108long nan = now % 1000_000;109return String.format("[%d s, %d ms, %d ns] ", secs, mill, nan);110}111112final ReferenceTracker TRACKER = ReferenceTracker.INSTANCE;113private volatile HttpClient sharedClient;114115static class TestExecutor implements Executor {116final AtomicLong tasks = new AtomicLong();117Executor executor;118TestExecutor(Executor executor) {119this.executor = executor;120}121122@Override123public void execute(Runnable command) {124long id = tasks.incrementAndGet();125executor.execute(() -> {126try {127command.run();128} catch (Throwable t) {129tasksFailed = true;130System.out.printf(now() + "Task %s failed: %s%n", id, t);131System.err.printf(now() + "Task %s failed: %s%n", id, t);132FAILURES.putIfAbsent("Task " + id, t);133throw t;134}135});136}137}138139protected boolean stopAfterFirstFailure() {140return Boolean.getBoolean("jdk.internal.httpclient.debug");141}142143final AtomicReference<SkipException> skiptests = new AtomicReference<>();144void checkSkip() {145var skip = skiptests.get();146if (skip != null) throw skip;147}148static String name(ITestResult result) {149var params = result.getParameters();150return result.getName()151+ (params == null ? "()" : Arrays.toString(result.getParameters()));152}153154@BeforeMethod155void beforeMethod(ITestContext context) {156if (stopAfterFirstFailure() && context.getFailedTests().size() > 0) {157if (skiptests.get() == null) {158SkipException skip = new SkipException("some tests failed");159skip.setStackTrace(new StackTraceElement[0]);160skiptests.compareAndSet(null, skip);161}162}163}164165@AfterClass166static final void printFailedTests(ITestContext context) {167out.println("\n=========================");168try {169// Exceptions should already have been added to FAILURES170// var failed = context.getFailedTests().getAllResults().stream()171// .collect(Collectors.toMap(r -> name(r), ITestResult::getThrowable));172// FAILURES.putAll(failed);173174out.printf("%n%sCreated %d servers and %d clients%n",175now(), serverCount.get(), clientCount.get());176if (FAILURES.isEmpty()) return;177out.println("Failed tests: ");178FAILURES.entrySet().forEach((e) -> {179out.printf("\t%s: %s%n", e.getKey(), e.getValue());180e.getValue().printStackTrace(out);181e.getValue().printStackTrace();182});183if (tasksFailed) {184System.out.println("WARNING: Some tasks failed");185}186} finally {187out.println("\n=========================\n");188}189}190191private String[] uris() {192return new String[] {193httpURI_fixed,194httpURI_chunk,195httpsURI_fixed,196httpsURI_chunk,197http2URI_fixed,198http2URI_chunk,199https2URI_fixed,200https2URI_chunk,201};202}203204static AtomicLong URICOUNT = new AtomicLong();205206@DataProvider(name = "sanity")207public Object[][] sanity() {208String[] uris = uris();209Object[][] result = new Object[uris.length * 2][];210int i = 0;211for (boolean sameClient : List.of(false, true)) {212for (String uri: uris()) {213result[i++] = new Object[] {uri, sameClient};214}215}216assert i == uris.length * 2;217return result;218}219220@DataProvider(name = "variants")221public Object[][] variants(ITestContext context) {222if (stopAfterFirstFailure() && context.getFailedTests().size() > 0) {223return new Object[0][];224}225String[] uris = uris();226Object[][] result = new Object[uris.length * 2 * 2][];227int i = 0;228for (Thrower thrower : List.of(229new UncheckedIOExceptionThrower(),230new UncheckedCustomExceptionThrower())) {231for (boolean sameClient : List.of(false, true)) {232for (String uri : uris()) {233result[i++] = new Object[]{uri, sameClient, thrower};234}235}236}237assert i == uris.length * 2 * 2;238return result;239}240241private HttpClient makeNewClient() {242clientCount.incrementAndGet();243HttpClient client = HttpClient.newBuilder()244.proxy(HttpClient.Builder.NO_PROXY)245.executor(executor)246.sslContext(sslContext)247.build();248return TRACKER.track(client);249}250251HttpClient newHttpClient(boolean share) {252if (!share) return makeNewClient();253HttpClient shared = sharedClient;254if (shared != null) return shared;255synchronized (this) {256shared = sharedClient;257if (shared == null) {258shared = sharedClient = makeNewClient();259}260return shared;261}262}263264enum SubscriberType {265INLINE, // In line subscribers complete their CF on ON_COMPLETE266// e.g. BodySubscribers::ofString267OFFLINE; // Off line subscribers complete their CF immediately268// but require the client to pull the data after the269// CF completes (e.g. BodySubscribers::ofInputStream)270}271272static EnumSet<Where> excludes(SubscriberType type) {273EnumSet<Where> set = EnumSet.noneOf(Where.class);274275if (type == SubscriberType.OFFLINE) {276// Throwing on onSubscribe needs some more work277// for the case of InputStream, where the body has already278// completed by the time the subscriber is subscribed.279// The only way we have at that point to relay the exception280// is to call onError on the subscriber, but should we if281// Subscriber::onSubscribed has thrown an exception and282// not completed normally?283set.add(Where.ON_SUBSCRIBE);284}285286// Don't know how to make the stack reliably cause onError287// to be called without closing the connection.288// And how do we get the exception if onError throws anyway?289set.add(Where.ON_ERROR);290291return set;292}293294//@Test(dataProvider = "sanity")295protected void testSanityImpl(String uri, boolean sameClient)296throws Exception {297HttpClient client = null;298String uri2 = uri + "-" + URICOUNT.incrementAndGet() + "/sanity";299out.printf("%ntestSanity(%s, %b)%n", uri2, sameClient);300for (int i=0; i< ITERATION_COUNT; i++) {301if (!sameClient || client == null)302client = newHttpClient(sameClient);303304HttpRequest req = HttpRequest.newBuilder(URI.create(uri2))305.build();306BodyHandler<String> handler =307new ThrowingBodyHandler((w) -> {},308BodyHandlers.ofString());309HttpResponse<String> response = client.send(req, handler);310String body = response.body();311assertEquals(URI.create(body).getPath(), URI.create(uri2).getPath());312}313}314315//@Test(dataProvider = "variants")316protected void testThrowingAsStringImpl(String uri,317boolean sameClient,318Thrower thrower)319throws Exception320{321uri = uri + "-" + URICOUNT.incrementAndGet();322String test = format("testThrowingAsString(%s, %b, %s)",323uri, sameClient, thrower);324testThrowing(test, uri, sameClient, BodyHandlers::ofString,325this::shouldHaveThrown, thrower,false,326excludes(SubscriberType.INLINE));327}328329//@Test(dataProvider = "variants")330protected void testThrowingAsLinesImpl(String uri,331boolean sameClient,332Thrower thrower)333throws Exception334{335uri = uri + "-" + URICOUNT.incrementAndGet();336String test = format("testThrowingAsLines(%s, %b, %s)",337uri, sameClient, thrower);338testThrowing(test, uri, sameClient, BodyHandlers::ofLines,339this::checkAsLines, thrower,false,340excludes(SubscriberType.OFFLINE));341}342343//@Test(dataProvider = "variants")344protected void testThrowingAsInputStreamImpl(String uri,345boolean sameClient,346Thrower thrower)347throws Exception348{349uri = uri + "-" + URICOUNT.incrementAndGet();350String test = format("testThrowingAsInputStream(%s, %b, %s)",351uri, sameClient, thrower);352testThrowing(test, uri, sameClient, BodyHandlers::ofInputStream,353this::checkAsInputStream, thrower,false,354excludes(SubscriberType.OFFLINE));355}356357//@Test(dataProvider = "variants")358protected void testThrowingAsStringAsyncImpl(String uri,359boolean sameClient,360Thrower thrower)361throws Exception362{363uri = uri + "-" + URICOUNT.incrementAndGet();364String test = format("testThrowingAsStringAsync(%s, %b, %s)",365uri, sameClient, thrower);366testThrowing(test, uri, sameClient, BodyHandlers::ofString,367this::shouldHaveThrown, thrower, true,368excludes(SubscriberType.INLINE));369}370371//@Test(dataProvider = "variants")372protected void testThrowingAsLinesAsyncImpl(String uri,373boolean sameClient,374Thrower thrower)375throws Exception376{377uri = uri + "-" + URICOUNT.incrementAndGet();378String test = format("testThrowingAsLinesAsync(%s, %b, %s)",379uri, sameClient, thrower);380testThrowing(test, uri, sameClient, BodyHandlers::ofLines,381this::checkAsLines, thrower,true,382excludes(SubscriberType.OFFLINE));383}384385//@Test(dataProvider = "variants")386protected void testThrowingAsInputStreamAsyncImpl(String uri,387boolean sameClient,388Thrower thrower)389throws Exception390{391uri = uri + "-" + URICOUNT.incrementAndGet();392String test = format("testThrowingAsInputStreamAsync(%s, %b, %s)",393uri, sameClient, thrower);394testThrowing(test, uri, sameClient, BodyHandlers::ofInputStream,395this::checkAsInputStream, thrower,true,396excludes(SubscriberType.OFFLINE));397}398399private <T,U> void testThrowing(String name, String uri, boolean sameClient,400Supplier<BodyHandler<T>> handlers,401Finisher finisher, Thrower thrower,402boolean async, EnumSet<Where> excludes)403throws Exception404{405checkSkip();406out.printf("%n%s%s%n", now(), name);407try {408testThrowing(uri, sameClient, handlers, finisher, thrower, async, excludes);409} catch (Error | Exception x) {410FAILURES.putIfAbsent(name, x);411throw x;412}413}414415private <T,U> void testThrowing(String uri, boolean sameClient,416Supplier<BodyHandler<T>> handlers,417Finisher finisher, Thrower thrower,418boolean async,419EnumSet<Where> excludes)420throws Exception421{422HttpClient client = null;423for (Where where : EnumSet.complementOf(excludes)) {424425if (!sameClient || client == null)426client = newHttpClient(sameClient);427String uri2 = uri + "-" + where;428HttpRequest req = HttpRequest.429newBuilder(URI.create(uri2))430.build();431BodyHandler<T> handler =432new ThrowingBodyHandler(where.select(thrower), handlers.get());433System.out.println("try throwing in " + where);434HttpResponse<T> response = null;435if (async) {436try {437response = client.sendAsync(req, handler).join();438} catch (Error | Exception x) {439Throwable cause = findCause(x, thrower);440if (cause == null) throw causeNotFound(where, x);441System.out.println(now() + "Got expected exception: " + cause);442}443} else {444try {445response = client.send(req, handler);446} catch (Error | Exception t) {447// synchronous send will rethrow exceptions448Throwable throwable = t.getCause();449assert throwable != null;450451if (thrower.test(throwable)) {452System.out.println(now() + "Got expected exception: " + throwable);453} else throw causeNotFound(where, t);454}455}456if (response != null) {457finisher.finish(where, response, thrower);458}459}460}461462enum Where {463BODY_HANDLER, ON_SUBSCRIBE, ON_NEXT, ON_COMPLETE, ON_ERROR, GET_BODY, BODY_CF;464public Consumer<Where> select(Consumer<Where> consumer) {465return new Consumer<Where>() {466@Override467public void accept(Where where) {468if (Where.this == where) {469consumer.accept(where);470}471}472};473}474}475476static AssertionError causeNotFound(Where w, Throwable t) {477return new AssertionError("Expected exception not found in " + w, t);478}479480interface Thrower extends Consumer<Where>, Predicate<Throwable> {481482}483484interface Finisher<T,U> {485U finish(Where w, HttpResponse<T> resp, Thrower thrower) throws IOException;486}487488final <T,U> U shouldHaveThrown(Where w, HttpResponse<T> resp, Thrower thrower) {489String msg = "Expected exception not thrown in " + w490+ "\n\tReceived: " + resp491+ "\n\tWith body: " + resp.body();492System.out.println(msg);493throw new RuntimeException(msg);494}495496final List<String> checkAsLines(Where w, HttpResponse<Stream<String>> resp, Thrower thrower) {497switch(w) {498case BODY_HANDLER: return shouldHaveThrown(w, resp, thrower);499case GET_BODY: return shouldHaveThrown(w, resp, thrower);500case BODY_CF: return shouldHaveThrown(w, resp, thrower);501default: break;502}503List<String> result = null;504try {505result = resp.body().collect(Collectors.toList());506} catch (Error | Exception x) {507Throwable cause = findCause(x, thrower);508if (cause != null) {509out.println(now() + "Got expected exception in " + w + ": " + cause);510return result;511}512throw causeNotFound(w, x);513}514return shouldHaveThrown(w, resp, thrower);515}516517final List<String> checkAsInputStream(Where w, HttpResponse<InputStream> resp,518Thrower thrower)519throws IOException520{521switch(w) {522case BODY_HANDLER: return shouldHaveThrown(w, resp, thrower);523case GET_BODY: return shouldHaveThrown(w, resp, thrower);524case BODY_CF: return shouldHaveThrown(w, resp, thrower);525default: break;526}527List<String> result = null;528try (InputStreamReader r1 = new InputStreamReader(resp.body(), UTF_8);529BufferedReader r = new BufferedReader(r1)) {530try {531result = r.lines().collect(Collectors.toList());532} catch (Error | Exception x) {533Throwable cause = findCause(x, thrower);534if (cause != null) {535out.println(now() + "Got expected exception in " + w + ": " + cause);536return result;537}538throw causeNotFound(w, x);539}540}541return shouldHaveThrown(w, resp, thrower);542}543544private static Throwable findCause(Throwable x,545Predicate<Throwable> filter) {546while (x != null && !filter.test(x)) x = x.getCause();547return x;548}549550static final class UncheckedCustomExceptionThrower implements Thrower {551@Override552public void accept(Where where) {553out.println(now() + "Throwing in " + where);554throw new UncheckedCustomException(where.name());555}556557@Override558public boolean test(Throwable throwable) {559return UncheckedCustomException.class.isInstance(throwable);560}561562@Override563public String toString() {564return "UncheckedCustomExceptionThrower";565}566}567568static final class UncheckedIOExceptionThrower implements Thrower {569@Override570public void accept(Where where) {571out.println(now() + "Throwing in " + where);572throw new UncheckedIOException(new CustomIOException(where.name()));573}574575@Override576public boolean test(Throwable throwable) {577return UncheckedIOException.class.isInstance(throwable)578&& CustomIOException.class.isInstance(throwable.getCause());579}580581@Override582public String toString() {583return "UncheckedIOExceptionThrower";584}585}586587static final class UncheckedCustomException extends RuntimeException {588UncheckedCustomException(String message) {589super(message);590}591UncheckedCustomException(String message, Throwable cause) {592super(message, cause);593}594}595596static final class CustomIOException extends IOException {597CustomIOException(String message) {598super(message);599}600CustomIOException(String message, Throwable cause) {601super(message, cause);602}603}604605static final class ThrowingBodyHandler<T> implements BodyHandler<T> {606final Consumer<Where> throwing;607final BodyHandler<T> bodyHandler;608ThrowingBodyHandler(Consumer<Where> throwing, BodyHandler<T> bodyHandler) {609this.throwing = throwing;610this.bodyHandler = bodyHandler;611}612@Override613public BodySubscriber<T> apply(HttpResponse.ResponseInfo rinfo) {614throwing.accept(Where.BODY_HANDLER);615BodySubscriber<T> subscriber = bodyHandler.apply(rinfo);616return new ThrowingBodySubscriber(throwing, subscriber);617}618}619620static final class ThrowingBodySubscriber<T> implements BodySubscriber<T> {621private final BodySubscriber<T> subscriber;622volatile boolean onSubscribeCalled;623final Consumer<Where> throwing;624ThrowingBodySubscriber(Consumer<Where> throwing, BodySubscriber<T> subscriber) {625this.throwing = throwing;626this.subscriber = subscriber;627}628629@Override630public void onSubscribe(Flow.Subscription subscription) {631//out.println("onSubscribe ");632onSubscribeCalled = true;633throwing.accept(Where.ON_SUBSCRIBE);634subscriber.onSubscribe(subscription);635}636637@Override638public void onNext(List<ByteBuffer> item) {639// out.println("onNext " + item);640assertTrue(onSubscribeCalled);641throwing.accept(Where.ON_NEXT);642subscriber.onNext(item);643}644645@Override646public void onError(Throwable throwable) {647//out.println("onError");648assertTrue(onSubscribeCalled);649throwing.accept(Where.ON_ERROR);650subscriber.onError(throwable);651}652653@Override654public void onComplete() {655//out.println("onComplete");656assertTrue(onSubscribeCalled, "onComplete called before onSubscribe");657throwing.accept(Where.ON_COMPLETE);658subscriber.onComplete();659}660661@Override662public CompletionStage<T> getBody() {663throwing.accept(Where.GET_BODY);664try {665throwing.accept(Where.BODY_CF);666} catch (Throwable t) {667return CompletableFuture.failedFuture(t);668}669return subscriber.getBody();670}671}672673674@BeforeTest675public void setup() throws Exception {676sslContext = new SimpleSSLContext().get();677if (sslContext == null)678throw new AssertionError("Unexpected null sslContext");679680// HTTP/1.1681HttpTestHandler h1_fixedLengthHandler = new HTTP_FixedLengthHandler();682HttpTestHandler h1_chunkHandler = new HTTP_ChunkedHandler();683InetSocketAddress sa = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0);684httpTestServer = HttpTestServer.of(HttpServer.create(sa, 0));685httpTestServer.addHandler(h1_fixedLengthHandler, "/http1/fixed");686httpTestServer.addHandler(h1_chunkHandler, "/http1/chunk");687httpURI_fixed = "http://" + httpTestServer.serverAuthority() + "/http1/fixed/x";688httpURI_chunk = "http://" + httpTestServer.serverAuthority() + "/http1/chunk/x";689690HttpsServer httpsServer = HttpsServer.create(sa, 0);691httpsServer.setHttpsConfigurator(new HttpsConfigurator(sslContext));692httpsTestServer = HttpTestServer.of(httpsServer);693httpsTestServer.addHandler(h1_fixedLengthHandler, "/https1/fixed");694httpsTestServer.addHandler(h1_chunkHandler, "/https1/chunk");695httpsURI_fixed = "https://" + httpsTestServer.serverAuthority() + "/https1/fixed/x";696httpsURI_chunk = "https://" + httpsTestServer.serverAuthority() + "/https1/chunk/x";697698// HTTP/2699HttpTestHandler h2_fixedLengthHandler = new HTTP_FixedLengthHandler();700HttpTestHandler h2_chunkedHandler = new HTTP_ChunkedHandler();701702http2TestServer = HttpTestServer.of(new Http2TestServer("localhost", false, 0));703http2TestServer.addHandler(h2_fixedLengthHandler, "/http2/fixed");704http2TestServer.addHandler(h2_chunkedHandler, "/http2/chunk");705http2URI_fixed = "http://" + http2TestServer.serverAuthority() + "/http2/fixed/x";706http2URI_chunk = "http://" + http2TestServer.serverAuthority() + "/http2/chunk/x";707708https2TestServer = HttpTestServer.of(new Http2TestServer("localhost", true, sslContext));709https2TestServer.addHandler(h2_fixedLengthHandler, "/https2/fixed");710https2TestServer.addHandler(h2_chunkedHandler, "/https2/chunk");711https2URI_fixed = "https://" + https2TestServer.serverAuthority() + "/https2/fixed/x";712https2URI_chunk = "https://" + https2TestServer.serverAuthority() + "/https2/chunk/x";713714serverCount.addAndGet(4);715httpTestServer.start();716httpsTestServer.start();717http2TestServer.start();718https2TestServer.start();719}720721@AfterTest722public void teardown() throws Exception {723String sharedClientName =724sharedClient == null ? null : sharedClient.toString();725sharedClient = null;726Thread.sleep(100);727AssertionError fail = TRACKER.check(500);728try {729httpTestServer.stop();730httpsTestServer.stop();731http2TestServer.stop();732https2TestServer.stop();733} finally {734if (fail != null) {735if (sharedClientName != null) {736System.err.println("Shared client name is: " + sharedClientName);737}738throw fail;739}740}741}742743static class HTTP_FixedLengthHandler implements HttpTestHandler {744@Override745public void handle(HttpTestExchange t) throws IOException {746out.println("HTTP_FixedLengthHandler received request to " + t.getRequestURI());747try (InputStream is = t.getRequestBody()) {748is.readAllBytes();749}750byte[] resp = t.getRequestURI().toString().getBytes(StandardCharsets.UTF_8);751t.sendResponseHeaders(200, resp.length); //fixed content length752try (OutputStream os = t.getResponseBody()) {753os.write(resp);754}755}756}757758static class HTTP_ChunkedHandler implements HttpTestHandler {759@Override760public void handle(HttpTestExchange t) throws IOException {761out.println("HTTP_ChunkedHandler received request to " + t.getRequestURI());762byte[] resp = t.getRequestURI().toString().getBytes(StandardCharsets.UTF_8);763try (InputStream is = t.getRequestBody()) {764is.readAllBytes();765}766t.sendResponseHeaders(200, -1); // chunked/variable767try (OutputStream os = t.getResponseBody()) {768os.write(resp);769}770}771}772773}774775776