在 HttpComponentsClientHttpRequestFactory 中配置特定的 HTTPClient 属性

Config specific HTTPClient properties in HttpComponentsClientHttpRequestFactory

我正在 spring 使用 HttpComponentsClientHttpRequestFactory 与 http-outbound-gateway 集成。我有以下异常 NoHttpResponseException。

2019-08-13 16:09:51,839 http-bio-8443-exec-439 ERROR LoggingHandler:184 - org.springframework.messaging.MessageHandlingException: HTTP request execution failed for URI [https://my-service.com/my-service]; nested exception is org.springframework.web.client.ResourceAccessException: I/O error on POST request for "https://my-service.com/my-service": my-service:443 failed to respond; nested exception is org.apache.http.NoHttpResponseException: my-service.com:443 failed to respond
at org.springframework.integration.http.outbound.HttpRequestExecutingMessageHandler.handleRequestMessage(HttpRequestExecutingMessageHandler.java:409)
at org.springframework.integration.handler.AbstractReplyProducingMessageHandler.handleMessageInternal(AbstractReplyProducingMessageHandler.java:109)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Unknown Source)
   Caused by: org.springframework.web.client.ResourceAccessException: I/O error on POST request for "https://my-service.com/my-service": my-service.com:443 failed to respond; nested exception is org.apache.http.NoHttpResponseException: my-service.com:443 failed to respond
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:633)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:595)
at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:516)
at org.springframework.integration.http.outbound.HttpRequestExecutingMessageHandler.handleRequestMessage(HttpRequestExecutingMessageHandler.java:382)
... 213 more
 Caused by: org.apache.http.NoHttpResponseException: my-service.com:443 failed to respond
at org.apache.http.impl.conn.DefaultHttpResponseParser.parseHead(DefaultHttpResponseParser.java:143)
at org.apache.http.impl.conn.DefaultHttpResponseParser.parseHead(DefaultHttpResponseParser.java:57)
at org.apache.http.impl.io.AbstractMessageParser.parse(AbstractMessageParser.java:261)
at org.apache.http.impl.DefaultBHttpClientConnection.receiveResponseHeader(DefaultBHttpClientConnection.java:165)
at org.apache.http.impl.conn.CPoolProxy.receiveResponseHeader(CPoolProxy.java:167)
at org.apache.http.protocol.HttpRequestExecutor.doReceiveResponse(HttpRequestExecutor.java:272)
at org.apache.http.protocol.HttpRequestExecutor.execute(HttpRequestExecutor.java:124)
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:55)
at org.springframework.http.client.HttpComponentsClientHttpRequest.executeInternal(HttpComponentsClientHttpRequest.java:91)
at org.springframework.http.client.AbstractBufferingClientHttpRequest.executeInternal(AbstractBufferingClientHttpRequest.java:48)
at org.springframework.http.client.AbstractClientHttpRequest.execute(AbstractClientHttpRequest.java:53)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:619)
... 216 more

四处搜索,我发现这个 link 陈旧的连接是问题所在,所以我想按照文档中的说明驱逐它们

One of the major shortcomings of the classic blocking I/O model is that the network socket can react to I/O events only when blocked in an I/O operation. When a connection is released back to the manager, it can be kept alive however it is unable to monitor the status of the socket and react to any I/O events. If the connection gets closed on the server side, the client side connection is unable to detect the change in the connection state (and react appropriately by closing the socket on its end).

HttpClient tries to mitigate the problem by testing whether the connection is 'stale', that is no longer valid because it was closed on the server side, prior to using the connection for executing an HTTP request. The stale connection check is not 100% reliable. The only feasible solution that does not involve a one thread per socket model for idle connections is a dedicated monitor thread used to evict connections that are considered expired due to a long period of inactivity. The monitor thread can periodically call ClientConnectionManager#closeExpiredConnections() method to close all expired connections and evict closed connections from the pool. It can also optionally call ClientConnectionManager#closeIdleConnections() method to close all connections that have been idle over a given period of time.

<int-http:outbound-gateway id="httpOutboundGateway" request-channel="requestServiceHttpChannel"
    reply-channel="responseServiceHttpChannel" url-expression="headers.serviceUrl" http-method="POST"
    expected-response-type="java.lang.String" charset="UTF-8" request-factory="httpOutboundRequestFactoryBean"
    message-converters="messageConverterList" header-mapper="headerMapperBean"/>

<bean id="httpOutboundRequestFactoryBean"
      class="org.springframework.http.client.HttpComponentsClientHttpRequestFactory">
        <property name="readTimeout" value="${http.service.timeout.readResponse}"/>
        <property name="connectTimeout" value="${http.service.timeout.connection}"/>
    </bean>

不幸的是,Spring 抽象仅提供修改超时,但我不能像 http client docs 所说的那样更改池配置或访问池管理器来执行此操作。

一个选项是扩展 HttpComponentsClientHttpRequestFactory 并添加方法以从 cron 作业访问 HttpClientConnectionManager 以清除陈旧的连接,但您可能知道更好的选项

是否有 spring 集成抽象我可以使用来自 apache 的 HTTPClient 清除 http-outboung-gateway 上的陈旧连接?

Spring 集成目前没有,但如果您想贡献一些东西,我们很乐意添加它。

虽然我不会使用每 5 秒唤醒一次的专用线程,但我会使用 Spring Integration 的内置任务调度程序,具有可配置的时间表和过期空闲时间。

不过看起来你不需要扩展工厂;您可以使用 factory.getHttpClient().getConnectionManager() - 虽然我看到它已被弃用,因此可能需要更新工厂以使用更新的 API.

但是,那是另外一回事了;可能应该针对 Spring 框架本身提出一些问题。