mirror of
https://github.com/apache/httpcomponents-client.git
synced 2025-02-16 23:16:33 +00:00
HTTPCLIENT-2035: Remove HttpRequestRetryHandler in favor of HttpRequestRetryStrategy
This closes #183
This commit is contained in:
parent
e06af3d50b
commit
52c6cf7037
@ -1,65 +0,0 @@
|
||||
/*
|
||||
* ====================================================================
|
||||
* 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.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
|
||||
package org.apache.hc.client5.http;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.apache.hc.core5.annotation.Contract;
|
||||
import org.apache.hc.core5.annotation.ThreadingBehavior;
|
||||
import org.apache.hc.core5.http.HttpRequest;
|
||||
import org.apache.hc.core5.http.protocol.HttpContext;
|
||||
|
||||
/**
|
||||
* A handler for determining if an HttpRequest should be retried after a
|
||||
* recoverable exception during execution.
|
||||
* <p>
|
||||
* Implementations of this interface must be thread-safe. Access to shared
|
||||
* data must be synchronized as methods of this interface may be executed
|
||||
* from multiple threads.
|
||||
*
|
||||
* @since 4.0
|
||||
*/
|
||||
@Contract(threading = ThreadingBehavior.STATELESS)
|
||||
public interface HttpRequestRetryHandler {
|
||||
|
||||
/**
|
||||
* Determines if a method should be retried after an IOException
|
||||
* occurs during execution.
|
||||
*
|
||||
* @param request request failed die to an I/O exception.
|
||||
* @param exception the exception that occurred
|
||||
* @param executionCount the number of times this method has been
|
||||
* unsuccessfully executed
|
||||
* @param context the context for the request execution
|
||||
*
|
||||
* @return {@code true} if the method should be retried, {@code false}
|
||||
* otherwise
|
||||
*/
|
||||
boolean retryRequest(HttpRequest request, IOException exception, int executionCount, HttpContext context);
|
||||
|
||||
}
|
@ -34,7 +34,6 @@
|
||||
*/
|
||||
public enum ChainElement {
|
||||
|
||||
REDIRECT, BACK_OFF, RETRY, RETRY_IO_ERROR, CACHING, PROTOCOL,
|
||||
CONNECT, MAIN_TRANSPORT
|
||||
REDIRECT, BACK_OFF, RETRY, CACHING, PROTOCOL, CONNECT, MAIN_TRANSPORT
|
||||
|
||||
}
|
||||
|
@ -1,169 +0,0 @@
|
||||
/*
|
||||
* ====================================================================
|
||||
* 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.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
|
||||
package org.apache.hc.client5.http.impl;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.net.ConnectException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.net.ssl.SSLException;
|
||||
|
||||
import org.apache.hc.client5.http.HttpRequestRetryHandler;
|
||||
import org.apache.hc.core5.annotation.Contract;
|
||||
import org.apache.hc.core5.annotation.ThreadingBehavior;
|
||||
import org.apache.hc.core5.concurrent.CancellableDependency;
|
||||
import org.apache.hc.core5.http.ConnectionClosedException;
|
||||
import org.apache.hc.core5.http.HttpRequest;
|
||||
import org.apache.hc.core5.http.Methods;
|
||||
import org.apache.hc.core5.http.protocol.HttpContext;
|
||||
import org.apache.hc.core5.util.Args;
|
||||
|
||||
/**
|
||||
* The default {@link HttpRequestRetryHandler} used by request executors.
|
||||
*
|
||||
* @since 4.0
|
||||
*/
|
||||
@Contract(threading = ThreadingBehavior.STATELESS)
|
||||
public class DefaultHttpRequestRetryHandler implements HttpRequestRetryHandler {
|
||||
|
||||
public static final DefaultHttpRequestRetryHandler INSTANCE = new DefaultHttpRequestRetryHandler();
|
||||
|
||||
/**
|
||||
* the number of times a method will be retried
|
||||
*/
|
||||
private final int retryCount;
|
||||
|
||||
private final Set<Class<? extends IOException>> nonRetriableClasses;
|
||||
|
||||
/**
|
||||
* Create the request retry handler using the specified IOException classes
|
||||
*
|
||||
* @param retryCount how many times to retry; 0 means no retries
|
||||
* @param clazzes the IOException types that should not be retried
|
||||
* @since 5.0
|
||||
*/
|
||||
@SafeVarargs
|
||||
protected DefaultHttpRequestRetryHandler(
|
||||
final int retryCount,
|
||||
final Class<? extends IOException>... clazzes) {
|
||||
super();
|
||||
this.retryCount = retryCount;
|
||||
this.nonRetriableClasses = new HashSet<>();
|
||||
this.nonRetriableClasses.addAll(Arrays.asList(clazzes));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the request retry handler using the following list of
|
||||
* non-retriable IOException classes: <br>
|
||||
* <ul>
|
||||
* <li>InterruptedIOException</li>
|
||||
* <li>UnknownHostException</li>
|
||||
* <li>ConnectException</li>
|
||||
* <li>ConnectionClosedException</li>
|
||||
* <li>SSLException</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param retryCount how many times to retry; 0 means no retries
|
||||
* @since 5.0
|
||||
*/
|
||||
public DefaultHttpRequestRetryHandler(final int retryCount) {
|
||||
this(retryCount,
|
||||
InterruptedIOException.class,
|
||||
UnknownHostException.class,
|
||||
ConnectException.class,
|
||||
ConnectionClosedException.class,
|
||||
SSLException.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the request retry handler with a retry count of 3, requestSentRetryEnabled false
|
||||
* and using the following list of non-retriable IOException classes: <br>
|
||||
* <ul>
|
||||
* <li>InterruptedIOException</li>
|
||||
* <li>UnknownHostException</li>
|
||||
* <li>ConnectException</li>
|
||||
* <li>SSLException</li>
|
||||
* </ul>
|
||||
*/
|
||||
public DefaultHttpRequestRetryHandler() {
|
||||
this(3);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used {@code retryCount} and {@code requestSentRetryEnabled} to determine
|
||||
* if the given method should be retried.
|
||||
*/
|
||||
@Override
|
||||
public boolean retryRequest(
|
||||
final HttpRequest request,
|
||||
final IOException exception,
|
||||
final int executionCount,
|
||||
final HttpContext context) {
|
||||
Args.notNull(request, "HTTP request");
|
||||
Args.notNull(exception, "I/O exception");
|
||||
|
||||
if (executionCount > this.retryCount) {
|
||||
// Do not retry if over max retry count
|
||||
return false;
|
||||
}
|
||||
if (this.nonRetriableClasses.contains(exception.getClass())) {
|
||||
return false;
|
||||
} else {
|
||||
for (final Class<? extends IOException> rejectException : this.nonRetriableClasses) {
|
||||
if (rejectException.isInstance(exception)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (request instanceof CancellableDependency && ((CancellableDependency) request).isCancelled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Retry if the request is considered idempotent
|
||||
return handleAsIdempotent(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the maximum number of times a method will be retried
|
||||
*/
|
||||
public int getRetryCount() {
|
||||
return retryCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 4.2
|
||||
*/
|
||||
protected boolean handleAsIdempotent(final HttpRequest request) {
|
||||
return Methods.isIdempotent(request.getMethod());
|
||||
}
|
||||
|
||||
}
|
@ -1,151 +0,0 @@
|
||||
/*
|
||||
* ====================================================================
|
||||
* 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.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
package org.apache.hc.client5.http.impl.async;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.apache.hc.client5.http.HttpRequestRetryHandler;
|
||||
import org.apache.hc.client5.http.HttpRoute;
|
||||
import org.apache.hc.client5.http.async.AsyncExecCallback;
|
||||
import org.apache.hc.client5.http.async.AsyncExecChain;
|
||||
import org.apache.hc.client5.http.async.AsyncExecChainHandler;
|
||||
import org.apache.hc.client5.http.impl.RequestCopier;
|
||||
import org.apache.hc.client5.http.protocol.HttpClientContext;
|
||||
import org.apache.hc.core5.annotation.Contract;
|
||||
import org.apache.hc.core5.annotation.Internal;
|
||||
import org.apache.hc.core5.annotation.ThreadingBehavior;
|
||||
import org.apache.hc.core5.http.EntityDetails;
|
||||
import org.apache.hc.core5.http.HttpException;
|
||||
import org.apache.hc.core5.http.HttpRequest;
|
||||
import org.apache.hc.core5.http.HttpResponse;
|
||||
import org.apache.hc.core5.http.nio.AsyncDataConsumer;
|
||||
import org.apache.hc.core5.http.nio.AsyncEntityProducer;
|
||||
import org.apache.hc.core5.util.Args;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Request execution handler in the asynchronous request execution chain
|
||||
* responsbile for making a decision whether a request failed due to
|
||||
* an I/O error should be re-executed.
|
||||
* <p>
|
||||
* Further responsibilities such as communication with the opposite
|
||||
* endpoint is delegated to the next executor in the request execution
|
||||
* chain.
|
||||
* </p>
|
||||
*
|
||||
* @since 5.0
|
||||
*/
|
||||
@Contract(threading = ThreadingBehavior.STATELESS)
|
||||
@Internal
|
||||
public final class AsyncRetryExec implements AsyncExecChainHandler {
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(getClass());
|
||||
|
||||
private final HttpRequestRetryHandler retryHandler;
|
||||
|
||||
public AsyncRetryExec(final HttpRequestRetryHandler retryHandler) {
|
||||
Args.notNull(retryHandler, "HTTP request retry handler");
|
||||
this.retryHandler = retryHandler;
|
||||
}
|
||||
|
||||
private void internalExecute(
|
||||
final int execCount,
|
||||
final HttpRequest request,
|
||||
final AsyncEntityProducer entityProducer,
|
||||
final AsyncExecChain.Scope scope,
|
||||
final AsyncExecChain chain,
|
||||
final AsyncExecCallback asyncExecCallback) throws HttpException, IOException {
|
||||
|
||||
final String exchangeId = scope.exchangeId;
|
||||
|
||||
chain.proceed(RequestCopier.INSTANCE.copy(request), entityProducer, scope, new AsyncExecCallback() {
|
||||
|
||||
@Override
|
||||
public AsyncDataConsumer handleResponse(
|
||||
final HttpResponse response,
|
||||
final EntityDetails entityDetails) throws HttpException, IOException {
|
||||
return asyncExecCallback.handleResponse(response, entityDetails);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleInformationResponse(final HttpResponse response) throws HttpException, IOException {
|
||||
asyncExecCallback.handleInformationResponse(response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void completed() {
|
||||
asyncExecCallback.completed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(final Exception cause) {
|
||||
if (cause instanceof IOException) {
|
||||
final HttpRoute route = scope.route;
|
||||
final HttpClientContext clientContext = scope.clientContext;
|
||||
if (entityProducer != null && !entityProducer.isRepeatable()) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug(exchangeId + ": cannot retry non-repeatable request");
|
||||
}
|
||||
} else if (retryHandler.retryRequest(request, (IOException) cause, execCount, clientContext)) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug(exchangeId + ": " + cause.getMessage(), cause);
|
||||
}
|
||||
if (log.isInfoEnabled()) {
|
||||
log.info("Recoverable I/O exception ("+ cause.getClass().getName() + ") " +
|
||||
"caught when processing request to " + route);
|
||||
}
|
||||
scope.execRuntime.discardEndpoint();
|
||||
if (entityProducer != null) {
|
||||
entityProducer.releaseResources();
|
||||
}
|
||||
try {
|
||||
internalExecute(execCount + 1, request, entityProducer, scope, chain, asyncExecCallback);
|
||||
} catch (final IOException | HttpException ex) {
|
||||
asyncExecCallback.failed(ex);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
asyncExecCallback.failed(cause);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(
|
||||
final HttpRequest request,
|
||||
final AsyncEntityProducer entityProducer,
|
||||
final AsyncExecChain.Scope scope,
|
||||
final AsyncExecChain chain,
|
||||
final AsyncExecCallback asyncExecCallback) throws HttpException, IOException {
|
||||
internalExecute(1, request, entityProducer, scope, chain, asyncExecCallback);
|
||||
}
|
||||
|
||||
}
|
@ -40,7 +40,6 @@
|
||||
|
||||
import org.apache.hc.client5.http.AuthenticationStrategy;
|
||||
import org.apache.hc.client5.http.DnsResolver;
|
||||
import org.apache.hc.client5.http.HttpRequestRetryHandler;
|
||||
import org.apache.hc.client5.http.HttpRequestRetryStrategy;
|
||||
import org.apache.hc.client5.http.SchemePortResolver;
|
||||
import org.apache.hc.client5.http.SystemDefaultDnsResolver;
|
||||
@ -192,7 +191,6 @@ private ExecInterceptorEntry(
|
||||
|
||||
private HttpRoutePlanner routePlanner;
|
||||
private RedirectStrategy redirectStrategy;
|
||||
private HttpRequestRetryHandler retryHandler;
|
||||
private HttpRequestRetryStrategy retryStrategy;
|
||||
|
||||
private Lookup<AuthSchemeProvider> authSchemeRegistry;
|
||||
@ -380,17 +378,6 @@ public final H2AsyncClientBuilder addRequestInterceptorLast(final HttpRequestInt
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns {@link HttpRequestRetryHandler} instance.
|
||||
* <p>
|
||||
* Please note this value can be overridden by the {@link #disableAutomaticRetries()}
|
||||
* method.
|
||||
*/
|
||||
public final H2AsyncClientBuilder setRetryHandler(final HttpRequestRetryHandler retryHandler) {
|
||||
this.retryHandler = retryHandler;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns {@link HttpRequestRetryStrategy} instance.
|
||||
* <p>
|
||||
@ -687,20 +674,13 @@ public CloseableHttpAsyncClient build() {
|
||||
|
||||
// Add request retry executor, if not disabled
|
||||
if (!automaticRetriesDisabled) {
|
||||
final HttpRequestRetryHandler retryHandlerCopy = this.retryHandler;
|
||||
if (retryHandlerCopy != null) {
|
||||
execChainDefinition.addFirst(
|
||||
new AsyncRetryExec(retryHandlerCopy),
|
||||
ChainElement.RETRY_IO_ERROR.name());
|
||||
} else {
|
||||
HttpRequestRetryStrategy retryStrategyCopy = this.retryStrategy;
|
||||
if (retryStrategyCopy == null) {
|
||||
retryStrategyCopy = DefaultHttpRequestRetryStrategy.INSTANCE;
|
||||
}
|
||||
execChainDefinition.addFirst(
|
||||
new AsyncHttpRequestRetryExec(retryStrategyCopy),
|
||||
ChainElement.RETRY.name());
|
||||
HttpRequestRetryStrategy retryStrategyCopy = this.retryStrategy;
|
||||
if (retryStrategyCopy == null) {
|
||||
retryStrategyCopy = DefaultHttpRequestRetryStrategy.INSTANCE;
|
||||
}
|
||||
execChainDefinition.addFirst(
|
||||
new AsyncHttpRequestRetryExec(retryStrategyCopy),
|
||||
ChainElement.RETRY.name());
|
||||
}
|
||||
|
||||
HttpRoutePlanner routePlannerCopy = this.routePlanner;
|
||||
|
@ -40,7 +40,6 @@
|
||||
|
||||
import org.apache.hc.client5.http.AuthenticationStrategy;
|
||||
import org.apache.hc.client5.http.ConnectionKeepAliveStrategy;
|
||||
import org.apache.hc.client5.http.HttpRequestRetryHandler;
|
||||
import org.apache.hc.client5.http.HttpRequestRetryStrategy;
|
||||
import org.apache.hc.client5.http.SchemePortResolver;
|
||||
import org.apache.hc.client5.http.SystemDefaultDnsResolver;
|
||||
@ -227,7 +226,6 @@ private ExecInterceptorEntry(
|
||||
|
||||
private HttpRoutePlanner routePlanner;
|
||||
private RedirectStrategy redirectStrategy;
|
||||
private HttpRequestRetryHandler retryHandler;
|
||||
private HttpRequestRetryStrategy retryStrategy;
|
||||
|
||||
private ConnectionReuseStrategy reuseStrategy;
|
||||
@ -487,17 +485,6 @@ public final HttpAsyncClientBuilder addRequestInterceptorLast(final HttpRequestI
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns {@link HttpRequestRetryHandler} instance.
|
||||
* <p>
|
||||
* Please note this value can be overridden by the {@link #disableAutomaticRetries()}
|
||||
* method.
|
||||
*/
|
||||
public final HttpAsyncClientBuilder setRetryHandler(final HttpRequestRetryHandler retryHandler) {
|
||||
this.retryHandler = retryHandler;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns {@link HttpRequestRetryStrategy} instance.
|
||||
* <p>
|
||||
@ -837,20 +824,13 @@ public CloseableHttpAsyncClient build() {
|
||||
|
||||
// Add request retry executor, if not disabled
|
||||
if (!automaticRetriesDisabled) {
|
||||
final HttpRequestRetryHandler retryHandlerCopy = this.retryHandler;
|
||||
if (retryHandlerCopy != null) {
|
||||
execChainDefinition.addFirst(
|
||||
new AsyncRetryExec(retryHandlerCopy),
|
||||
ChainElement.RETRY_IO_ERROR.name());
|
||||
} else {
|
||||
HttpRequestRetryStrategy retryStrategyCopy = this.retryStrategy;
|
||||
if (retryStrategyCopy == null) {
|
||||
retryStrategyCopy = DefaultHttpRequestRetryStrategy.INSTANCE;
|
||||
}
|
||||
execChainDefinition.addFirst(
|
||||
new AsyncHttpRequestRetryExec(retryStrategyCopy),
|
||||
ChainElement.RETRY.name());
|
||||
HttpRequestRetryStrategy retryStrategyCopy = this.retryStrategy;
|
||||
if (retryStrategyCopy == null) {
|
||||
retryStrategyCopy = DefaultHttpRequestRetryStrategy.INSTANCE;
|
||||
}
|
||||
execChainDefinition.addFirst(
|
||||
new AsyncHttpRequestRetryExec(retryStrategyCopy),
|
||||
ChainElement.RETRY.name());
|
||||
}
|
||||
|
||||
HttpRoutePlanner routePlannerCopy = this.routePlanner;
|
||||
|
@ -39,7 +39,6 @@
|
||||
|
||||
import org.apache.hc.client5.http.AuthenticationStrategy;
|
||||
import org.apache.hc.client5.http.ConnectionKeepAliveStrategy;
|
||||
import org.apache.hc.client5.http.HttpRequestRetryHandler;
|
||||
import org.apache.hc.client5.http.HttpRequestRetryStrategy;
|
||||
import org.apache.hc.client5.http.SchemePortResolver;
|
||||
import org.apache.hc.client5.http.SystemDefaultDnsResolver;
|
||||
@ -203,7 +202,6 @@ private ExecInterceptorEntry(
|
||||
private LinkedList<ResponseInterceptorEntry> responseInterceptors;
|
||||
private LinkedList<ExecInterceptorEntry> execInterceptors;
|
||||
|
||||
private HttpRequestRetryHandler retryHandler;
|
||||
private HttpRequestRetryStrategy retryStrategy;
|
||||
private HttpRoutePlanner routePlanner;
|
||||
private RedirectStrategy redirectStrategy;
|
||||
@ -494,17 +492,6 @@ public final HttpClientBuilder disableAuthCaching() {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns {@link HttpRequestRetryHandler} instance.
|
||||
* <p>
|
||||
* Please note this value can be overridden by the {@link #disableAutomaticRetries()}
|
||||
* method.
|
||||
*/
|
||||
public final HttpClientBuilder setRetryHandler(final HttpRequestRetryHandler retryHandler) {
|
||||
this.retryHandler = retryHandler;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns {@link HttpRequestRetryStrategy} instance.
|
||||
* <p>
|
||||
@ -861,21 +848,13 @@ public boolean keepAlive(
|
||||
|
||||
// Add request retry executor, if not disabled
|
||||
if (!automaticRetriesDisabled) {
|
||||
// This needs to be cleaned up as soon as HttpRequestRetryHandler will be removed
|
||||
final HttpRequestRetryHandler retryHandlerCopy = this.retryHandler;
|
||||
if (retryHandlerCopy != null) {
|
||||
execChainDefinition.addFirst(
|
||||
new RetryExec(retryHandlerCopy),
|
||||
ChainElement.RETRY_IO_ERROR.name());
|
||||
} else {
|
||||
HttpRequestRetryStrategy retryStrategyCopy = this.retryStrategy;
|
||||
if (retryStrategyCopy == null) {
|
||||
retryStrategyCopy = DefaultHttpRequestRetryStrategy.INSTANCE;
|
||||
}
|
||||
execChainDefinition.addFirst(
|
||||
new HttpRequestRetryExec(retryStrategyCopy),
|
||||
ChainElement.RETRY.name());
|
||||
HttpRequestRetryStrategy retryStrategyCopy = this.retryStrategy;
|
||||
if (retryStrategyCopy == null) {
|
||||
retryStrategyCopy = DefaultHttpRequestRetryStrategy.INSTANCE;
|
||||
}
|
||||
execChainDefinition.addFirst(
|
||||
new HttpRequestRetryExec(retryStrategyCopy),
|
||||
ChainElement.RETRY.name());
|
||||
}
|
||||
|
||||
HttpRoutePlanner routePlannerCopy = this.routePlanner;
|
||||
|
@ -1,122 +0,0 @@
|
||||
/*
|
||||
* ====================================================================
|
||||
* 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.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
|
||||
package org.apache.hc.client5.http.impl.classic;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.apache.hc.client5.http.HttpRequestRetryHandler;
|
||||
import org.apache.hc.client5.http.HttpRoute;
|
||||
import org.apache.hc.client5.http.classic.ExecChain;
|
||||
import org.apache.hc.client5.http.classic.ExecChainHandler;
|
||||
import org.apache.hc.client5.http.protocol.HttpClientContext;
|
||||
import org.apache.hc.core5.annotation.Contract;
|
||||
import org.apache.hc.core5.annotation.Internal;
|
||||
import org.apache.hc.core5.annotation.ThreadingBehavior;
|
||||
import org.apache.hc.core5.http.ClassicHttpRequest;
|
||||
import org.apache.hc.core5.http.ClassicHttpResponse;
|
||||
import org.apache.hc.core5.http.HttpEntity;
|
||||
import org.apache.hc.core5.http.HttpException;
|
||||
import org.apache.hc.core5.http.NoHttpResponseException;
|
||||
import org.apache.hc.core5.util.Args;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Request execution handler in the classic request execution chain
|
||||
* responsible for making a decision whether a request failed due to
|
||||
* an I/O error should be re-executed.
|
||||
* <p>
|
||||
* Further responsibilities such as communication with the opposite
|
||||
* endpoint is delegated to the next executor in the request execution
|
||||
* chain.
|
||||
* </p>
|
||||
*
|
||||
* @since 4.3
|
||||
*/
|
||||
@Contract(threading = ThreadingBehavior.STATELESS)
|
||||
@Internal
|
||||
public final class RetryExec implements ExecChainHandler {
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(getClass());
|
||||
|
||||
private final HttpRequestRetryHandler retryHandler;
|
||||
|
||||
public RetryExec(
|
||||
final HttpRequestRetryHandler retryHandler) {
|
||||
Args.notNull(retryHandler, "HTTP request retry handler");
|
||||
this.retryHandler = retryHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClassicHttpResponse execute(
|
||||
final ClassicHttpRequest request,
|
||||
final ExecChain.Scope scope,
|
||||
final ExecChain chain) throws IOException, HttpException {
|
||||
Args.notNull(request, "HTTP request");
|
||||
Args.notNull(scope, "Scope");
|
||||
final String exchangeId = scope.exchangeId;
|
||||
final HttpRoute route = scope.route;
|
||||
final HttpClientContext context = scope.clientContext;
|
||||
ClassicHttpRequest currentRequest = request;
|
||||
for (int execCount = 1;; execCount++) {
|
||||
try {
|
||||
return chain.proceed(currentRequest, scope);
|
||||
} catch (final IOException ex) {
|
||||
if (scope.execRuntime.isExecutionAborted()) {
|
||||
throw new RequestFailedException("Request aborted");
|
||||
}
|
||||
final HttpEntity requestEntity = request.getEntity();
|
||||
if (requestEntity != null && !requestEntity.isRepeatable()) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug(exchangeId + ": cannot retry non-repeatable request");
|
||||
}
|
||||
throw ex;
|
||||
}
|
||||
if (retryHandler.retryRequest(request, ex, execCount, context)) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug(exchangeId + ": " + ex.getMessage(), ex);
|
||||
}
|
||||
if (log.isInfoEnabled()) {
|
||||
log.info("Recoverable I/O exception ("+ ex.getClass().getName() + ") " +
|
||||
"caught when processing request to " + route);
|
||||
}
|
||||
currentRequest = ClassicRequestCopier.INSTANCE.copy(scope.originalRequest);
|
||||
} else {
|
||||
if (ex instanceof NoHttpResponseException) {
|
||||
final NoHttpResponseException updatedex = new NoHttpResponseException(
|
||||
route.getTargetHost().toHostString() + " failed to respond");
|
||||
updatedex.setStackTrace(ex.getStackTrace());
|
||||
throw updatedex;
|
||||
}
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,88 +0,0 @@
|
||||
/*
|
||||
* ====================================================================
|
||||
* 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.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
package org.apache.hc.client5.http.impl;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
import org.apache.hc.client5.http.classic.methods.HttpGet;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
|
||||
@SuppressWarnings("boxing") // test class
|
||||
public class TestDefaultHttpRequestRetryHandler {
|
||||
|
||||
@Test
|
||||
public void noRetryOnConnectTimeout() throws Exception {
|
||||
final HttpGet request = new HttpGet("/");
|
||||
|
||||
final DefaultHttpRequestRetryHandler retryHandler = new DefaultHttpRequestRetryHandler();
|
||||
Assert.assertEquals(3, retryHandler.getRetryCount());
|
||||
|
||||
Assert.assertFalse(retryHandler.retryRequest(request, new SocketTimeoutException(), 1, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noRetryOnUnknownHost() throws Exception {
|
||||
final HttpGet request = new HttpGet("/");
|
||||
|
||||
final DefaultHttpRequestRetryHandler retryHandler = new DefaultHttpRequestRetryHandler();
|
||||
|
||||
Assert.assertFalse(retryHandler.retryRequest(request, new UnknownHostException(), 1, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noRetryOnAbortedRequests() throws Exception{
|
||||
final HttpGet request = new HttpGet("/");
|
||||
request.cancel();
|
||||
|
||||
final DefaultHttpRequestRetryHandler retryHandler = new DefaultHttpRequestRetryHandler();
|
||||
|
||||
Assert.assertFalse(retryHandler.retryRequest(request, new IOException(), 3, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void retryOnNonAbortedRequests() throws Exception{
|
||||
final HttpGet request = new HttpGet("/");
|
||||
|
||||
final DefaultHttpRequestRetryHandler retryHandler = new DefaultHttpRequestRetryHandler();
|
||||
|
||||
Assert.assertTrue(retryHandler.retryRequest(request, new IOException(), 3, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noRetryOnConnectionTimeout() throws Exception{
|
||||
final HttpGet request = new HttpGet("/");
|
||||
|
||||
final DefaultHttpRequestRetryHandler retryHandler = new DefaultHttpRequestRetryHandler();
|
||||
|
||||
Assert.assertFalse(retryHandler.retryRequest(request, new SocketTimeoutException(), 3, null));
|
||||
}
|
||||
|
||||
}
|
@ -1,186 +0,0 @@
|
||||
/*
|
||||
* ====================================================================
|
||||
* 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.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
package org.apache.hc.client5.http.impl.classic;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.apache.hc.client5.http.HttpRequestRetryHandler;
|
||||
import org.apache.hc.client5.http.HttpRoute;
|
||||
import org.apache.hc.client5.http.classic.ExecChain;
|
||||
import org.apache.hc.client5.http.classic.ExecRuntime;
|
||||
import org.apache.hc.client5.http.classic.methods.HttpGet;
|
||||
import org.apache.hc.client5.http.classic.methods.HttpPost;
|
||||
import org.apache.hc.client5.http.entity.EntityBuilder;
|
||||
import org.apache.hc.client5.http.protocol.HttpClientContext;
|
||||
import org.apache.hc.core5.http.ClassicHttpRequest;
|
||||
import org.apache.hc.core5.http.Header;
|
||||
import org.apache.hc.core5.http.HttpHost;
|
||||
import org.apache.hc.core5.http.HttpRequest;
|
||||
import org.apache.hc.core5.http.protocol.HttpContext;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.stubbing.Answer;
|
||||
|
||||
@SuppressWarnings({"boxing","static-access"}) // test code
|
||||
public class TestRetryExec {
|
||||
|
||||
@Mock
|
||||
private HttpRequestRetryHandler retryHandler;
|
||||
@Mock
|
||||
private ExecRuntime endpoint;
|
||||
@Mock
|
||||
private ExecChain chain;
|
||||
|
||||
private RetryExec retryExec;
|
||||
private HttpHost target;
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
retryExec = new RetryExec(retryHandler);
|
||||
target = new HttpHost("localhost", 80);
|
||||
}
|
||||
|
||||
@Test(expected = IOException.class)
|
||||
public void testFundamentals() throws Exception {
|
||||
final HttpRoute route = new HttpRoute(target);
|
||||
final HttpGet originalRequest = new HttpGet("/test");
|
||||
originalRequest.addHeader("header", "this");
|
||||
originalRequest.addHeader("header", "that");
|
||||
final HttpClientContext context = HttpClientContext.create();
|
||||
|
||||
Mockito.when(chain.proceed(
|
||||
Mockito.<ClassicHttpRequest>any(),
|
||||
Mockito.<ExecChain.Scope>any())).thenAnswer(new Answer<Object>() {
|
||||
|
||||
@Override
|
||||
public Object answer(final InvocationOnMock invocationOnMock) throws Throwable {
|
||||
final Object[] args = invocationOnMock.getArguments();
|
||||
final ClassicHttpRequest wrapper = (ClassicHttpRequest) args[0];
|
||||
final Header[] headers = wrapper.getHeaders();
|
||||
Assert.assertEquals(2, headers.length);
|
||||
Assert.assertEquals("this", headers[0].getValue());
|
||||
Assert.assertEquals("that", headers[1].getValue());
|
||||
wrapper.addHeader("Cookie", "monster");
|
||||
throw new IOException("Ka-boom");
|
||||
}
|
||||
|
||||
});
|
||||
Mockito.when(retryHandler.retryRequest(
|
||||
Mockito.<HttpRequest>any(),
|
||||
Mockito.<IOException>any(),
|
||||
Mockito.eq(1),
|
||||
Mockito.<HttpContext>any())).thenReturn(Boolean.TRUE);
|
||||
final ExecChain.Scope scope = new ExecChain.Scope("test", route, originalRequest, endpoint, context);
|
||||
final ClassicHttpRequest request = ClassicRequestCopier.INSTANCE.copy(originalRequest);
|
||||
try {
|
||||
retryExec.execute(request, scope, chain);
|
||||
} catch (final IOException ex) {
|
||||
Mockito.verify(chain, Mockito.times(2)).proceed(
|
||||
Mockito.<ClassicHttpRequest>any(),
|
||||
Mockito.same(scope));
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected = IOException.class)
|
||||
public void testAbortedRequest() throws Exception {
|
||||
final HttpRoute route = new HttpRoute(target);
|
||||
final HttpGet originalRequest = new HttpGet("/test");
|
||||
final HttpClientContext context = HttpClientContext.create();
|
||||
|
||||
Mockito.when(chain.proceed(
|
||||
Mockito.<ClassicHttpRequest>any(),
|
||||
Mockito.<ExecChain.Scope>any())).thenThrow(new IOException("Ka-boom"));
|
||||
Mockito.when(endpoint.isExecutionAborted()).thenReturn(true);
|
||||
|
||||
final ExecChain.Scope scope = new ExecChain.Scope("test", route, originalRequest, endpoint, context);
|
||||
final ClassicHttpRequest request = ClassicRequestCopier.INSTANCE.copy(originalRequest);
|
||||
try {
|
||||
retryExec.execute(request, scope, chain);
|
||||
} catch (final IOException ex) {
|
||||
Mockito.verify(chain, Mockito.times(1)).proceed(
|
||||
Mockito.same(request),
|
||||
Mockito.same(scope));
|
||||
Mockito.verify(retryHandler, Mockito.never()).retryRequest(
|
||||
Mockito.<HttpRequest>any(),
|
||||
Mockito.<IOException>any(),
|
||||
Mockito.anyInt(),
|
||||
Mockito.<HttpContext>any());
|
||||
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected = IOException.class)
|
||||
public void testNonRepeatableRequest() throws Exception {
|
||||
final HttpRoute route = new HttpRoute(target);
|
||||
final HttpPost originalRequest = new HttpPost("/test");
|
||||
originalRequest.setEntity(EntityBuilder.create()
|
||||
.setStream(new ByteArrayInputStream(new byte[]{}))
|
||||
.build());
|
||||
final HttpClientContext context = HttpClientContext.create();
|
||||
|
||||
Mockito.when(chain.proceed(
|
||||
Mockito.<ClassicHttpRequest>any(),
|
||||
Mockito.<ExecChain.Scope>any())).thenAnswer(new Answer<Object>() {
|
||||
|
||||
@Override
|
||||
public Object answer(final InvocationOnMock invocationOnMock) throws Throwable {
|
||||
final Object[] args = invocationOnMock.getArguments();
|
||||
final ClassicHttpRequest req = (ClassicHttpRequest) args[0];
|
||||
req.getEntity().writeTo(new ByteArrayOutputStream());
|
||||
throw new IOException("Ka-boom");
|
||||
}
|
||||
|
||||
});
|
||||
Mockito.when(retryHandler.retryRequest(
|
||||
Mockito.<HttpRequest>any(),
|
||||
Mockito.<IOException>any(),
|
||||
Mockito.eq(1),
|
||||
Mockito.<HttpContext>any())).thenReturn(Boolean.TRUE);
|
||||
final ExecChain.Scope scope = new ExecChain.Scope("test", route, originalRequest, endpoint, context);
|
||||
final ClassicHttpRequest request = ClassicRequestCopier.INSTANCE.copy(originalRequest);
|
||||
try {
|
||||
retryExec.execute(request, scope, chain);
|
||||
} catch (final IOException ex) {
|
||||
Mockito.verify(chain, Mockito.times(1)).proceed(
|
||||
Mockito.same(request),
|
||||
Mockito.same(scope));
|
||||
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user