From 7dfd7898a54a84282ab59bb79c4e6c1feaecaf84 Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Thu, 1 Jun 2023 09:17:36 +0200 Subject: [PATCH] [MRESOLVER-361] Unreliable TCP and retries (#288) Make httpClient retry for PUT. --- https://issues.apache.org/jira/browse/MRESOLVER-361 --- .../transport/http/HttpTransporter.java | 35 ++++++++++++++++--- src/site/markdown/configuration.md | 2 ++ 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/maven-resolver-transport-http/src/main/java/org/eclipse/aether/transport/http/HttpTransporter.java b/maven-resolver-transport-http/src/main/java/org/eclipse/aether/transport/http/HttpTransporter.java index 471300a0d..e788787f2 100644 --- a/maven-resolver-transport-http/src/main/java/org/eclipse/aether/transport/http/HttpTransporter.java +++ b/maven-resolver-transport-http/src/main/java/org/eclipse/aether/transport/http/HttpTransporter.java @@ -45,6 +45,7 @@ import org.apache.http.auth.AuthSchemeProvider; import org.apache.http.auth.AuthScope; import org.apache.http.client.CredentialsProvider; +import org.apache.http.client.HttpRequestRetryHandler; import org.apache.http.client.HttpResponseException; import org.apache.http.client.config.AuthSchemes; import org.apache.http.client.config.RequestConfig; @@ -71,6 +72,7 @@ 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.util.EntityUtils; import org.eclipse.aether.ConfigurationProperties; import org.eclipse.aether.RepositorySystemSession; @@ -102,6 +104,15 @@ final class HttpTransporter extends AbstractTransporter { static final String USE_SYSTEM_PROPERTIES = "aether.connector.http.useSystemProperties"; + static final String HTTP_RETRY_HANDLER_NAME = "aether.connector.http.retryHandler.name"; + + private static final String HTTP_RETRY_HANDLER_NAME_STANDARD = "standard"; + + private static final String HTTP_RETRY_HANDLER_NAME_DEFAULT = "default"; + + static final String HTTP_RETRY_HANDLER_REQUEST_SENT_ENABLED = + "aether.connector.http.retryHandler.requestSentEnabled"; + private static final Pattern CONTENT_RANGE_PATTERN = Pattern.compile("\\s*bytes\\s+([0-9]+)\\s*-\\s*([0-9]+)\\s*/.*"); @@ -214,11 +225,20 @@ final class HttpTransporter extends AbstractTransporter { ConfigurationProperties.DEFAULT_HTTP_RETRY_HANDLER_COUNT, ConfigurationProperties.HTTP_RETRY_HANDLER_COUNT + "." + repository.getId(), ConfigurationProperties.HTTP_RETRY_HANDLER_COUNT); + String retryHandlerName = ConfigUtils.getString( + session, + HTTP_RETRY_HANDLER_NAME_STANDARD, + HTTP_RETRY_HANDLER_NAME + "." + repository.getId(), + HTTP_RETRY_HANDLER_NAME); + boolean retryHandlerRequestSentEnabled = ConfigUtils.getBoolean( + session, + false, + HTTP_RETRY_HANDLER_REQUEST_SENT_ENABLED + "." + repository.getId(), + HTTP_RETRY_HANDLER_REQUEST_SENT_ENABLED); String userAgent = ConfigUtils.getString( session, ConfigurationProperties.DEFAULT_USER_AGENT, ConfigurationProperties.USER_AGENT); Charset credentialsCharset = Charset.forName(credentialEncoding); - Registry authSchemeRegistry = RegistryBuilder.create() .register(AuthSchemes.BASIC, new BasicSchemeFactory(credentialsCharset)) .register(AuthSchemes.DIGEST, new DigestSchemeFactory(credentialsCharset)) @@ -226,17 +246,23 @@ final class HttpTransporter extends AbstractTransporter { .register(AuthSchemes.SPNEGO, new SPNegoSchemeFactory()) .register(AuthSchemes.KERBEROS, new KerberosSchemeFactory()) .build(); - SocketConfig socketConfig = SocketConfig.custom().setSoTimeout(requestTimeout).build(); - RequestConfig requestConfig = RequestConfig.custom() .setConnectTimeout(connectTimeout) .setConnectionRequestTimeout(connectTimeout) .setSocketTimeout(requestTimeout) .build(); - DefaultHttpRequestRetryHandler retryHandler = new DefaultHttpRequestRetryHandler(retryCount, false); + HttpRequestRetryHandler retryHandler; + if (HTTP_RETRY_HANDLER_NAME_STANDARD.equals(retryHandlerName)) { + retryHandler = new StandardHttpRequestRetryHandler(retryCount, retryHandlerRequestSentEnabled); + } else if (HTTP_RETRY_HANDLER_NAME_DEFAULT.equals(retryHandlerName)) { + retryHandler = new DefaultHttpRequestRetryHandler(retryCount, retryHandlerRequestSentEnabled); + } else { + throw new IllegalArgumentException( + "Unsupported parameter " + HTTP_RETRY_HANDLER_NAME + " value: " + retryHandlerName); + } HttpClientBuilder builder = HttpClientBuilder.create() .setUserAgent(userAgent) @@ -248,7 +274,6 @@ final class HttpTransporter extends AbstractTransporter { .setConnectionManagerShared(true) .setDefaultCredentialsProvider(toCredentialsProvider(server, repoAuthContext, proxy, proxyAuthContext)) .setProxy(proxy); - final boolean useSystemProperties = ConfigUtils.getBoolean( session, false, USE_SYSTEM_PROPERTIES + "." + repository.getId(), USE_SYSTEM_PROPERTIES); if (useSystemProperties) { diff --git a/src/site/markdown/configuration.md b/src/site/markdown/configuration.md index bba3b2125..797e8433b 100644 --- a/src/site/markdown/configuration.md +++ b/src/site/markdown/configuration.md @@ -42,6 +42,8 @@ Option | Type | Description | Default Value | Supports Repo ID Suffix `aether.connector.http.preemptiveAuth` | boolean | Should HTTP client use preemptive-authentication for all HTTP verbs (works only w/ BASIC). By default is disabled, as it is considered less secure. | `false` | yes `aether.connector.http.preemptivePutAuth` | boolean | Should HTTP client use preemptive-authentication for HTTP PUTs only (works only w/ BASIC). By default is enabled (same as Wagon). | `true` | yes `aether.connector.http.retryHandler.count` | int | The maximum number of times a request to a remote HTTP server should be retried in case of an error. | `3` | yes +`aether.connector.http.retryHandler.name` | String | The name of retryHandler, supported values are "standard", that obeys RFC-2616, regarding idempotent methods, and "default" that considers requests w/o payload as idempotent. | `standard` | yes +`aether.connector.http.retryHandler.requestSentEnabled` | boolean | Set to `true` if it is acceptable to retry non-idempotent requests, that have been sent. | `false` | yes `aether.connector.http.reuseConnections` | boolean | Should HTTP client reuse connections (in other words, pool connections) or not? | `true` | yes `aether.connector.http.supportWebDav` | boolean | If enabled, transport makes best effort to deploy to WebDAV server. This mode is not recommended, better use real Maven Repository Manager instead. | `false` | yes `aether.connector.http.useSystemProperties` | boolean | If enabled, underlying Apache HttpClient will use system properties as well to configure itself (typically used to set up HTTP Proxy via Java system properties). See HttpClientBuilder for used properties. This mode is **not recommended**, better use documented ways of configuration instead. | `false` | yes