HttpClient默认重试策略不处理SocketTimeoutException

HttpClient默认重试策略不处理SocketTimeoutException

大家好,又见面了,我是你们的朋友全栈君。

背景

与公司外服务采用http交互,使用httpClient 4.5.2版本的默认重试策略DefaultHttpRequestRetryHandler,发现对方接口连接超时(3秒)后, 并不会重试 而是直接抛出ConnectTimeoutException,不符合预期。

结论先行

httpClient默认重试策略DefaultHttpRequestRetryHandler针对连接超时和获取数据超时并不会重试,需要自定义重试策略。

问题分析

详细场景

采用HttpClient 4.5.2发起http请求,代码如下,使用方式正常

//http设置,retryHandler采用组件提供默认的重试策略DefaultHttpRequestRetryHandler,重试次数为3
return HttpClients.custom()
                .setConnectionManager(****)
                .setDefaultRequestConfig(****)
                .setRetryHandler(new DefaultHttpRequestRetryHandler())
                .build();

//请求发起主要代码
HttpPost httpPost = new HttpPost(url);
httpPost.setEntity(new UrlEncodedFormEntity(nameValuePairs, UTF_8));
CloseableHttpResponse response = httpClient.execute(request);
HttpEntity entity = response.getEntity();
……其他关闭资源代码

请求后过了超时时间3s得到ConnectTimeoutException异常,且log中没有重试日志,堆栈如下:

Caused by: org.apache.http.conn.ConnectTimeoutException: Connect to www.XXX.com:443 [www.XXX.com/XX.1.XX.12] failed: connect timed out
	at org.apache.http.impl.conn.DefaultHttpClientConnectionOperator.connect(DefaultHttpClientConnectionOperator.java:150)
	at org.apache.http.impl.conn.PoolingHttpClientConnectionManager.connect(PoolingHttpClientConnectionManager.java:353)
	at org.apache.http.impl.execchain.MainClientExec.establishRoute(MainClientExec.java:380)
	at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:236)
	at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:184)
	at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:88)
	at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:110)
	at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:184)
	at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:82)
	at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:107)
	at com.sankuai.meituan.resv.platform.utils.HttpUtil.getResult(HttpUtil.java:124)
	... 36 more
Caused by: java.net.SocketTimeoutException: connect timed out
	at java.net.PlainSocketImpl.socketConnect(Native Method)
	at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350)
	at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206)
	at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188)
	at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392)
	at java.net.Socket.connect(Socket.java:589)
	at org.apache.http.conn.ssl.SSLConnectionSocketFactory.connectSocket(SSLConnectionSocketFactory.java:337)
	at org.apache.http.impl.conn.DefaultHttpClientConnectionOperator.connect(DefaultHttpClientConnectionOperator.java:141)
	... 46 more

原因分析

出现这个问题,只好看默认重试策略的代码了,DefaultHttpRequestRetryHandler实现了HttpRequestRetryHandler,其中有这么一个方法public boolean retryRequest(final IOException exception,final int executionCount,final HttpContext context),判断是否需要重试。

那么我们试图请求facebook(实验机器访问不了)来复现连接超时的情况,并进行断点调试,看这个方法的返回值。抛出的异常符合预期,即为ConnectTimeoutException,断点情况如下: timeout断点

该方法的确返回了false,即不会重试。

代码分析

实例化DefaultHttpRequestRetryHandler时会初始化nonRetriableClasses,结果如上图圈红出所示,然后发现这个继承关系public class ConnectTimeoutException extends InterruptedIOException {
,所以进入if分支返回false。

举一反三读取数据超时

连接超时不会重试,那读取数据超时是否会重试呢,根据继承关系public class SocketTimeoutException extends java.io.InterruptedIOException 判断是不会重试的。我们通过测试来复现下。

复现方式:http请求本地某个服务,设置SocketTimeout为3s,在服务端打上断点,让client调用超时。 结果的确没有重试,异常堆栈如下,

Caused by: java.net.SocketTimeoutException: Read timed out
	at java.net.SocketInputStream.socketRead0(Native Method)
	at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
	at java.net.SocketInputStream.read(SocketInputStream.java:171)
	at java.net.SocketInputStream.read(SocketInputStream.java:141)
	at org.apache.http.impl.io.SessionInputBufferImpl.streamRead(SessionInputBufferImpl.java:137)
	at org.apache.http.impl.io.SessionInputBufferImpl.fillBuffer(SessionInputBufferImpl.java:153)
	at org.apache.http.impl.io.SessionInputBufferImpl.readLine(SessionInputBufferImpl.java:282)
	at org.apache.http.impl.conn.DefaultHttpResponseParser.parseHead(DefaultHttpResponseParser.java:140)
	at org.apache.http.impl.conn.DefaultHttpResponseParser.parseHead(DefaultHttpResponseParser.java:57)
	at org.apache.http.impl.io.AbstractMessageParser.parse(AbstractMessageParser.java:259)
	at org.apache.http.impl.DefaultBHttpClientConnection.receiveResponseHeader(DefaultBHttpClientConnection.java:163)
	at org.apache.http.impl.conn.CPoolProxy.receiveResponseHeader(CPoolProxy.java:167)
	at org.apache.http.protocol.HttpRequestExecutor.doReceiveResponse(HttpRequestExecutor.java:273)
	at org.apache.http.protocol.HttpRequestExecutor.execute(HttpRequestExecutor.java:125)
	at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:271)
	at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:184)
	at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:88)
	at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:110)
	at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:184)
	at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:82)
	at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:107)

