使用apache httpclient和jetty容器进行休息呼叫时,无法捕获空闲超时异常

问题描述

我已经嵌入了码头弹簧启动应用程序。在使用apache httpclient lib进行REST调用时,建立连接后要进行REST调用的目标服务在建立连接后会关闭,然后在几毫秒内我可以看到控制台上有一个空闲超时异常。

我需要在代码中捕获此异常,但无法做到这一点。请帮助!!!

下面是日志

2020-11-11 07:26:53.208 SocketChannelEndPoint@4107f6f5{/10.192.73.136:37100<->/10.195.165.64:8080,OPEN,fill=FI,flush=-,to=29955/30000}{io=1/1,kio=1,kro=1}->httpconnection@2419e554[p=HttpParser{s=START,0 of -1},g=HttpGenerator@5a570f7c{s=START}]=>HttpChanneloverHttp@33c85589{s=HttpChannelState@55afe3fc{s=IDLE rs=BLOCKING os=OPEN is=IDLE awp=false se=false i=true al=0},r=1,c=false/false,a=IDLE,uri=null,age=0} idle timeout check,elapsed: 29955 ms,remaining: 45 ms
2020-11-11 07:26:53.253 SocketChannelEndPoint@4107f6f5{/10.192.73.136:37100<->/10.195.165.64:8080,to=30001/30000}{io=1/1,elapsed: 30001 ms,remaining: -1 ms
2020-11-11 07:26:53.253 SocketChannelEndPoint@4107f6f5{/10.192.73.136:37100<->/10.195.165.64:8080,age=0} idle timeout expired
2020-11-11 07:26:53.254 onFail FillInterest@2bc26da2{AC.ReadCB@2419e554{httpconnection@2419e554::SocketChannelEndPoint@4107f6f5{/10.192.73.136:37100<->/10.195.165.64:8080,age=0}}}
java.util.concurrent.TimeoutException: Idle timeout expired: 30001/30000 ms
        at org.eclipse.jetty.io.IdleTimeout.checkIdleTimeout(IdleTimeout.java:171) ~[jetty-io-9.4.26.v20200117.jar!/:9.4.26.v20200117]
        at org.eclipse.jetty.io.IdleTimeout.idleCheck(IdleTimeout.java:113) ~[jetty-io-9.4.26.v20200117.jar!/:9.4.26.v20200117]
        at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515) [?:?]
        at java.util.concurrent.FutureTask.run(FutureTask.java:264) [?:?]
        at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:304) [?:?]
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) [?:?]
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) [?:?]
        at java.lang.Thread.run(Thread.java:834) [?:?]
2020-11-11 07:26:53.254 close HttpParser{s=START,0 of -1}
2020-11-11 07:26:53.254 START --> CLOSE
2020-11-11 07:26:53.254 httpconnection@2419e554::SocketChannelEndPoint@4107f6f5{/10.192.73.136:37100<->/10.195.165.64:8080,fill=-,to=30002/30000}{io=1/1,kro=1}->httpconnection@2419e554[p=HttpParser{s=CLOSE,age=0} onFillInterestedFailed {}
2020-11-11 07:26:53.254 shutdownOutput SocketChannelEndPoint@4107f6f5{/10.192.73.136:37100<->/10.195.165.64:8080,age=0}
2020-11-11 07:26:53.256 fillInterested httpconnection@2419e554::SocketChannelEndPoint@4107f6f5{/10.192.73.136:37100<->/10.195.165.64:8080,OSHUT,to=30004/30000}{io=1/1,age=0}
2020-11-11 07:26:53.256 interested FillInterest@2bc26da2{AC.ReadCB@2419e554{httpconnection@2419e554::SocketChannelEndPoint@4107f6f5{/10.192.73.136:37100<->/10.195.165.64:8080,to=0/30000}{io=1/1,age=0}}}
2020-11-11 07:26:53.256 changeInterests p=false 1->1 for SocketChannelEndPoint@4107f6f5{/10.192.73.136:37100<->/10.195.165.64:8080,age=0}
2020-11-11 07:26:53.256 Queued change org.eclipse.jetty.io.ChannelEndPoint$1@158db25 on ManagedSelector@2935fd2c{STARTED} id=0 keys=2 selected=0 updates=0
2020-11-11 07:26:53.256 Wakeup on submit ManagedSelector@2935fd2c{STARTED} id=0 keys=2 selected=0 updates=1
2020-11-11 07:26:53.257 Selector sun.nio.ch.EPollSelectorImpl@50d0532a woken with none selected
2020-11-11 07:26:53.257 Selector sun.nio.ch.EPollSelectorImpl@50d0532a woken up from select,0/0/2 selected
2020-11-11 07:26:53.257 Selector sun.nio.ch.EPollSelectorImpl@50d0532a processing 0 keys,1 updates
2020-11-11 07:26:53.257 updateable 1
2020-11-11 07:26:53.257 ignored: WriteFlusher@5f339f8b{IDLE}->null
java.util.concurrent.TimeoutException: Idle timeout expired: 30001/30000 ms
        at org.eclipse.jetty.io.IdleTimeout.checkIdleTimeout(IdleTimeout.java:171) ~[jetty-io-9.4.26.v20200117.jar!/:9.4.26.v20200117]
        at org.eclipse.jetty.io.IdleTimeout.idleCheck(IdleTimeout.java:113) ~[jetty-io-9.4.26.v20200117.jar!/:9.4.26.v20200117]
        at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515) [?:?]
        at java.util.concurrent.FutureTask.run(FutureTask.java:264) [?:?]
        at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:304) [?:?]
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) [?:?]
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) [?:?]
        at java.lang.Thread.run(Thread.java:834) [?:?]
