1
votes

I'm using spring integration with an http-outbound-gateway using the HttpComponentsClientHttpRequestFactory. I'm having the following exception 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

Searching around I found this link that stale connections where the problem, so I wanted to evict the them as it's said in the docs

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>

Unfortunatelly, Spring abstractions offers only to modify timeouts, but I cannot change the pool configuration or access the pool manager to do it as the http client docs says.

One option is to extend the HttpComponentsClientHttpRequestFactory and add methods to access the HttpClientConnectionManager from a cron job to clean the stale connections, but maybe there is a better option you know

Is there a spring integration abstraction I can use to clean stale connections on http-outboung-gateway using the HTTPClient from apache?

1

1 Answers

2
votes

Spring Integration doesn't currently have it, but we'd be happy to add it if you want to contribute something.

I wouldn't use a dedicated thread that wakes up every 5 seconds though, I'd use Spring Integration's built-in task scheduler, with a configurable schedule and expire idle time.

It doesn't look like you need to extend the factory, though; you can use factory.getHttpClient().getConnectionManager() - although I see that is deprecated, so the factory probably needs to be updated to use the newer API.

But, that's a different story; probably something that should be raised against Spring Framework itself.