Skip to content

Commit

Permalink
[MRESOLVER-361] Unreliable TCP and retries (s4u#288)
Browse files Browse the repository at this point in the history
Make httpClient retry for PUT.

---

https://issues.apache.org/jira/browse/MRESOLVER-361
  • Loading branch information
cstamas authored Jun 1, 2023
1 parent 466f419 commit 7dfd789
Show file tree
Hide file tree
Showing 2 changed files with 32 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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*/.*");

Expand Down Expand Up @@ -214,29 +225,44 @@ 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<AuthSchemeProvider> authSchemeRegistry = RegistryBuilder.<AuthSchemeProvider>create()
.register(AuthSchemes.BASIC, new BasicSchemeFactory(credentialsCharset))
.register(AuthSchemes.DIGEST, new DigestSchemeFactory(credentialsCharset))
.register(AuthSchemes.NTLM, new NTLMSchemeFactory())
.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)
Expand All @@ -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) {
Expand Down
2 changes: 2 additions & 0 deletions src/site/markdown/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <a href="https://hc.apache.org/httpcomponents-client-4.5.x/current/httpclient/apidocs/org/apache/http/impl/client/HttpClientBuilder.html">HttpClientBuilder</a> for used properties. This mode is **not recommended**, better use documented ways of configuration instead. | `false` | yes
Expand Down

0 comments on commit 7dfd789

Please sign in to comment.