[ 
https://issues.apache.org/jira/browse/WAGON-526?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=16592995#comment-16592995
 ] 

ASF GitHub Bot commented on WAGON-526:
--------------------------------------

asfgit closed pull request #37: [WAGON-526] making the retry handle of http 
client configurable
URL: https://github.com/apache/maven-wagon/pull/37
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

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 6c475573..eb600a11 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.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.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.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,95 @@ private static PoolingHttpClientConnectionManager 
createConnManager()
         return connManager;
     }
 
+    /**
+     * The type of the retry handler, default to 
DefaultHttpRequestRetryHandler.
+     * Values can be default (DefaultHttpRequestRetryHandler), 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.
+     * 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.
+     * 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 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 ) )
+                {
+                    if ( RETRY_HANDLER_COUNT == 3 && 
!RETRY_HANDLER_REQUEST_SENT_ENABLED )
+                    {
+                        return DefaultHttpRequestRetryHandler.INSTANCE;
+                    }
+                    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 +457,7 @@ private static CloseableHttpClient createClient()
             .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 732af58f..fe5052a1 100644
--- a/wagon-providers/wagon-http/src/site/apt/index.apt
+++ b/wagon-providers/wagon-http/src/site/apt/index.apt
@@ -57,3 +57,21 @@ 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 as well with system 
properties:
+
+ * <<<maven.wagon.http.retryhandler.class>>> supports this set of values:
+ 
+    * `default` will use a new 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}}
 (see 
https://hc.apache.org/httpcomponents-client-ga/httpclient/apidocs/org/apache/http/impl/client/StandardHttpRequestRetryHandler.html).
+    * 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>>> = the comma 
separated list of classes 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 e4091ecf..260ad158 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 @@
  * 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 void test()
 
         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 );
+        }
+    }
 }


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
us...@infra.apache.org


> Make the retry handling of HttpClient configurable
> --------------------------------------------------
>
>                 Key: WAGON-526
>                 URL: https://issues.apache.org/jira/browse/WAGON-526
>             Project: Maven Wagon
>          Issue Type: New Feature
>          Components: wagon-http
>    Affects Versions: 3.1.0
>            Reporter: Romain Manni-Bucau
>            Assignee: Michael Osipov
>            Priority: Major
>             Fix For: 3.2.0
>
>
> Provide a way to customize a lot (until a custom impl) the way wagon retries 
> downloads.



--
This message was sent by Atlassian JIRA
(v7.6.3#76005)

Reply via email to