You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by mi...@apache.org on 2018/08/24 17:00:32 UTC
[maven-wagon] 01/01: [WAGON-526] Make the retry handling of
HttpClient configurable
This is an automated email from the ASF dual-hosted git repository.
michaelo pushed a commit to branch WAGON-526
in repository https://gitbox.apache.org/repos/asf/maven-wagon.git
commit f920bc1a4d0f407453d0c45b5cf39316d9eb19d3
Author: Romain Manni-Bucau <rm...@gmail.com>
AuthorDate: Mon Aug 20 10:55:23 2018 +0200
[WAGON-526] Make the retry handling of HttpClient configurable
This closes #37
---
.../wagon/shared/http/AbstractHttpClientWagon.java | 94 +++++++++
wagon-providers/wagon-http/src/site/apt/index.apt | 21 +-
.../http/AbstractHttpClientWagonTest.java | 221 +++++++++++++++++++++
3 files changed, 335 insertions(+), 1 deletion(-)
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 6c47557..63d848a 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
@@ -32,6 +32,7 @@ import org.apache.http.auth.NTCredentials;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.AuthCache;
import org.apache.http.client.CredentialsProvider;
+import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.CookieSpecs;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
@@ -54,7 +55,9 @@ import org.apache.http.impl.auth.BasicScheme;
import org.apache.http.impl.client.BasicAuthCache;
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.HttpClientBuilder;
+import org.apache.http.impl.client.StandardHttpRequestRetryHandler;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicHeader;
import org.apache.http.protocol.HTTP;
@@ -82,7 +85,10 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collection;
import java.util.Date;
+import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
@@ -354,6 +360,93 @@ public abstract class AbstractHttpClientWagon
return connManager;
}
+ /**
+ * The type of the retry handler, defaults to {@code standard}.
+ * Values can be {@link default DefaultHttpRequestRetryHandler},
+ * or {@link standard StandardHttpRequestRetryHandler},
+ * or a fully qualified name class with a no-arg.
+ *
+ * @since 3.2
+ */
+ private static final String RETRY_HANDLER_CLASS =
+ System.getProperty( "maven.wagon.http.retryHandler.class", "standard" );
+
+ /**
+ * Whether or not methods that have successfully sent their request will be retried,
+ * defaults to {@code false}.
+ * Note: only used for default and standard retry handlers.
+ *
+ * @since 3.2
+ */
+ private static final boolean RETRY_HANDLER_REQUEST_SENT_ENABLED =
+ Boolean.getBoolean( "maven.wagon.http.retryHandler.requestSentEnabled" );
+
+ /**
+ * Number of retries for the retry handler, defaults to 3.
+ * Note: only used for default and standard retry handlers.
+ *
+ * @since 3.2
+ */
+ private static final int RETRY_HANDLER_COUNT =
+ Integer.getInteger( "maven.wagon.http.retryHandler.count", 3 );
+
+ /**
+ * Comma-separated list of non-retryable exception classes.
+ * Note: only used for default retry handler.
+ *
+ * @since 3.2
+ */
+ private static final String RETRY_HANDLER_EXCEPTIONS =
+ System.getProperty( "maven.wagon.http.retryHandler.nonRetryableClasses" );
+
+ private static HttpRequestRetryHandler createRetryHandler()
+ {
+ switch ( RETRY_HANDLER_CLASS )
+ {
+ case "default":
+ if ( StringUtils.isEmpty( RETRY_HANDLER_EXCEPTIONS ) )
+ {
+ return new DefaultHttpRequestRetryHandler(
+ RETRY_HANDLER_COUNT, RETRY_HANDLER_REQUEST_SENT_ENABLED );
+ }
+ return new DefaultHttpRequestRetryHandler(
+ RETRY_HANDLER_COUNT, RETRY_HANDLER_REQUEST_SENT_ENABLED, getNonRetryableExceptions() )
+ {
+ };
+ case "standard":
+ return new StandardHttpRequestRetryHandler( RETRY_HANDLER_COUNT, RETRY_HANDLER_REQUEST_SENT_ENABLED );
+ default:
+ try
+ {
+ final ClassLoader classLoader = AbstractHttpClientWagon.class.getClassLoader();
+ return HttpRequestRetryHandler.class.cast( classLoader.loadClass( RETRY_HANDLER_CLASS )
+ .getConstructor().newInstance() );
+ }
+ catch ( final Exception e )
+ {
+ throw new IllegalArgumentException( e );
+ }
+ }
+ }
+
+ private static Collection<Class<? extends IOException>> getNonRetryableExceptions()
+ {
+ final List<Class<? extends IOException>> exceptions = new ArrayList<>();
+ final ClassLoader loader = AbstractHttpClientWagon.class.getClassLoader();
+ for ( final String ex : RETRY_HANDLER_EXCEPTIONS.split( "," ) )
+ {
+ try
+ {
+ exceptions.add( ( Class<? extends IOException> ) loader.loadClass( ex ) );
+ }
+ catch ( final ClassNotFoundException e )
+ {
+ throw new IllegalArgumentException( e );
+ }
+ }
+ return exceptions;
+ }
+
private static CloseableHttpClient httpClient = createClient();
private static CloseableHttpClient createClient()
@@ -362,6 +455,7 @@ public abstract class AbstractHttpClientWagon
.useSystemProperties() //
.disableConnectionState() //
.setConnectionManager( httpClientConnectionManager ) //
+ .setRetryHandler( createRetryHandler() )
.build();
}
diff --git a/wagon-providers/wagon-http/src/site/apt/index.apt b/wagon-providers/wagon-http/src/site/apt/index.apt
index 732af58..12ccf46 100644
--- a/wagon-providers/wagon-http/src/site/apt/index.apt
+++ b/wagon-providers/wagon-http/src/site/apt/index.apt
@@ -31,7 +31,7 @@ Maven Wagon HTTP
This component is an implementation of Wagon provider for HTTP access.
It uses {{{http://hc.apache.org/httpcomponents-client-ga/}Apache HttpComponents client}} as lower level layer.
-
+
It enables Maven to use remote repositories stored in HTTP servers.
@@ -57,3 +57,22 @@ Features
* <<<maven.wagon.http.ssl.ignore.validity.dates>>> = true/false (default false), ignore issues with certificate dates.
* <<<maven.wagon.rto>>> = time in ms (default 1800000), read time out.
+
+ []
+
+ Since version 3.2, the retry handler can be configured with system properties:
+
+ * <<<maven.wagon.http.retryHandler.class>>> supports this set of values:
+
+ * <<<default>>> will use an instance of {{{http://hc.apache.org/httpcomponents-client-ga/httpclient/apidocs/org/apache/http/impl/client/DefaultHttpRequestRetryHandler.html}<<<DefaultHttpRequestRetryHandler>>>}} respecting <<<requestSentEnabled>>>, <<<count>>> and <<<nonRetryableClasses>>>.
+
+ *<< <standard>>> will use an instance of {{{http://hc.apache.org/httpcomponents-client-ga/httpclient/apidocs/org/apache/http/impl/client/StandardHttpRequestRetryHandler.html}<<<StandardHttpRequestRetryHandler>>>}} respecting <<<requestSentEnabled>>> and <<<count>>>.
+
+ * Any fully qualified name of a {{{https://hc.apache.org/httpcomponents-client-ga/httpclient/apidocs/org/apache/http/client/HttpRequestRetryHandler.html}<<<HttpRequestRetryHandler>>>}} implementation will be instantiated with its default constructor.
+
+ * <<<maven.wagon.http.retryHandler.requestSentEnabled>>> = <<<requestSentEnabled>>> for <<<default>>> or <<<standard>>> implementations.
+
+ * <<<maven.wagon.http.retryHandler.count>>> = number of retries for <<<default>>> or <<<standard>>> implementations.
+
+ * <<<maven.wagon.http.retryHandler.nonRetryableClasses>>> = a comma-separated list of fully qualified class names bypassing the retries (only the <<<default>>> implementation).
+ If not set, the default value from {{{http://hc.apache.org/httpcomponents-client-ga/httpclient/apidocs/org/apache/http/impl/client/DefaultHttpRequestRetryHandler.html}<<<DefaultHttpRequestRetryHandler>>>}} will be used.
diff --git a/wagon-providers/wagon-http/src/test/java/org/apache/maven/wagon/providers/http/AbstractHttpClientWagonTest.java b/wagon-providers/wagon-http/src/test/java/org/apache/maven/wagon/providers/http/AbstractHttpClientWagonTest.java
index e4091ec..8eb8233 100644
--- a/wagon-providers/wagon-http/src/test/java/org/apache/maven/wagon/providers/http/AbstractHttpClientWagonTest.java
+++ b/wagon-providers/wagon-http/src/test/java/org/apache/maven/wagon/providers/http/AbstractHttpClientWagonTest.java
@@ -19,6 +19,34 @@ package org.apache.maven.wagon.providers.http;
* under the License.
*/
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.lang.reflect.Field;
+import java.net.ConnectException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Set;
+
+import javax.net.ssl.SSLException;
+
+import org.apache.http.client.HttpRequestRetryHandler;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
+import org.apache.http.impl.execchain.RedirectExec;
+import org.apache.http.impl.execchain.RetryExec;
import org.apache.maven.wagon.InputData;
import org.apache.maven.wagon.repository.Repository;
import org.apache.maven.wagon.resource.Resource;
@@ -51,4 +79,197 @@ public class AbstractHttpClientWagonTest
wagon.disconnect();
}
+
+ @Test
+ public void retryableConfigurationDefaultTest() throws Exception
+ {
+ doTestHttpClient( new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ final HttpRequestRetryHandler handler = getCurrentHandler();
+ assertNotNull( handler );
+ assertThat( handler, instanceOf( DefaultHttpRequestRetryHandler.class ) );
+ final DefaultHttpRequestRetryHandler impl = DefaultHttpRequestRetryHandler.class.cast(handler);
+ assertEquals( 3, impl.getRetryCount() );
+ assertFalse( impl.isRequestSentRetryEnabled() );
+ }
+ });
+ }
+
+ @Test
+ public void retryableConfigurationCountTest() throws Exception
+ {
+ doTestHttpClient( new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ System.setProperty( "maven.wagon.http.retryHandler.class", "default" );
+ System.setProperty( "maven.wagon.http.retryHandler.count", "5" );
+
+ final HttpRequestRetryHandler handler = getCurrentHandler();
+ assertNotNull( handler );
+ assertThat( handler, instanceOf( DefaultHttpRequestRetryHandler.class ) );
+ final DefaultHttpRequestRetryHandler impl = DefaultHttpRequestRetryHandler.class.cast(handler);
+ assertEquals( 5, impl.getRetryCount() );
+ assertFalse( impl.isRequestSentRetryEnabled() );
+ }
+ });
+ }
+
+ @Test
+ public void retryableConfigurationSentTest() throws Exception
+ {
+ doTestHttpClient( new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ System.setProperty( "maven.wagon.http.retryHandler.class", "default" );
+ System.setProperty( "maven.wagon.http.retryHandler.requestSentEnabled", "true" );
+
+ final HttpRequestRetryHandler handler = getCurrentHandler();
+ assertNotNull( handler );
+ assertThat( handler, instanceOf( DefaultHttpRequestRetryHandler.class ) );
+ final DefaultHttpRequestRetryHandler impl = DefaultHttpRequestRetryHandler.class.cast(handler);
+ assertEquals( 3, impl.getRetryCount() );
+ assertTrue( impl.isRequestSentRetryEnabled() );
+ }
+ });
+ }
+
+ @Test
+ public void retryableConfigurationExceptionsTest() throws Exception
+ {
+ doTestHttpClient( new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ System.setProperty( "maven.wagon.http.retryHandler.class", "default" );
+ System.setProperty( "maven.wagon.http.retryHandler.nonRetryableClasses", IOException.class.getName() );
+
+ final HttpRequestRetryHandler handler = getCurrentHandler();
+ assertNotNull( handler );
+ assertThat( handler, instanceOf( DefaultHttpRequestRetryHandler.class ) );
+ final DefaultHttpRequestRetryHandler impl = DefaultHttpRequestRetryHandler.class.cast(handler);
+ assertEquals( 3, impl.getRetryCount() );
+ assertFalse( impl.isRequestSentRetryEnabled() );
+
+ try
+ {
+ final Field nonRetriableClasses = handler.getClass().getSuperclass()
+ .getDeclaredField( "nonRetriableClasses" );
+ if ( !nonRetriableClasses.isAccessible() )
+ {
+ nonRetriableClasses.setAccessible(true);
+ }
+ final Set<?> exceptions = Set.class.cast( nonRetriableClasses.get(handler) );
+ assertEquals( 1, exceptions.size() );
+ assertTrue( exceptions.contains( IOException.class ) );
+ }
+ catch ( final Exception e )
+ {
+ fail( e.getMessage() );
+ }
+ }
+ });
+ }
+
+ private HttpRequestRetryHandler getCurrentHandler()
+ {
+ try
+ {
+ final Class<?> impl = Thread.currentThread().getContextClassLoader().loadClass(
+ "org.apache.maven.wagon.shared.http.AbstractHttpClientWagon" );
+
+ final CloseableHttpClient httpClient = CloseableHttpClient.class.cast(
+ impl.getMethod("getHttpClient").invoke(null) );
+
+ final Field redirectExec = httpClient.getClass().getDeclaredField( "execChain" );
+ if ( !redirectExec.isAccessible() )
+ {
+ redirectExec.setAccessible( true );
+ }
+ final RedirectExec redirectExecInstance = RedirectExec.class.cast(
+ redirectExec.get( httpClient ) );
+
+ final Field requestExecutor = redirectExecInstance.getClass().getDeclaredField( "requestExecutor" );
+ if ( !requestExecutor.isAccessible() )
+ {
+ requestExecutor.setAccessible( true );
+ }
+ final RetryExec requestExecutorInstance = RetryExec.class.cast(
+ requestExecutor.get( redirectExecInstance ) );
+
+ final Field retryHandler = requestExecutorInstance.getClass().getDeclaredField( "retryHandler" );
+ if ( !retryHandler.isAccessible() )
+ {
+ retryHandler.setAccessible( true );
+ }
+ return HttpRequestRetryHandler.class.cast( retryHandler.get( requestExecutorInstance ) );
+ }
+ catch ( final Exception e )
+ {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ private void doTestHttpClient( final Runnable test ) throws Exception
+ {
+ final String classpath = System.getProperty( "java.class.path" );
+ final String[] paths = classpath.split( File.pathSeparator );
+ final Collection<URL> urls = new ArrayList<>( paths.length );
+ for ( final String path : paths )
+ {
+ try
+ {
+ urls.add( new File( path ).toURI().toURL() );
+ }
+ catch ( final MalformedURLException e )
+ {
+ fail( e.getMessage() );
+ }
+ }
+ final URLClassLoader loader = new URLClassLoader( urls.toArray( new URL[ paths.length ] ) , new ClassLoader()
+ {
+ @Override
+ protected Class<?> loadClass( final String name, final boolean resolve ) throws ClassNotFoundException
+ {
+ if ( name.startsWith( "org.apache.maven.wagon.shared.http" ) )
+ {
+ throw new ClassNotFoundException( name );
+ }
+ return super.loadClass( name, resolve );
+ }
+ });
+ final Thread thread = Thread.currentThread();
+ final ClassLoader contextClassLoader = thread.getContextClassLoader();
+ thread.setContextClassLoader( loader );
+
+ final String originalClass = System.getProperty( "maven.wagon.http.retryHandler.class", "default" );
+ final String originalSentEnabled = System.getProperty(
+ "maven.wagon.http.retryHandler.requestSentEnabled", "false" );
+ final String originalCount = System.getProperty( "maven.wagon.http.retryHandler.count", "3" );
+ final String originalExceptions = System.getProperty( "maven.wagon.http.retryHandler.nonRetryableClasses",
+ InterruptedIOException.class.getName() + ","
+ + UnknownHostException.class.getName() + ","
+ + ConnectException.class.getName() + ","
+ + SSLException.class.getName());
+ try
+ {
+ test.run();
+ }
+ finally
+ {
+ loader.close();
+ thread.setContextClassLoader( contextClassLoader );
+ System.setProperty( "maven.wagon.http.retryHandler.class", originalClass );
+ System.setProperty( "maven.wagon.http.retryHandler.requestSentEnabled", originalSentEnabled );
+ System.setProperty( "maven.wagon.http.retryHandler.count", originalCount );
+ System.setProperty( "maven.wagon.http.retryHandler.nonRetryableClasses", originalExceptions );
+ }
+ }
}