解决方案

自定义重试策略,某些异常时让它返回true重试就好。

private class MyHttpRequestRetryHandler implements HttpRequestRetryHandler{

        @Override
        public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
            if (executionCount > retryTimes){
                return false;
            }
            if (exception instanceof InterruptedIOException
                    || exception instanceof NoHttpResponseException) {
                // Timeout or 服务端断开连接
                return true;
            }
            // Unknown host
            if (exception instanceof UnknownHostException) {
                return false;
            }
            // SSL handshake exception
            if (exception instanceof SSLException) {
                return false;
            }

            final HttpClientContext clientContext = HttpClientContext.adapt(context);
            final HttpRequest request = clientContext.getRequest();
            boolean idempotent = !(request instanceof HttpEntityEnclosingRequest);
            if (idempotent) {
                // Retry if the request is considered idempotent
                return true;
            }
            return false;
        }
    }

几个超时时间设置参数

    /** 从连接池获取连接超时时间 **/
    private int connectionRequestTimeout = 500;

    /** 连接一个url的连接等待时间,建立socket连接时间 **/
    private int connectTimeout = 3000;

    /** 传输数据等待返回的超时时间 **/
    private int socketTimeout = 3000;

反思总结

coding时认为默认重试策略遇到连接超时是会重试的,但实际没有进行这种异常case的测试,也没有看默认策略的代码实现,所以线上出现超时并没有重试的情况。

总结 :为异常流程开发的处理方式,测试的时候虽然会麻烦些,但应该覆盖到,保证线上真正出现异常时恢复流程能正常work。

转载于:https://my.oschina.net/hebaodan/blog/1795348

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/107579.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • resttemplate post请求发送body_java发送post请求

    resttemplate post请求发送body_java发送post请求当使用RestTemplate进行http请求时,的确很方便,但是当需要进行post请求时遇到了坑1POST传递参数:采用LinkedMultiValueMap,不能使用HashMapStringurl=’http://posturl’;MultiValueMap<String,String>map=newLinkedMultiValueMap&lt……

  • 2014年度总结

    2014年度总结2015都过了近1个月了,早该好好总结一下了。   2014年,对我来说,意味着忙乱、匆促、迷惑、徘徊,还有最准确的表达:抉择!   还是老规矩,分别从生活、工作、学习三个维度来回顾。  【生活篇】   (1)陪伴妻儿     看着儿子一天天长大,仿佛每一天都有新的变化:从可以走路,到能说完整的句子,每一个关键事件,都让我和老婆欣喜不已!当

  • nactive15激活码_最新在线免费激活2022.02.11「建议收藏」

    (nactive15激活码)这是一篇idea技术相关文章,由全栈君为大家提供,主要知识点是关于2021JetBrains全家桶永久激活码的内容IntelliJ2021最新激活注册码,破解教程可免费永久激活,亲测有效,下面是详细链接哦~https://javaforall.cn/100143.html4KDDGND3CI-eyJsaWN…

  • Ljava/lang/Iterable与AbstractMethodError

    Ljava/lang/Iterable与AbstractMethodErrorjava.lang.AbstractMethodError:com.example.demo.repository.UserRepositoryImpl.findAll()Ljava/lang/Iterable; atorg.springframework.data.repository.CrudRepository$$FastClassBySpringCGLIB$$b5a31e6f.invo…

  • C#全角转换成半角并检查建议收藏

    新建一个项目quanbanjiao,在此项目下新建一个类Class11usingSystem;2usingSystem.Web;3usingSystem.Text;45name

    2021年12月20日
  • Python 正则表达式大全[通俗易懂]

    Python 正则表达式大全[通俗易懂]1前言正则表达式是对字符串(包括普通字符(例如,a到z之间的字母)和特殊字符(称为“元字符”))操作的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑。正则表达式是一种文本模式,该模式描述在搜索文本时要匹配的一个或多个字符串。上面都是官方的说明,博主自己的理解是(仅供参考):通过事先规定好一些特殊字符的匹配规则,然后利用这些字符进行组合来匹配各种复杂的字符串场景。比如现在的爬虫和数据分析,字符串校验等等都

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号