Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/mobile
Path: blob/master/src/java.net.http/share/classes/jdk/internal/net/http/Exchange.java
41171 views
1
/*
2
* Copyright (c) 2015, 2021, 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. Oracle designates this
8
* particular file as subject to the "Classpath" exception as provided
9
* by Oracle in the LICENSE file that accompanied this code.
10
*
11
* This code is distributed in the hope that it will be useful, but WITHOUT
12
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14
* version 2 for more details (a copy is included in the LICENSE file that
15
* accompanied this code).
16
*
17
* You should have received a copy of the GNU General Public License version
18
* 2 along with this work; if not, write to the Free Software Foundation,
19
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20
*
21
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22
* or visit www.oracle.com if you need additional information or have any
23
* questions.
24
*/
25
26
package jdk.internal.net.http;
27
28
import java.io.IOException;
29
import java.lang.System.Logger.Level;
30
import java.net.InetSocketAddress;
31
import java.net.ProxySelector;
32
import java.net.URI;
33
import java.net.URISyntaxException;
34
import java.net.URLPermission;
35
import java.security.AccessControlContext;
36
import java.time.Duration;
37
import java.util.List;
38
import java.util.Map;
39
import java.util.Optional;
40
import java.util.concurrent.CompletableFuture;
41
import java.util.concurrent.Executor;
42
import java.util.function.Function;
43
import java.net.http.HttpClient;
44
import java.net.http.HttpHeaders;
45
import java.net.http.HttpResponse;
46
import java.net.http.HttpTimeoutException;
47
48
import jdk.internal.net.http.common.Logger;
49
import jdk.internal.net.http.common.MinimalFuture;
50
import jdk.internal.net.http.common.Utils;
51
import jdk.internal.net.http.common.Log;
52
53
import static jdk.internal.net.http.common.Utils.permissionForProxy;
54
55
/**
56
* One request/response exchange (handles 100/101 intermediate response also).
57
* depth field used to track number of times a new request is being sent
58
* for a given API request. If limit exceeded exception is thrown.
59
*
60
* Security check is performed here:
61
* - uses AccessControlContext captured at API level
62
* - checks for appropriate URLPermission for request
63
* - if permission allowed, grants equivalent SocketPermission to call
64
* - in case of direct HTTP proxy, checks additionally for access to proxy
65
* (CONNECT proxying uses its own Exchange, so check done there)
66
*
67
*/
68
final class Exchange<T> {
69
70
final Logger debug = Utils.getDebugLogger(this::dbgString, Utils.DEBUG);
71
72
final HttpRequestImpl request;
73
final HttpClientImpl client;
74
volatile ExchangeImpl<T> exchImpl;
75
volatile CompletableFuture<? extends ExchangeImpl<T>> exchangeCF;
76
volatile CompletableFuture<Void> bodyIgnored;
77
78
// used to record possible cancellation raised before the exchImpl
79
// has been established.
80
private volatile IOException failed;
81
@SuppressWarnings("removal")
82
final AccessControlContext acc;
83
final MultiExchange<T> multi;
84
final Executor parentExecutor;
85
volatile boolean upgrading; // to HTTP/2
86
volatile boolean upgraded; // to HTTP/2
87
final PushGroup<T> pushGroup;
88
final String dbgTag;
89
90
// Keeps track of the underlying connection when establishing an HTTP/2
91
// exchange so that it can be aborted/timed out mid setup.
92
final ConnectionAborter connectionAborter = new ConnectionAborter();
93
94
Exchange(HttpRequestImpl request, MultiExchange<T> multi) {
95
this.request = request;
96
this.upgrading = false;
97
this.client = multi.client();
98
this.multi = multi;
99
this.acc = multi.acc;
100
this.parentExecutor = multi.executor;
101
this.pushGroup = multi.pushGroup;
102
this.dbgTag = "Exchange";
103
}
104
105
/* If different AccessControlContext to be used */
106
Exchange(HttpRequestImpl request,
107
MultiExchange<T> multi,
108
@SuppressWarnings("removal") AccessControlContext acc)
109
{
110
this.request = request;
111
this.acc = acc;
112
this.upgrading = false;
113
this.client = multi.client();
114
this.multi = multi;
115
this.parentExecutor = multi.executor;
116
this.pushGroup = multi.pushGroup;
117
this.dbgTag = "Exchange";
118
}
119
120
PushGroup<T> getPushGroup() {
121
return pushGroup;
122
}
123
124
Executor executor() {
125
return parentExecutor;
126
}
127
128
public HttpRequestImpl request() {
129
return request;
130
}
131
132
public Optional<Duration> remainingConnectTimeout() {
133
return multi.remainingConnectTimeout();
134
}
135
136
HttpClientImpl client() {
137
return client;
138
}
139
140
// Keeps track of the underlying connection when establishing an HTTP/2
141
// exchange so that it can be aborted/timed out mid setup.
142
static final class ConnectionAborter {
143
private volatile HttpConnection connection;
144
private volatile boolean closeRequested;
145
146
void connection(HttpConnection connection) {
147
this.connection = connection;
148
if (closeRequested) closeConnection();
149
}
150
151
void closeConnection() {
152
closeRequested = true;
153
HttpConnection connection = this.connection;
154
this.connection = null;
155
if (connection != null) {
156
try {
157
connection.close();
158
} catch (Throwable t) {
159
// ignore
160
}
161
}
162
}
163
164
void disable() {
165
connection = null;
166
closeRequested = false;
167
}
168
}
169
170
// Called for 204 response - when no body is permitted
171
// This is actually only needed for HTTP/1.1 in order
172
// to return the connection to the pool (or close it)
173
void nullBody(HttpResponse<T> resp, Throwable t) {
174
exchImpl.nullBody(resp, t);
175
}
176
177
public CompletableFuture<T> readBodyAsync(HttpResponse.BodyHandler<T> handler) {
178
// If we received a 407 while establishing the exchange
179
// there will be no body to read: bodyIgnored will be true,
180
// and exchImpl will be null (if we were trying to establish
181
// an HTTP/2 tunnel through an HTTP/1.1 proxy)
182
if (bodyIgnored != null) return MinimalFuture.completedFuture(null);
183
184
// The connection will not be returned to the pool in the case of WebSocket
185
return exchImpl.readBodyAsync(handler, !request.isWebSocket(), parentExecutor)
186
.whenComplete((r,t) -> exchImpl.completed());
187
}
188
189
/**
190
* Called after a redirect or similar kind of retry where a body might
191
* be sent but we don't want it. Should send a RESET in h2. For http/1.1
192
* we can consume small quantity of data, or close the connection in
193
* other cases.
194
*/
195
public CompletableFuture<Void> ignoreBody() {
196
if (bodyIgnored != null) return bodyIgnored;
197
return exchImpl.ignoreBody();
198
}
199
200
/**
201
* Called when a new exchange is created to replace this exchange.
202
* At this point it is guaranteed that readBody/readBodyAsync will
203
* not be called.
204
*/
205
public void released() {
206
ExchangeImpl<?> impl = exchImpl;
207
if (impl != null) impl.released();
208
// Don't set exchImpl to null here. We need to keep
209
// it alive until it's replaced by a Stream in wrapForUpgrade.
210
// Setting it to null here might get it GC'ed too early, because
211
// the Http1Response is now only weakly referenced by the Selector.
212
}
213
214
public void cancel() {
215
// cancel can be called concurrently before or at the same time
216
// that the exchange impl is being established.
217
// In that case we won't be able to propagate the cancellation
218
// right away
219
if (exchImpl != null) {
220
exchImpl.cancel();
221
} else {
222
// no impl - can't cancel impl yet.
223
// call cancel(IOException) instead which takes care
224
// of race conditions between impl/cancel.
225
cancel(new IOException("Request cancelled"));
226
}
227
}
228
229
public void cancel(IOException cause) {
230
if (debug.on()) debug.log("cancel exchImpl: %s, with \"%s\"", exchImpl, cause);
231
// If the impl is non null, propagate the exception right away.
232
// Otherwise record it so that it can be propagated once the
233
// exchange impl has been established.
234
ExchangeImpl<?> impl = exchImpl;
235
if (impl != null) {
236
// propagate the exception to the impl
237
if (debug.on()) debug.log("Cancelling exchImpl: %s", exchImpl);
238
impl.cancel(cause);
239
} else {
240
// no impl yet. record the exception
241
failed = cause;
242
243
// abort/close the connection if setting up the exchange. This can
244
// be important when setting up HTTP/2
245
connectionAborter.closeConnection();
246
247
// now call checkCancelled to recheck the impl.
248
// if the failed state is set and the impl is not null, reset
249
// the failed state and propagate the exception to the impl.
250
checkCancelled();
251
}
252
}
253
254
// This method will raise an exception if one was reported and if
255
// it is possible to do so. If the exception can be raised, then
256
// the failed state will be reset. Otherwise, the failed state
257
// will persist until the exception can be raised and the failed state
258
// can be cleared.
259
// Takes care of possible race conditions.
260
private void checkCancelled() {
261
ExchangeImpl<?> impl = null;
262
IOException cause = null;
263
CompletableFuture<? extends ExchangeImpl<T>> cf = null;
264
if (failed != null) {
265
synchronized(this) {
266
cause = failed;
267
impl = exchImpl;
268
cf = exchangeCF;
269
}
270
}
271
if (cause == null) return;
272
if (impl != null) {
273
// The exception is raised by propagating it to the impl.
274
if (debug.on()) debug.log("Cancelling exchImpl: %s", impl);
275
impl.cancel(cause);
276
failed = null;
277
} else {
278
Log.logTrace("Exchange: request [{0}/timeout={1}ms] no impl is set."
279
+ "\n\tCan''t cancel yet with {2}",
280
request.uri(),
281
request.timeout().isPresent() ?
282
// calling duration.toMillis() can throw an exception.
283
// this is just debugging, we don't care if it overflows.
284
(request.timeout().get().getSeconds() * 1000
285
+ request.timeout().get().getNano() / 1000000) : -1,
286
cause);
287
if (cf != null) cf.completeExceptionally(cause);
288
}
289
}
290
291
<T> CompletableFuture<T> checkCancelled(CompletableFuture<T> cf, HttpConnection connection) {
292
return cf.handle((r,t) -> {
293
if (t == null) {
294
if (multi.requestCancelled()) {
295
// if upgraded, we don't close the connection.
296
// cancelling will be handled by the HTTP/2 exchange
297
// in its own time.
298
if (!upgraded) {
299
t = getCancelCause();
300
if (t == null) t = new IOException("Request cancelled");
301
if (debug.on()) debug.log("exchange cancelled during connect: " + t);
302
try {
303
connection.close();
304
} catch (Throwable x) {
305
if (debug.on()) debug.log("Failed to close connection", x);
306
}
307
return MinimalFuture.<T>failedFuture(t);
308
}
309
}
310
}
311
return cf;
312
}).thenCompose(Function.identity());
313
}
314
315
public void h2Upgrade() {
316
upgrading = true;
317
request.setH2Upgrade(client.client2());
318
}
319
320
synchronized IOException getCancelCause() {
321
return failed;
322
}
323
324
// get/set the exchange impl, solving race condition issues with
325
// potential concurrent calls to cancel() or cancel(IOException)
326
private CompletableFuture<? extends ExchangeImpl<T>>
327
establishExchange(HttpConnection connection) {
328
if (debug.on()) {
329
debug.log("establishing exchange for %s,%n\t proxy=%s",
330
request, request.proxy());
331
}
332
// check if we have been cancelled first.
333
Throwable t = getCancelCause();
334
checkCancelled();
335
if (t != null) {
336
if (debug.on()) {
337
debug.log("exchange was cancelled: returned failed cf (%s)", String.valueOf(t));
338
}
339
return exchangeCF = MinimalFuture.failedFuture(t);
340
}
341
342
CompletableFuture<? extends ExchangeImpl<T>> cf, res;
343
cf = ExchangeImpl.get(this, connection);
344
// We should probably use a VarHandle to get/set exchangeCF
345
// instead - as we need CAS semantics.
346
synchronized (this) { exchangeCF = cf; };
347
res = cf.whenComplete((r,x) -> {
348
synchronized(Exchange.this) {
349
if (exchangeCF == cf) exchangeCF = null;
350
}
351
});
352
checkCancelled();
353
return res.thenCompose((eimpl) -> {
354
// recheck for cancelled, in case of race conditions
355
exchImpl = eimpl;
356
IOException tt = getCancelCause();
357
checkCancelled();
358
if (tt != null) {
359
return MinimalFuture.failedFuture(tt);
360
} else {
361
// Now we're good to go. Because exchImpl is no longer
362
// null cancel() will be able to propagate directly to
363
// the impl after this point ( if needed ).
364
return MinimalFuture.completedFuture(eimpl);
365
} });
366
}
367
368
// Completed HttpResponse will be null if response succeeded
369
// will be a non null responseAsync if expect continue returns an error
370
371
public CompletableFuture<Response> responseAsync() {
372
return responseAsyncImpl(null);
373
}
374
375
CompletableFuture<Response> responseAsyncImpl(HttpConnection connection) {
376
SecurityException e = checkPermissions();
377
if (e != null) {
378
return MinimalFuture.failedFuture(e);
379
} else {
380
return responseAsyncImpl0(connection);
381
}
382
}
383
384
// check whether the headersSentCF was completed exceptionally with
385
// ProxyAuthorizationRequired. If so the Response embedded in the
386
// exception is returned. Otherwise we proceed.
387
private CompletableFuture<Response> checkFor407(ExchangeImpl<T> ex, Throwable t,
388
Function<ExchangeImpl<T>,CompletableFuture<Response>> andThen) {
389
t = Utils.getCompletionCause(t);
390
if (t instanceof ProxyAuthenticationRequired) {
391
if (debug.on()) debug.log("checkFor407: ProxyAuthenticationRequired: building synthetic response");
392
bodyIgnored = MinimalFuture.completedFuture(null);
393
Response proxyResponse = ((ProxyAuthenticationRequired)t).proxyResponse;
394
HttpConnection c = ex == null ? null : ex.connection();
395
Response syntheticResponse = new Response(request, this,
396
proxyResponse.headers, c, proxyResponse.statusCode,
397
proxyResponse.version, true);
398
return MinimalFuture.completedFuture(syntheticResponse);
399
} else if (t != null) {
400
if (debug.on()) debug.log("checkFor407: no response - %s", (Object)t);
401
return MinimalFuture.failedFuture(t);
402
} else {
403
if (debug.on()) debug.log("checkFor407: all clear");
404
return andThen.apply(ex);
405
}
406
}
407
408
// After sending the request headers, if no ProxyAuthorizationRequired
409
// was raised and the expectContinue flag is on, we need to wait
410
// for the 100-Continue response
411
private CompletableFuture<Response> expectContinue(ExchangeImpl<T> ex) {
412
assert request.expectContinue();
413
return ex.getResponseAsync(parentExecutor)
414
.thenCompose((Response r1) -> {
415
Log.logResponse(r1::toString);
416
int rcode = r1.statusCode();
417
if (rcode == 100) {
418
Log.logTrace("Received 100-Continue: sending body");
419
if (debug.on()) debug.log("Received 100-Continue for %s", r1);
420
CompletableFuture<Response> cf =
421
exchImpl.sendBodyAsync()
422
.thenCompose(exIm -> exIm.getResponseAsync(parentExecutor));
423
cf = wrapForUpgrade(cf);
424
cf = wrapForLog(cf);
425
return cf;
426
} else {
427
Log.logTrace("Expectation failed: Received {0}",
428
rcode);
429
if (debug.on()) debug.log("Expect-Continue failed (%d) for: %s", rcode, r1);
430
if (upgrading && rcode == 101) {
431
IOException failed = new IOException(
432
"Unable to handle 101 while waiting for 100");
433
return MinimalFuture.failedFuture(failed);
434
}
435
return exchImpl.readBodyAsync(this::ignoreBody, false, parentExecutor)
436
.thenApply(v -> r1);
437
}
438
});
439
}
440
441
// After sending the request headers, if no ProxyAuthorizationRequired
442
// was raised and the expectContinue flag is off, we can immediately
443
// send the request body and proceed.
444
private CompletableFuture<Response> sendRequestBody(ExchangeImpl<T> ex) {
445
assert !request.expectContinue();
446
if (debug.on()) debug.log("sendRequestBody");
447
CompletableFuture<Response> cf = ex.sendBodyAsync()
448
.thenCompose(exIm -> exIm.getResponseAsync(parentExecutor));
449
cf = wrapForUpgrade(cf);
450
cf = wrapForLog(cf);
451
return cf;
452
}
453
454
CompletableFuture<Response> responseAsyncImpl0(HttpConnection connection) {
455
Function<ExchangeImpl<T>, CompletableFuture<Response>> after407Check;
456
bodyIgnored = null;
457
if (request.expectContinue()) {
458
request.addSystemHeader("Expect", "100-Continue");
459
Log.logTrace("Sending Expect: 100-Continue");
460
// wait for 100-Continue before sending body
461
after407Check = this::expectContinue;
462
} else {
463
// send request body and proceed.
464
after407Check = this::sendRequestBody;
465
}
466
// The ProxyAuthorizationRequired can be triggered either by
467
// establishExchange (case of HTTP/2 SSL tunneling through HTTP/1.1 proxy
468
// or by sendHeaderAsync (case of HTTP/1.1 SSL tunneling through HTTP/1.1 proxy
469
// Therefore we handle it with a call to this checkFor407(...) after these
470
// two places.
471
Function<ExchangeImpl<T>, CompletableFuture<Response>> afterExch407Check =
472
(ex) -> ex.sendHeadersAsync()
473
.handle((r,t) -> this.checkFor407(r, t, after407Check))
474
.thenCompose(Function.identity());
475
return establishExchange(connection)
476
.handle((r,t) -> this.checkFor407(r,t, afterExch407Check))
477
.thenCompose(Function.identity());
478
}
479
480
private CompletableFuture<Response> wrapForUpgrade(CompletableFuture<Response> cf) {
481
if (upgrading) {
482
return cf.thenCompose(r -> checkForUpgradeAsync(r, exchImpl));
483
}
484
return cf;
485
}
486
487
private CompletableFuture<Response> wrapForLog(CompletableFuture<Response> cf) {
488
if (Log.requests()) {
489
return cf.thenApply(response -> {
490
Log.logResponse(response::toString);
491
return response;
492
});
493
}
494
return cf;
495
}
496
497
HttpResponse.BodySubscriber<T> ignoreBody(HttpResponse.ResponseInfo hdrs) {
498
return HttpResponse.BodySubscribers.replacing(null);
499
}
500
501
// if this response was received in reply to an upgrade
502
// then create the Http2Connection from the HttpConnection
503
// initialize it and wait for the real response on a newly created Stream
504
505
private CompletableFuture<Response>
506
checkForUpgradeAsync(Response resp,
507
ExchangeImpl<T> ex) {
508
509
int rcode = resp.statusCode();
510
if (upgrading && (rcode == 101)) {
511
Http1Exchange<T> e = (Http1Exchange<T>)ex;
512
// check for 101 switching protocols
513
// 101 responses are not supposed to contain a body.
514
// => should we fail if there is one?
515
if (debug.on()) debug.log("Upgrading async %s", e.connection());
516
return e.readBodyAsync(this::ignoreBody, false, parentExecutor)
517
.thenCompose((T v) -> {// v is null
518
debug.log("Ignored body");
519
// we pass e::getBuffer to allow the ByteBuffers to accumulate
520
// while we build the Http2Connection
521
ex.upgraded();
522
upgraded = true;
523
return Http2Connection.createAsync(e.connection(),
524
client.client2(),
525
this, e::drainLeftOverBytes)
526
.thenCompose((Http2Connection c) -> {
527
boolean cached = c.offerConnection();
528
if (cached) connectionAborter.disable();
529
Stream<T> s = c.getStream(1);
530
531
if (s == null) {
532
// s can be null if an exception occurred
533
// asynchronously while sending the preface.
534
Throwable t = c.getRecordedCause();
535
IOException ioe;
536
if (t != null) {
537
if (!cached)
538
c.close();
539
ioe = new IOException("Can't get stream 1: " + t, t);
540
} else {
541
ioe = new IOException("Can't get stream 1");
542
}
543
return MinimalFuture.failedFuture(ioe);
544
}
545
exchImpl.released();
546
Throwable t;
547
// There's a race condition window where an external
548
// thread (SelectorManager) might complete the
549
// exchange in timeout at the same time where we're
550
// trying to switch the exchange impl.
551
// 'failed' will be reset to null after
552
// exchImpl.cancel() has completed, so either we
553
// will observe failed != null here, or we will
554
// observe e.getCancelCause() != null, or the
555
// timeout exception will be routed to 's'.
556
// Either way, we need to relay it to s.
557
synchronized (this) {
558
exchImpl = s;
559
t = failed;
560
}
561
// Check whether the HTTP/1.1 was cancelled.
562
if (t == null) t = e.getCancelCause();
563
// if HTTP/1.1 exchange was timed out, or the request
564
// was cancelled don't try to go further.
565
if (t instanceof HttpTimeoutException || multi.requestCancelled()) {
566
if (t == null) t = new IOException("Request cancelled");
567
s.cancelImpl(t);
568
return MinimalFuture.failedFuture(t);
569
}
570
if (debug.on())
571
debug.log("Getting response async %s", s);
572
return s.getResponseAsync(null);
573
});}
574
);
575
}
576
return MinimalFuture.completedFuture(resp);
577
}
578
579
private URI getURIForSecurityCheck() {
580
URI u;
581
String method = request.method();
582
InetSocketAddress authority = request.authority();
583
URI uri = request.uri();
584
585
// CONNECT should be restricted at API level
586
if (method.equalsIgnoreCase("CONNECT")) {
587
try {
588
u = new URI("socket",
589
null,
590
authority.getHostString(),
591
authority.getPort(),
592
null,
593
null,
594
null);
595
} catch (URISyntaxException e) {
596
throw new InternalError(e); // shouldn't happen
597
}
598
} else {
599
u = uri;
600
}
601
return u;
602
}
603
604
/**
605
* Returns the security permission required for the given details.
606
* If method is CONNECT, then uri must be of form "scheme://host:port"
607
*/
608
private static URLPermission permissionForServer(URI uri,
609
String method,
610
Map<String, List<String>> headers) {
611
if (method.equals("CONNECT")) {
612
return new URLPermission(uri.toString(), "CONNECT");
613
} else {
614
return Utils.permissionForServer(uri, method, headers.keySet().stream());
615
}
616
}
617
618
/**
619
* Performs the necessary security permission checks required to retrieve
620
* the response. Returns a security exception representing the denied
621
* permission, or null if all checks pass or there is no security manager.
622
*/
623
private SecurityException checkPermissions() {
624
String method = request.method();
625
@SuppressWarnings("removal")
626
SecurityManager sm = System.getSecurityManager();
627
if (sm == null || method.equals("CONNECT")) {
628
// tunneling will have a null acc, which is fine. The proxy
629
// permission check will have already been preformed.
630
return null;
631
}
632
633
HttpHeaders userHeaders = request.getUserHeaders();
634
URI u = getURIForSecurityCheck();
635
URLPermission p = permissionForServer(u, method, userHeaders.map());
636
637
try {
638
assert acc != null;
639
sm.checkPermission(p, acc);
640
} catch (SecurityException e) {
641
return e;
642
}
643
String hostHeader = userHeaders.firstValue("Host").orElse(null);
644
if (hostHeader != null && !hostHeader.equalsIgnoreCase(u.getHost())) {
645
// user has set a Host header different to request URI
646
// must check that for URLPermission also
647
URI u1 = replaceHostInURI(u, hostHeader);
648
URLPermission p1 = permissionForServer(u1, method, userHeaders.map());
649
try {
650
assert acc != null;
651
sm.checkPermission(p1, acc);
652
} catch (SecurityException e) {
653
return e;
654
}
655
}
656
ProxySelector ps = client.proxySelector();
657
if (ps != null) {
658
if (!method.equals("CONNECT")) {
659
// a non-tunneling HTTP proxy. Need to check access
660
URLPermission proxyPerm = permissionForProxy(request.proxy());
661
if (proxyPerm != null) {
662
try {
663
sm.checkPermission(proxyPerm, acc);
664
} catch (SecurityException e) {
665
return e;
666
}
667
}
668
}
669
}
670
return null;
671
}
672
673
private static URI replaceHostInURI(URI u, String hostPort) {
674
StringBuilder sb = new StringBuilder();
675
sb.append(u.getScheme())
676
.append("://")
677
.append(hostPort)
678
.append(u.getRawPath());
679
return URI.create(sb.toString());
680
}
681
682
HttpClient.Version version() {
683
return multi.version();
684
}
685
686
String dbgString() {
687
return dbgTag;
688
}
689
}
690
691