You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hc.apache.org by mi...@apache.org on 2019/11/27 14:22:05 UTC

[httpcomponents-client] 01/01: HTTPCLIENT-2019: WIP

This is an automated email from the ASF dual-hosted git repository.

michaelo pushed a commit to branch HTTPCLIENT-2019
in repository https://gitbox.apache.org/repos/asf/httpcomponents-client.git

commit 384ab239de30013868b0bda47928ce8a66c0a146
Author: Michael Osipov <mi...@apache.org>
AuthorDate: Wed Nov 27 15:21:40 2019 +0100

    HTTPCLIENT-2019: WIP
---
 .../hc/client5/http/HttpRequestRetryHandler.java   |   2 +
 ...Strategy.java => HttpRequestRetryStrategy.java} |  37 +++-
 .../http/ServiceUnavailableRetryStrategy.java      |   2 +
 .../http/impl/DefaultHttpRequestRetryHandler.java  |   2 +
 .../http/impl/DefaultHttpRequestRetryStrategy.java | 241 +++++++++++++++++++++
 .../DefaultServiceUnavailableRetryStrategy.java    |   2 +
 6 files changed, 281 insertions(+), 5 deletions(-)

diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/HttpRequestRetryHandler.java b/httpclient5/src/main/java/org/apache/hc/client5/http/HttpRequestRetryHandler.java
index e1573b4..3c430ad 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/HttpRequestRetryHandler.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/HttpRequestRetryHandler.java
@@ -43,7 +43,9 @@ import org.apache.hc.core5.http.protocol.HttpContext;
  * from multiple threads.
  *
  * @since 4.0
+ * @see HttpRequestRetryStrategy
  */
