聊聊HttpClient的重试机制

news/2024/12/2 21:31:58/

本文主要研究一下HttpClient的重试机制

HttpRequestRetryHandler

org/apache/http/client/HttpRequestRetryHandler.java

public interface HttpRequestRetryHandler {/*** Determines if a method should be retried after an IOException* occurs during execution.** @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(IOException exception, int executionCount, HttpContext context);}

HttpRequestRetryHandler接口定义了retryRequest方法,它接收IOException、executionCount及context,然后判断是否可以重试

DefaultHttpRequestRetryHandler

org/apache/http/impl/client/DefaultHttpRequestRetryHandler.java

@Contract(threading = ThreadingBehavior.IMMUTABLE)
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;/** Whether or not methods that have successfully sent their request will be retried */private final boolean requestSentRetryEnabled;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 requestSentRetryEnabled true if it's OK to retry requests that have been sent* @param clazzes the IOException types that should not be retried* @since 4.3*/protected DefaultHttpRequestRetryHandler(final int retryCount,final boolean requestSentRetryEnabled,final Collection<Class<? extends IOException>> clazzes) {super();this.retryCount = retryCount;this.requestSentRetryEnabled = requestSentRetryEnabled;this.nonRetriableClasses = new HashSet<Class<? extends IOException>>();for (final Class<? extends IOException> clazz: clazzes) {this.nonRetriableClasses.add(clazz);}}/*** 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>SSLException</li>* </ul>* @param retryCount how many times to retry; 0 means no retries* @param requestSentRetryEnabled true if it's OK to retry non-idempotent requests that have been sent*/@SuppressWarnings("unchecked")public DefaultHttpRequestRetryHandler(final int retryCount, final boolean requestSentRetryEnabled) {this(retryCount, requestSentRetryEnabled, Arrays.asList(InterruptedIOException.class,UnknownHostException.class,ConnectException.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, false);}/*** Used {@code retryCount} and {@code requestSentRetryEnabled} to determine* if the given method should be retried.*/@Overridepublic boolean retryRequest(final IOException exception,final int executionCount,final HttpContext context) {Args.notNull(exception, "Exception parameter");Args.notNull(context, "HTTP context");if (executionCount > this.retryCount) {// Do not retry if over max retry countreturn false;}if (this.nonRetriableClasses.contains(exception.getClass())) {return false;}for (final Class<? extends IOException> rejectException : this.nonRetriableClasses) {if (rejectException.isInstance(exception)) {return false;}}final HttpClientContext clientContext = HttpClientContext.adapt(context);final HttpRequest request = clientContext.getRequest();if(requestIsAborted(request)){return false;}if (handleAsIdempotent(request)) {// Retry if the request is considered idempotentreturn true;}if (!clientContext.isRequestSent() || this.requestSentRetryEnabled) {// Retry if the request has not been sent fully or// if it's OK to retry methods that have been sentreturn true;}// otherwise do not retryreturn false;}/*** @return {@code true} if this handler will retry methods that have* successfully sent their request, {@code false} otherwise*/public boolean isRequestSentRetryEnabled() {return requestSentRetryEnabled;}/*** @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 !(request instanceof HttpEntityEnclosingRequest);}/*** @since 4.2** @deprecated (4.3)*/@Deprecatedprotected boolean requestIsAborted(final HttpRequest request) {HttpRequest req = request;if (request instanceof RequestWrapper) { // does not forward request to originalreq = ((RequestWrapper) request).getOriginal();}return (req instanceof HttpUriRequest && ((HttpUriRequest)req).isAborted());}}

DefaultHttpRequestRetryHandler实现了HttpRequestRetryHandler接口,其无参构造器默认将InterruptedIOException、UnknownHostException、ConnectException、SSLException设定为不重试的异常,默认retryCount为3,requestSentRetryEnabled为false;其retryRequest方法先判断executionCount是否超出retryCount,接着判断异常类型是否是不重试的异常类型,若request为aborted则返回false,若request非HttpEntityEnclosingRequest则表示幂等请求,返回true,若请求未完全发送则返回true,其余的默认返回false。

RetryExec

org/apache/http/impl/execchain/RetryExec.java

@Contract(threading = ThreadingBehavior.IMMUTABLE_CONDITIONAL)
public class RetryExec implements ClientExecChain {private final Log log = LogFactory.getLog(getClass());private final ClientExecChain requestExecutor;private final HttpRequestRetryHandler retryHandler;public RetryExec(final ClientExecChain requestExecutor,final HttpRequestRetryHandler retryHandler) {Args.notNull(requestExecutor, "HTTP request executor");Args.notNull(retryHandler, "HTTP request retry handler");this.requestExecutor = requestExecutor;this.retryHandler = retryHandler;}@Overridepublic CloseableHttpResponse execute(final HttpRoute route,final HttpRequestWrapper request,final HttpClientContext context,final HttpExecutionAware execAware) throws IOException, HttpException {Args.notNull(route, "HTTP route");Args.notNull(request, "HTTP request");Args.notNull(context, "HTTP context");final Header[] origheaders = request.getAllHeaders();for (int execCount = 1;; execCount++) {try {return this.requestExecutor.execute(route, request, context, execAware);} catch (final IOException ex) {if (execAware != null && execAware.isAborted()) {this.log.debug("Request has been aborted");throw ex;}if (retryHandler.retryRequest(ex, execCount, context)) {if (this.log.isInfoEnabled()) {this.log.info("I/O exception ("+ ex.getClass().getName() +") caught when processing request to "+ route +": "+ ex.getMessage());}if (this.log.isDebugEnabled()) {this.log.debug(ex.getMessage(), ex);}if (!RequestEntityProxy.isRepeatable(request)) {this.log.debug("Cannot retry non-repeatable request");throw new NonRepeatableRequestException("Cannot retry request " +"with a non-repeatable request entity", ex);}request.setHeaders(origheaders);if (this.log.isInfoEnabled()) {this.log.info("Retrying request to " + route);}} else {if (ex instanceof NoHttpResponseException) {final NoHttpResponseException updatedex = new NoHttpResponseException(route.getTargetHost().toHostString() + " failed to respond");updatedex.setStackTrace(ex.getStackTrace());throw updatedex;}throw ex;}}}}}

RetryExec实现了ClientExecChain接口,其execute方法会循环执行requestExecutor.execute,它catch了IOException,对于retryHandler.retryRequest(ex, execCount, context)返回true的,会在通过RequestEntityProxy.isRepeatable(request)判断一下是否是可重复读取的request,不是则抛出NonRepeatableRequestException;对于retryHandler.retryRequest返回false的则针对NoHttpResponseException重新包装一下,将targetHost体现在message里头然后重新抛出

HttpEntityEnclosingRequest

org/apache/http/HttpEntityEnclosingRequest.java

public interface HttpEntityEnclosingRequest extends HttpRequest {/*** Tells if this request should use the expect-continue handshake.* The expect continue handshake gives the server a chance to decide* whether to accept the entity enclosing request before the possibly* lengthy entity is sent across the wire.* @return true if the expect continue handshake should be used, false if* not.*/boolean expectContinue();/*** Associates the entity with this request.** @param entity the entity to send.*/void setEntity(HttpEntity entity);/*** Returns the entity associated with this request.** @return entity*/HttpEntity getEntity();}

HttpEntityEnclosingRequest定义了getEntity、setEntity、expectContinue方法,它的子类有HttpPut、HttpPost、HttpPatch、HttpDelete等

RequestEntityProxy.isRepeatable

org/apache/http/impl/execchain/RequestEntityProxy.java

class RequestEntityProxy implements HttpEntity  {private final HttpEntity original;private boolean consumed = false;public boolean isConsumed() {return consumed;}static boolean isRepeatable(final HttpRequest request) {if (request instanceof HttpEntityEnclosingRequest) {final HttpEntity entity = ((HttpEntityEnclosingRequest) request).getEntity();if (entity != null) {if (isEnhanced(entity)) {final RequestEntityProxy proxy = (RequestEntityProxy) entity;if (!proxy.isConsumed()) {return true;}}return entity.isRepeatable();}}return true;}
}

RequestEntityProxy提供了静态方法isRepeatable用于判断该request的entity是否可以重复读取,对于非HttpEntityEnclosingRequest的返回true,是HttpEntityEnclosingRequest类型的话则判断entity.isRepeatable(),若entity是RequestEntityProxy类型的,则通过RequestEntityProxy.isConsumed来判断

entity.isRepeatable()

public interface HttpEntity {/*** Tells if the entity is capable of producing its data more than once.* A repeatable entity's getContent() and writeTo(OutputStream) methods* can be called more than once whereas a non-repeatable entity's can not.* @return true if the entity is repeatable, false otherwise.*/boolean isRepeatable();//......
}    

HttpEntity接口定义了isRepeatable方法,用于表示entity的content及OutputStream是否可以读写多次。其实现类里头,BufferedHttpEntity、ByteArrayEntity、EntityTemplate、FileEntity、SerializableEntity、StringEntity为true,BasicHttpEntity、InputStreamEntity、StreamingHttpEntity为false

小结

HttpRequestRetryHandler接口定义了retryRequest方法,它接收IOException、executionCount及context,然后判断是否可以重试

DefaultHttpRequestRetryHandler实现了HttpRequestRetryHandler接口,其无参构造器默认将InterruptedIOException、UnknownHostException、ConnectException、SSLException设定为不重试的异常,默认retryCount为3,requestSentRetryEnabled为false;其retryRequest方法先判断executionCount是否超出retryCount,接着判断异常类型是否是不重试的异常类型,若request为aborted则返回false,若request非HttpEntityEnclosingRequest则表示幂等请求,返回true,若请求未完全发送则返回true,其余的默认返回false。

RetryExec实现了ClientExecChain接口,其execute方法会循环执行requestExecutor.execute,它catch了IOException,对于retryHandler.retryRequest(ex, execCount, context)返回true的,会在通过RequestEntityProxy.isRepeatable(request)判断一下是否是可重复读取的request,不是则抛出NonRepeatableRequestException

DefaultHttpRequestRetryHandler针对不是幂等请求的HttpEntityEnclosingRequest类型(HttpPut、HttpPost、HttpPatch、HttpDelete),不会重试;若retryHandler.retryRequest返回可以重试,RetryExec还有一个repeatable的判断,BufferedHttpEntity、ByteArrayEntity、EntityTemplate、FileEntity、SerializableEntity、StringEntity为true,BasicHttpEntity、InputStreamEntity、StreamingHttpEntity为false


http://www.ppmy.cn/news/1149893.html

相关文章

JavaWeb---Servlet

1.Srvlet概述 Servlet是运行在java服务器端的程序&#xff0c;用于接收和响应来着客户端基于HTTP协议的请求 如果想实现Servlet的功能&#xff0c;可以通过实现javax。servlet。Servlet接口或者继承它的实现类 核心方法&#xff1a;service&#xff08;&#xff09;&#xf…

【Method】把 arXiv论文 转换为 HTML5 网页

文章目录 MethodReference https://ar5iv.labs.arxiv.org/ Articles from arXiv.org as responsive HTML5 web pages. 可以将来自 arXiv 的 PDF 论文渲染成 HTML5 网页版本。 Method View any arXiv article URL by changing the X to a 5. 将 arXiv 网址中的 x 换成 5 再回…

python随手小练5

1、求1-100的累加和&#xff08;终止条件 1-100&#xff09;&#xff08;while和for两种&#xff09; #while循环 count 0 index 0 while index < 100:count indexindex 1 print(count)#for循环 sum 0 for i in range(0,101):sum i print(sum)结果&#xff1a; 5050 2…

1688拍立淘API接口分享

拍立淘接口&#xff0c;顾名思义&#xff0c;就是通过图片搜索到相关商品列表。通过此接口&#xff0c;可以实现图片搜索爆款商品等功能。 接口地址&#xff1a;1688.item_search_img 公共参数 名称类型必须描述keyString是调用key&#xff08;必须以GET方式拼接在URL中&…

车载相关名词--车载数据中心方案

车载数据中心方案 参考链接:https://zhuanlan.zhihu.com/p/600031042?utm_id=0 下面这张图是小鹏汽车嵌入式系统高级专家 唐黾 在同ARM一起的一个演讲稿中发布的,是一张未来车载数据中心单芯片方案构想图。主要针对的是智驾域和座舱域融合方案,下面对如上图的内外部组件及…

野火开发板使用FlyMcu一键ISP下载时

1.ISP 一键下载 野火开发板使用FlyMcu一键ISP下载时&#xff0c;记得拔掉JTAG那个20针的东西&#xff0c;要不然一直芯片超时不连接。 bsp:9600&#xff0c;使用共写入2KB,进度100%,耗时16641毫秒。 bsp:115200&#xff0c;共写入2KB,进度100%,耗时2188毫秒。 bsp:115200&#…

使用telnet+nc工具测试网络连通性

背景&#xff1a; 正常情况下使用ping命令即可测试网络的连通性&#xff0c;但如果做了内网穿透(端口转发)&#xff0c;则需要指定网络端口&#xff0c;此时ping命令无法实现ipport的连通性测试。则可以使用telnetnc测试网络连通性。 环境&#xff1a; 两台服务器都是按照的De…

RT-Thread SMP介绍与移植(学习)

RT-Thread SMP介绍与移植 SMP&#xff1a;对称多处理&#xff08;Symmetrical Multi-Processing&#xff09;简称SMP&#xff0c;是指在一个计算机上汇集了一组处理器&#xff08;多CPU&#xff09;&#xff0c;各CPU之间共享内存子系统以及总线结构。 RT-Thread自4.0.0版本开…