24 */ |
24 */ |
25 |
25 |
26 package jdk.internal.net.http; |
26 package jdk.internal.net.http; |
27 |
27 |
28 import java.io.IOException; |
28 import java.io.IOException; |
29 import java.io.UncheckedIOException; |
|
30 import java.net.ConnectException; |
29 import java.net.ConnectException; |
31 import java.net.http.HttpConnectTimeoutException; |
30 import java.net.http.HttpConnectTimeoutException; |
|
31 import java.time.Duration; |
32 import java.util.Iterator; |
32 import java.util.Iterator; |
33 import java.util.LinkedList; |
33 import java.util.LinkedList; |
34 import java.security.AccessControlContext; |
34 import java.security.AccessControlContext; |
|
35 import java.util.Objects; |
|
36 import java.util.Optional; |
35 import java.util.concurrent.CompletableFuture; |
37 import java.util.concurrent.CompletableFuture; |
36 import java.util.concurrent.CompletionStage; |
38 import java.util.concurrent.CompletionStage; |
37 import java.util.concurrent.CompletionException; |
39 import java.util.concurrent.CompletionException; |
38 import java.util.concurrent.ExecutionException; |
40 import java.util.concurrent.ExecutionException; |
39 import java.util.concurrent.Executor; |
41 import java.util.concurrent.Executor; |
40 import java.util.concurrent.Flow; |
42 import java.util.concurrent.Flow; |
41 import java.util.concurrent.atomic.AtomicInteger; |
43 import java.util.concurrent.atomic.AtomicInteger; |
|
44 import java.util.concurrent.atomic.AtomicLong; |
42 import java.util.function.Function; |
45 import java.util.function.Function; |
43 |
46 |
44 import java.net.http.HttpClient; |
47 import java.net.http.HttpClient; |
45 import java.net.http.HttpHeaders; |
48 import java.net.http.HttpHeaders; |
46 import java.net.http.HttpRequest; |
49 import java.net.http.HttpRequest; |
69 static final Logger debug = |
72 static final Logger debug = |
70 Utils.getDebugLogger("MultiExchange"::toString, Utils.DEBUG); |
73 Utils.getDebugLogger("MultiExchange"::toString, Utils.DEBUG); |
71 |
74 |
72 private final HttpRequest userRequest; // the user request |
75 private final HttpRequest userRequest; // the user request |
73 private final HttpRequestImpl request; // a copy of the user request |
76 private final HttpRequestImpl request; // a copy of the user request |
|
77 private final ConnectTimeoutTracker connectTimeout; // null if no timeout |
74 final AccessControlContext acc; |
78 final AccessControlContext acc; |
75 final HttpClientImpl client; |
79 final HttpClientImpl client; |
76 final HttpResponse.BodyHandler<T> responseHandler; |
80 final HttpResponse.BodyHandler<T> responseHandler; |
77 final HttpClientImpl.DelegatingExecutor executor; |
81 final HttpClientImpl.DelegatingExecutor executor; |
78 final AtomicInteger attempts = new AtomicInteger(); |
82 final AtomicInteger attempts = new AtomicInteger(); |
104 * (one per MultiExchange object, and one per Exchange object possibly) |
108 * (one per MultiExchange object, and one per Exchange object possibly) |
105 */ |
109 */ |
106 volatile AuthenticationFilter.AuthInfo serverauth, proxyauth; |
110 volatile AuthenticationFilter.AuthInfo serverauth, proxyauth; |
107 // RedirectHandler |
111 // RedirectHandler |
108 volatile int numberOfRedirects = 0; |
112 volatile int numberOfRedirects = 0; |
|
113 |
|
114 // This class is used to keep track of the connection timeout |
|
115 // across retries, when a ConnectException causes a retry. |
|
116 // In that case - we will retry the connect, but we don't |
|
117 // want to double the timeout by starting a new timer with |
|
118 // the full connectTimeout again. |
|
119 // Instead we use the ConnectTimeoutTracker to return a new |
|
120 // duration that takes into account the time spent in the |
|
121 // first connect attempt. |
|
122 // If however, the connection gets connected, but we later |
|
123 // retry the whole operation, then we reset the timer before |
|
124 // retrying (since the connection used for the second request |
|
125 // will not necessarily be the same: it could be a new |
|
126 // unconnected connection) - see getExceptionalCF(). |
|
127 private static final class ConnectTimeoutTracker { |
|
128 final Duration max; |
|
129 final AtomicLong startTime = new AtomicLong(); |
|
130 ConnectTimeoutTracker(Duration connectTimeout) { |
|
131 this.max = Objects.requireNonNull(connectTimeout); |
|
132 } |
|
133 |
|
134 Duration getRemaining() { |
|
135 long now = System.nanoTime(); |
|
136 long previous = startTime.compareAndExchange(0, now); |
|
137 if (previous == 0 || max.isZero()) return max; |
|
138 Duration remaining = max.minus(Duration.ofNanos(now - previous)); |
|
139 assert remaining.compareTo(max) <= 0; |
|
140 return remaining.isNegative() ? Duration.ZERO : remaining; |
|
141 } |
|
142 |
|
143 void reset() { startTime.set(0); } |
|
144 } |
109 |
145 |
110 /** |
146 /** |
111 * MultiExchange with one final response. |
147 * MultiExchange with one final response. |
112 */ |
148 */ |
113 MultiExchange(HttpRequest userRequest, |
149 MultiExchange(HttpRequest userRequest, |
133 : new PrivilegedExecutor(this.executor.delegate(), acc); |
169 : new PrivilegedExecutor(this.executor.delegate(), acc); |
134 this.pushGroup = new PushGroup<>(pushPromiseHandler, request, executor); |
170 this.pushGroup = new PushGroup<>(pushPromiseHandler, request, executor); |
135 } else { |
171 } else { |
136 pushGroup = null; |
172 pushGroup = null; |
137 } |
173 } |
138 |
174 this.connectTimeout = client.connectTimeout() |
|
175 .map(ConnectTimeoutTracker::new).orElse(null); |
139 this.exchange = new Exchange<>(request, this); |
176 this.exchange = new Exchange<>(request, this); |
140 } |
177 } |
141 |
178 |
142 synchronized Exchange<T> getExchange() { |
179 synchronized Exchange<T> getExchange() { |
143 return exchange; |
180 return exchange; |
157 private synchronized void setExchange(Exchange<T> exchange) { |
194 private synchronized void setExchange(Exchange<T> exchange) { |
158 if (this.exchange != null && exchange != this.exchange) { |
195 if (this.exchange != null && exchange != this.exchange) { |
159 this.exchange.released(); |
196 this.exchange.released(); |
160 } |
197 } |
161 this.exchange = exchange; |
198 this.exchange = exchange; |
|
199 } |
|
200 |
|
201 public Optional<Duration> remainingConnectTimeout() { |
|
202 return Optional.ofNullable(connectTimeout) |
|
203 .map(ConnectTimeoutTracker::getRemaining); |
162 } |
204 } |
163 |
205 |
164 private void cancelTimer() { |
206 private void cancelTimer() { |
165 if (responseTimerEvent != null) { |
207 if (responseTimerEvent != null) { |
166 client.cancelTimer(responseTimerEvent); |
208 client.cancelTimer(responseTimerEvent); |
353 if (s == null) |
395 if (s == null) |
354 return false; |
396 return false; |
355 return s.isEmpty() ? true : Boolean.parseBoolean(s); |
397 return s.isEmpty() ? true : Boolean.parseBoolean(s); |
356 } |
398 } |
357 |
399 |
358 private static boolean retryConnect() { |
400 private static boolean disableRetryConnect() { |
359 String s = Utils.getNetProperty("jdk.httpclient.disableRetryConnect"); |
401 String s = Utils.getNetProperty("jdk.httpclient.disableRetryConnect"); |
360 if (s == null) |
402 if (s == null) |
361 return false; |
403 return false; |
362 return s.isEmpty() ? true : Boolean.parseBoolean(s); |
404 return s.isEmpty() ? true : Boolean.parseBoolean(s); |
363 } |
405 } |
364 |
406 |
365 /** True if ALL ( even non-idempotent ) requests can be automatic retried. */ |
407 /** True if ALL ( even non-idempotent ) requests can be automatic retried. */ |
366 private static final boolean RETRY_ALWAYS = retryPostValue(); |
408 private static final boolean RETRY_ALWAYS = retryPostValue(); |
367 /** True if ConnectException should cause a retry. Enabled by default */ |
409 /** True if ConnectException should cause a retry. Enabled by default */ |
368 private static final boolean RETRY_CONNECT = retryConnect(); |
410 private static final boolean RETRY_CONNECT = !disableRetryConnect(); |
369 |
411 |
370 /** Returns true is given request has an idempotent method. */ |
412 /** Returns true is given request has an idempotent method. */ |
371 private static boolean isIdempotentRequest(HttpRequest request) { |
413 private static boolean isIdempotentRequest(HttpRequest request) { |
372 String method = request.method(); |
414 String method = request.method(); |
373 switch (method) { |
415 switch (method) { |
414 } |
456 } |
415 } else if (retryOnFailure(t)) { |
457 } else if (retryOnFailure(t)) { |
416 Throwable cause = retryCause(t); |
458 Throwable cause = retryCause(t); |
417 |
459 |
418 if (!(t instanceof ConnectException)) { |
460 if (!(t instanceof ConnectException)) { |
|
461 // we may need to start a new connection, and if so |
|
462 // we want to start with a fresh connect timeout again. |
|
463 if (connectTimeout != null) connectTimeout.reset(); |
419 if (!canRetryRequest(currentreq)) { |
464 if (!canRetryRequest(currentreq)) { |
420 return failedFuture(cause); // fails with original cause |
465 return failedFuture(cause); // fails with original cause |
421 } |
466 } |
422 } |
467 } // ConnectException: retry, but don't reset the connectTimeout. |
423 |
468 |
424 // allow the retry mechanism to do its work |
469 // allow the retry mechanism to do its work |
425 retryCause = cause; |
470 retryCause = cause; |
426 if (!expiredOnce) { |
471 if (!expiredOnce) { |
427 if (debug.on()) |
472 if (debug.on()) |