src/java.net.http/share/classes/jdk/internal/net/http/MultiExchange.java
changeset 53387 c9622e15ba29
parent 53300 54aa3ea04fe8
child 53467 97cf88608d76
equal deleted inserted replaced
53386:d5f6540c6bb1 53387:c9622e15ba29
    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())