+@Deprecated
 @Contract(threading = ThreadingBehavior.STATELESS)
 public interface HttpRequestRetryHandler {
 
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/ServiceUnavailableRetryStrategy.java b/httpclient5/src/main/java/org/apache/hc/client5/http/HttpRequestRetryStrategy.java
similarity index 63%
copy from httpclient5/src/main/java/org/apache/hc/client5/http/ServiceUnavailableRetryStrategy.java
copy to httpclient5/src/main/java/org/apache/hc/client5/http/HttpRequestRetryStrategy.java
index 9575330..dec7c72 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/ServiceUnavailableRetryStrategy.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/HttpRequestRetryStrategy.java
@@ -27,23 +27,43 @@
 
 package org.apache.hc.client5.http;
 
+import java.io.IOException;
+
 import org.apache.hc.core5.annotation.Contract;
 import org.apache.hc.core5.annotation.ThreadingBehavior;
+import org.apache.hc.core5.http.HttpRequest;
 import org.apache.hc.core5.http.HttpResponse;
 import org.apache.hc.core5.http.protocol.HttpContext;
+import org.apache.hc.core5.util.TimeValue;
 
 /**
  * Strategy interface that allows API users to plug in their own logic to
  * control whether or not a retry should automatically be done, how many times
  * it should be retried and so on.
  *
- * @since 4.2
+ * @since 5.0
  */
 @Contract(threading = ThreadingBehavior.STATELESS)
-public interface ServiceUnavailableRetryStrategy {
+public interface HttpRequestRetryStrategy {
 
     /**
-     * Determines if a method should be retried given the response from the target server.
+     * Determines if a method should be retried after an IOException
+     * occurs during execution.
+     *
+     * @param request request failed die to an I/O exception.
+     * @param exception the exception that occurred
+     * @param executionCount the number of times this method has been
+     * unsuccessfully executed
+     * @param context the context for the request execution
+     *
+     * @return {@code true} if the method should be retried, {@code false}
+     * otherwise
+     */
+    boolean retryRequest(HttpRequest request, IOException exception, int executionCount, HttpContext context);
+
+    /**
+     * Determines if a method should be retried given the response from
+     * the target server.
      *
      * @param response the response from the target server
      * @param executionCount the number of times this method has been
@@ -56,8 +76,15 @@ public interface ServiceUnavailableRetryStrategy {
     boolean retryRequest(HttpResponse response, int executionCount, HttpContext context);
 
     /**
-     * @return The interval between the subsequent retry in milliseconds.
+     * Determines the retry interval between subsequent retries.
+
+     * @param response the response from the target server
+     * @param executionCount the number of times this method has been
+     * unsuccessfully executed
+     * @param context the context for the request execution
+     *
+     * @return the retry interval between subsequent retries
      */
-    long getRetryInterval(HttpResponse response, HttpContext context);
+    TimeValue getRetryInterval(HttpResponse response, int executionCount, HttpContext context);
 
 }
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/ServiceUnavailableRetryStrategy.java b/httpclient5/src/main/java/org/apache/hc/client5/http/ServiceUnavailableRetryStrategy.java
index 9575330..96804de 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/ServiceUnavailableRetryStrategy.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/ServiceUnavailableRetryStrategy.java
@@ -38,7 +38,9 @@ import org.apache.hc.core5.http.protocol.HttpContext;
  * it should be retried and so on.
  *
  * @since 4.2
+ * @see HttpRequestRetryStrategy
  */
+@Deprecated
 @Contract(threading = ThreadingBehavior.STATELESS)
 public interface ServiceUnavailableRetryStrategy {
 
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/DefaultHttpRequestRetryHandler.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/DefaultHttpRequestRetryHandler.java
index 2591aa9..8ae7229 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/DefaultHttpRequestRetryHandler.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/DefaultHttpRequestRetryHandler.java
@@ -51,7 +51,9 @@ import org.apache.hc.core5.util.Args;
  * The default {@link HttpRequestRetryHandler} used by request executors.
  *
  * @since 4.0
+ * @see DefaultHttpRequestRetryStrategy
  */
+@Deprecated
 @Contract(threading = ThreadingBehavior.STATELESS)
 public class DefaultHttpRequestRetryHandler implements HttpRequestRetryHandler {
 
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/DefaultHttpRequestRetryStrategy.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/DefaultHttpRequestRetryStrategy.java
new file mode 100644
index 0000000..ecdd01f
--- /dev/null
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/DefaultHttpRequestRetryStrategy.java
@@ -0,0 +1,241 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.hc.client5.http.impl;
+
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.net.ConnectException;
+import java.net.UnknownHostException;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.net.ssl.SSLException;
+
+import org.apache.hc.client5.http.HttpRequestRetryStrategy;
+import org.apache.hc.client5.http.utils.DateUtils;
+import org.apache.hc.core5.annotation.Contract;
+import org.apache.hc.core5.annotation.ThreadingBehavior;
+import org.apache.hc.core5.concurrent.CancellableDependency;
+import org.apache.hc.core5.http.ConnectionClosedException;
+import org.apache.hc.core5.http.Header;
+import org.apache.hc.core5.http.HttpHeaders;
+import org.apache.hc.core5.http.HttpRequest;
+import org.apache.hc.core5.http.HttpResponse;
+import org.apache.hc.core5.http.HttpStatus;
+import org.apache.hc.core5.http.Methods;
+import org.apache.hc.core5.http.protocol.HttpContext;
+import org.apache.hc.core5.util.Args;
+import org.apache.hc.core5.util.TimeValue;
+
+/**
+ * Default implementation of the {@link HttpRequestRetryStrategy} interface.
+ *
+ * @since 5.0
+ */
+@Contract(threading = ThreadingBehavior.STATELESS)
+public class DefaultHttpRequestRetryStrategy implements HttpRequestRetryStrategy {
+
+    public static final DefaultHttpRequestRetryStrategy INSTANCE = new DefaultHttpRequestRetryStrategy();
+
+    /**
+     * Maximum number of allowed retries
+     */
+    private final int maxRetries;
+
+    /**
+     * Retry interval between subsequent retries
+     */
+    private final TimeValue defaultRetryInterval;
+
+    /**
+     * Derived {@code IOExceptions} which shall not be retried
+     */
+    private final Set<Class<? extends IOException>> nonRetriableClasses;
+
+    /**
+     * HTTP status codes which shall be retried
+     */
+    private final Set<Integer> retriableCodes;
+
+    protected DefaultHttpRequestRetryStrategy(
+            final int maxRetries,
+            final TimeValue defaultRetryInterval,
+            final Collection<Class<? extends IOException>> clazzes,
+            final Collection<Integer> codes) {
+        Args.positive(maxRetries, "maxRetries");
+        Args.positive(defaultRetryInterval.getDuration(), "defaultRetryInterval");
+        this.maxRetries = maxRetries;
+        this.defaultRetryInterval = defaultRetryInterval;
+        this.nonRetriableClasses = new HashSet<>();
+        this.nonRetriableClasses.addAll(clazzes);
+        this.retriableCodes = new HashSet<>();
+        this.retriableCodes.addAll(codes);
+    }
+
+    /**
+     * Create the HTTP request retry strategy using the following list of
+     * non-retriable IOException classes:<br>
+     * <ul>
+     * <li>InterruptedIOException</li>
+     * <li>UnknownHostException</li>
+     * <li>ConnectException</li>
+     * <li>ConnectionClosedException</li>
+     * <li>SSLException</li>
+     * </ul>
+     *
+     * and retriable HTTP status codes:<br>
+     * <ul>
+     * <li>SC_TOO_MANY_REQUESTS (429)</li>
+     * <li>SC_SERVICE_UNAVAILABLE (503)</li>
+     * </ul>
+     *
+     * @param maxRetries how many times to retry; 0 means no retries
+     * @param defaultRetryInterval the default retry interval between
+     * subsequent retries if the {@code Retry-After} header is not set
+     * or invalid.
+     */
+    public DefaultHttpRequestRetryStrategy(
+            final int maxRetries,
+            final TimeValue defaultRetryInterval) {
+        this(maxRetries, defaultRetryInterval,
+                Arrays.asList(
+                        InterruptedIOException.class,
+                        UnknownHostException.class,
+                        ConnectException.class,
+                        ConnectionClosedException.class,
+                        SSLException.class),
+                Arrays.asList(
+                        HttpStatus.SC_TOO_MANY_REQUESTS,
+                        HttpStatus.SC_SERVICE_UNAVAILABLE));
+    }
+
+    /**
+     * Create the HTTP request retry strategy with a max retry count of 1,
+     * default retry interval of 1 second, and using the following list of
+     * non-retriable IOException classes:<br>
+     * <ul>
+     * <li>InterruptedIOException</li>
+     * <li>UnknownHostException</li>
+     * <li>ConnectException</li>
+     * <li>ConnectionClosedException</li>
+     * <li>SSLException</li>
+     * </ul>
+     *
+     * and retriable HTTP status codes:<br>
+     * <ul>
+     * <li>SC_TOO_MANY_REQUESTS (429)</li>
+     * <li>SC_SERVICE_UNAVAILABLE (503)</li>
+     * </ul>
+     */
+    public DefaultHttpRequestRetryStrategy() {
+        this(1, TimeValue.ofSeconds(1L));
+    }
+
+    @Override
+    public boolean retryRequest(
+            final HttpRequest request,
+            final IOException exception,
+            final int executionCount,
+            final HttpContext context) {
+        Args.notNull(request, "request");
+        Args.notNull(exception, "exception");
+
+        if (executionCount > this.maxRetries) {
+            // Do not retry if over max retries
+            return false;
+        }
+        if (this.nonRetriableClasses.contains(exception.getClass())) {
+            return false;
+        } else {
+            for (final Class<? extends IOException> rejectException : this.nonRetriableClasses) {
+                if (rejectException.isInstance(exception)) {
+                    return false;
+                }
+            }
+        }
+        if (request instanceof CancellableDependency && ((CancellableDependency) request).isCancelled()) {
+            return false;
+        }
+
+        // Retry if the request is considered idempotent
+        return handleAsIdempotent(request);
+    }
+
+    @Override
+    public boolean retryRequest(
+            final HttpResponse response,
+            final int executionCount,
+            final HttpContext context) {
+        Args.notNull(response, "response");
+
+        return executionCount <= maxRetries && retriableCodes.contains(response.getCode());
+    }
+
+    @Override
+    public TimeValue getRetryInterval(
+            final HttpResponse response,
+            int executionCount,
+            final HttpContext context) {
+        Args.notNull(response, "response");
+
+        final Header header = response.getFirstHeader(HttpHeaders.RETRY_AFTER);
+        TimeValue retryAfter = null;
+        if (header != null) {
+            final String value = header.getValue();
+            try {
+                retryAfter = TimeValue.ofSeconds(Long.parseLong(value));
+            } catch (final NumberFormatException ignore) {
+                final Date retryAfterDate = DateUtils.parseDate(value);
+                if (retryAfterDate != null) {
+                    retryAfter =
+                            TimeValue.ofMilliseconds(retryAfterDate.getTime() - System.currentTimeMillis());
+                }
+            }
+
+            if (TimeValue.isPositive(retryAfter)) {
+                return retryAfter;
+            }
+        }
+        return this.defaultRetryInterval;
+    }
+
+    /**
+     * @return the maximum number of retries for a request
+     */
+    public int getMaxRetries() {
+        return maxRetries;
+    }
+
+    protected boolean handleAsIdempotent(final HttpRequest request) {
+        return Methods.isIdempotent(request.getMethod());
+    }
+
+}
diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/DefaultServiceUnavailableRetryStrategy.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/DefaultServiceUnavailableRetryStrategy.java
index 6c05c60..393de3e 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/DefaultServiceUnavailableRetryStrategy.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/DefaultServiceUnavailableRetryStrategy.java
@@ -46,7 +46,9 @@ import org.apache.hc.core5.util.Args;
  * at a fixed interval.
  *
  * @since 4.2
+ * @see DefaultHttpRequestRetryStrategy
  */
+@Deprecated
 @Contract(threading = ThreadingBehavior.STATELESS)
 public class DefaultServiceUnavailableRetryStrategy implements ServiceUnavailableRetryStrategy {