try{
ConnectionKeepAliveStrategy myStrategy = new ConnectionKeepAliveStrategy() {

                public long getKeepAliveDuration(HttpResponse response,HttpContext context) {
                    // Honor 'keep-alive' header
                    HeaderElementIterator it = new BasicHeaderElementIterator(
                            response.headerIterator(HTTP.CONN_KEEP_ALIVE));
                    while (it.hasNext()) {
                        HeaderElement he = it.nextElement();
                        String param = he.getName();
                        String value = he.getValue();
                        if (value != null && param.equalsIgnoreCase("timeout")) {
                            try {
                                return Long.parseLong(value) * 1000;
                            } catch(NumberFormatException ignore) {
                            }
                        }
                    }
                    // keep alive for 30 seconds only
                    return 30 * 1000;
                }

            };
httpClient =  HttpClients.custom().setKeepAliveStrategy(myStrategy).build();
postRequest = new HttpPost(url);
response = httpClient.execute(postRequest);
} catch(TimeoutException e) {
            throw e;
        } catch(IOException e) {
            throw e;
        } catch (Exception e) {
            throw e;
        }
 

解决方法

由于这是服务器端的空闲超时,因此您无法在客户端进行任何操作来捕获它。

根据HTTP规范,服务器可以出于任何原因自由关闭连接。

客户必须决定下一步做什么。

请参见https://stackoverflow.com/a/45019073/775715

这听起来像是您遇到的经典情况,即HTTP响应花费的时间比服务器定义的空闲超时时间长。

您可能会想延长服务器的空闲超时时间,这将对少数HTTP客户端有效。

等等吗?我为什么要说客户?嗯,客户端的空闲超时通常是您无法控制的,例如在Web浏览器或HTTP中介(例如公司代理,移动网络代理或负载平衡器)上。因此,如果您增加服务器上的空闲超时,现在可能会开始从不同位置看到空闲超时失败。

这意味着您必须以戏剧性的方式解决问题。

最简单的选择是将任务固定在服务器端,以免花费很长时间进行响应。

下一个选择是让服务器端任务在获得信息后立即将信息反馈给客户端(即使信息不完整),从而使连接保持活动状态。不要等到所有信息都写到客户端后。

一个例子是长时间查找信息,如果您有一些信息,则将其写入客户端(以适当的content-type / format。部分json,部分xml等)。然后继续这样做,直到获得完整的信息并可以完成您的回复为止。

另一种常见方法是将长时间运行的任务分解为多个请求/响应交换。

  1. 您的客户请求长期运行的任务,您将获得结果的唯一标识符。
  2. 您的客户端会随着时间的推移使用此唯一标识符,以发出更多有关结果状态的请求。当没有内容时返回状态码204(尚无),有内容时返回状态码200,如果没有响应则返回404,如果唯一标识符不再有效,则返回410。

另一种常见方法是限制任务的范围,以免其花费那么长时间。

  1. 您的客户端请求一个任务,该任务将返回大量数据。
  2. 服务器以(限制)数据的单个“页面”以及用于请求下一个“页面”的标识符进行响应。