[WAGON-567] Provide request retry strategy on transient client and server side errors
This closes #57
diff --git a/wagon-providers/wagon-http-shared/src/main/java/org/apache/maven/wagon/shared/http/AbstractHttpClientWagon.java b/wagon-providers/wagon-http-shared/src/main/java/org/apache/maven/wagon/shared/http/AbstractHttpClientWagon.java
index 7fcbc7f..3bf4291 100755
--- a/wagon-providers/wagon-http-shared/src/main/java/org/apache/maven/wagon/shared/http/AbstractHttpClientWagon.java
+++ b/wagon-providers/wagon-http-shared/src/main/java/org/apache/maven/wagon/shared/http/AbstractHttpClientWagon.java
@@ -34,6 +34,7 @@
import org.apache.http.client.AuthCache;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.HttpRequestRetryHandler;
+import org.apache.http.client.ServiceUnavailableRetryStrategy;
import org.apache.http.client.config.AuthSchemes;
import org.apache.http.client.config.CookieSpecs;
import org.apache.http.client.config.RequestConfig;
@@ -61,6 +62,7 @@
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
+import org.apache.http.impl.client.DefaultServiceUnavailableRetryStrategy;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.StandardHttpRequestRetryHandler;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
@@ -456,6 +458,55 @@
}
}
+ /**
+ * The type of the serviceUnavailableRetryStrategy, defaults to {@code none}.
+ * Values can be {@link default DefaultServiceUnavailableRetryStrategy},
+ * or {@link standard StandardServiceUnavailableRetryStrategy}, or
+ * a fully qualified name class with a no-arg or none to not use a ServiceUnavailableRetryStrategy.
+ */
+ private static final String SERVICE_UNAVAILABLE_RETRY_STRATEGY_CLASS =
+ System.getProperty( "maven.wagon.http.serviceUnavailableRetryStrategy.class", "none" );
+
+ /**
+ * Interval in milliseconds between retries when using a serviceUnavailableRetryStrategy.
+ * <b>1000 by default</b>
+ */
+ private static final int SERVICE_UNAVAILABLE_RETRY_STRATEGY_RETRY_INTERVAL =
+ Integer.getInteger( "maven.wagon.http.serviceUnavailableRetryStrategy.retryInterval", 1000 );
+
+ /**
+ * Maximum number of retries when using a serviceUnavailableRetryStrategy.
+ * <b>5 by default</b>
+ */
+ private static final int SERVICE_UNAVAILABLE_RETRY_STRATEGY_MAX_RETRIES =
+ Integer.getInteger( "maven.wagon.http.serviceUnavailableRetryStrategy.maxRetries", 5 );
+
+ private static ServiceUnavailableRetryStrategy createServiceUnavailableRetryStrategy()
+ {
+ switch ( SERVICE_UNAVAILABLE_RETRY_STRATEGY_CLASS )
+ {
+ case "none": return null;
+ case "default":
+ return new DefaultServiceUnavailableRetryStrategy(
+ SERVICE_UNAVAILABLE_RETRY_STRATEGY_MAX_RETRIES, SERVICE_UNAVAILABLE_RETRY_STRATEGY_RETRY_INTERVAL );
+ case "standard":
+ return new StandardServiceUnavailableRetryStrategy(
+ SERVICE_UNAVAILABLE_RETRY_STRATEGY_MAX_RETRIES, SERVICE_UNAVAILABLE_RETRY_STRATEGY_RETRY_INTERVAL );
+ default:
+ try
+ {
+ final ClassLoader classLoader = AbstractHttpClientWagon.class.getClassLoader();
+ return ServiceUnavailableRetryStrategy.class.cast(
+ classLoader.loadClass( SERVICE_UNAVAILABLE_RETRY_STRATEGY_CLASS )
+ .getConstructor().newInstance() );
+ }
+ catch ( final Exception e )
+ {
+ throw new IllegalArgumentException( e );
+ }
+ }
+ }
+
private static Registry<AuthSchemeProvider> createAuthSchemeRegistry()
{
return RegistryBuilder.<AuthSchemeProvider>create()
@@ -492,6 +543,7 @@
.disableConnectionState() //
.setConnectionManager( httpClientConnectionManager ) //
.setRetryHandler( createRetryHandler() )
+ .setServiceUnavailableRetryStrategy( createServiceUnavailableRetryStrategy() )
.setDefaultAuthSchemeRegistry( createAuthSchemeRegistry() )
.build();
}
diff --git a/wagon-providers/wagon-http-shared/src/main/java/org/apache/maven/wagon/shared/http/StandardServiceUnavailableRetryStrategy.java b/wagon-providers/wagon-http-shared/src/main/java/org/apache/maven/wagon/shared/http/StandardServiceUnavailableRetryStrategy.java
new file mode 100644
index 0000000..7f4d485
--- /dev/null
+++ b/wagon-providers/wagon-http-shared/src/main/java/org/apache/maven/wagon/shared/http/StandardServiceUnavailableRetryStrategy.java
@@ -0,0 +1,79 @@
+package org.apache.maven.wagon.shared.http;
+
+/*
+ * 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.
+ */
+
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.annotation.Contract;
+import org.apache.http.annotation.ThreadingBehavior;
+import org.apache.http.client.ServiceUnavailableRetryStrategy;
+import org.apache.http.protocol.HttpContext;
+import org.apache.http.util.Args;
+
+/**
+ * An implementation of the {@link ServiceUnavailableRetryStrategy} interface.
+ * that retries {@code 408} (Request Timeout), {@code 429} (Too Many Requests),
+ * and {@code 500} (Server side error) responses for a fixed number of times at a fixed interval.
+ */
+@Contract( threading = ThreadingBehavior.IMMUTABLE )
+public class StandardServiceUnavailableRetryStrategy implements ServiceUnavailableRetryStrategy
+{
+ /**
+ * Maximum number of allowed retries if the server responds with a HTTP code
+ * in our retry code list.
+ */
+ private final int maxRetries;
+
+ /**
+ * Retry interval between subsequent requests, in milliseconds.
+ */
+ private final long retryInterval;
+
+ public StandardServiceUnavailableRetryStrategy( final int maxRetries, final int retryInterval )
+ {
+ super();
+ Args.positive( maxRetries, "Max retries" );
+ Args.positive( retryInterval, "Retry interval" );
+ this.maxRetries = maxRetries;
+ this.retryInterval = retryInterval;
+ }
+
+ @Override
+ public boolean retryRequest( final HttpResponse response, final int executionCount, final HttpContext context )
+ {
+ int statusCode = response.getStatusLine().getStatusCode();
+ boolean retryableStatusCode = statusCode == HttpStatus.SC_REQUEST_TIMEOUT
+ // Too Many Requests ("standard" rate-limiting)
+ || statusCode == AbstractHttpClientWagon.SC_TOO_MANY_REQUESTS
+ // Assume server errors are momentary hiccups
+ || statusCode == HttpStatus.SC_INTERNAL_SERVER_ERROR
+ || statusCode == HttpStatus.SC_BAD_GATEWAY
+ || statusCode == HttpStatus.SC_SERVICE_UNAVAILABLE
+ || statusCode == HttpStatus.SC_GATEWAY_TIMEOUT;
+ return executionCount <= maxRetries && retryableStatusCode;
+ }
+
+ @Override
+ public long getRetryInterval()
+ {
+ return retryInterval;
+ }
+
+}