Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/mobile
Path: blob/master/test/jdk/java/net/httpclient/DependentActionsTest.java
41149 views
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();
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;
136
TestExecutor(Executor executor) {
137
this.executor = executor;
138
}
139
140
@Override
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
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
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
194
public SemaphoreStaller get() {
195
return new SemaphoreStaller();
196
}
197
@Override
198
public String toString() {
199
return "SemaphoreStaller";
200
}
201
}
202
203
@DataProvider(name = "noStalls")
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")
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
234
private HttpClient makeNewClient() {
235
clientCount.incrementAndGet();
236
return HttpClient.newBuilder()
237
.executor(executor)
238
.sslContext(sslContext)
239
.build();
240
}
241
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")
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")
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")
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")
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
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
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;
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
662