Apache HttpClient 临时错误: NoHttpResponseException

我有一个 webservice,它接受一个带有 XML 的 POST 方法。它工作良好,然后在某些随机场合,它未能通信到服务器抛出 IOException 与消息 The target server failed to respond。后续调用工作正常。

大多数情况下,当我打了一些电话,然后让我的应用程序空闲大约10-15分钟。之后进行的第一个调用返回这个错误。

我尝试了很多方法。

我将重试处理程序设置为

HttpRequestRetryHandler retryHandler = new HttpRequestRetryHandler() {


public boolean retryRequest(IOException e, int retryCount, HttpContext httpCtx) {
if (retryCount >= 3){
Logger.warn(CALLER, "Maximum tries reached, exception would be thrown to outer block");
return false;
}
if (e instanceof org.apache.http.NoHttpResponseException){
Logger.warn(CALLER, "No response from server on "+retryCount+" call");
return true;
}
return false;
}
};


httpPost.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, retryHandler);

但是这个重试从来没有被调用过。(是的,我使用的是正确的 instanceof 子句)当调试这个类的时候从来没有被调用过。

我甚至尝试设置 HttpProtocolParams.setUseExpectContinue(httpClient.getParams(), false);,但没有用。有人可以建议我现在可以做什么?

很重要 除了搞清楚为什么我会得到这个异常之外,我还有一个重要的问题是为什么重试处理程序不在这里工作?

185856 次浏览

Most likely persistent connections that are kept alive by the connection manager become stale. That is, the target server shuts down the connection on its end without HttpClient being able to react to that event, while the connection is being idle, thus rendering the connection half-closed or 'stale'. Usually this is not a problem. HttpClient employs several techniques to verify connection validity upon its lease from the pool. Even if the stale connection check is disabled and a stale connection is used to transmit a request message the request execution usually fails in the write operation with SocketException and gets automatically retried. However under some circumstances the write operation can terminate without an exception and the subsequent read operation returns -1 (end of stream). In this case HttpClient has no other choice but to assume the request succeeded but the server failed to respond most likely due to an unexpected error on the server side.

The simplest way to remedy the situation is to evict expired connections and connections that have been idle longer than, say, 1 minute from the pool after a period of inactivity. For details please see the 2.5. Connection eviction policy of the HttpClient 4.5 tutorial.

Accepted answer is right but lacks solution. To avoid this error, you can add setHttpRequestRetryHandler (or setRetryHandler for apache components 4.4) for your HTTP client like in this answer.

HttpClient 4.4 suffered from a bug in this area relating to validating possibly stale connections before returning to the requestor. It didn't validate whether a connection was stale, and this then results in an immediate NoHttpResponseException.

This issue was resolved in HttpClient 4.4.1. See this JIRA and the release notes

Nowadays, most HTTP connections are considered persistent unless declared otherwise. However, to save server ressources the connection is rarely kept open forever, the default connection timeout for many servers is rather short, for example 5 seconds for the Apache httpd 2.2 and above.

The org.apache.http.NoHttpResponseException error comes most likely from one persistent connection that was closed by the server.

It's possible to set the maximum time to keep unused connections open in the Apache Http client pool, in milliseconds.

With Spring Boot, one way to achieve this:

public class RestTemplateCustomizers {
static public class MaxConnectionTimeCustomizer implements RestTemplateCustomizer {


@Override
public void customize(RestTemplate restTemplate) {
HttpClient httpClient = HttpClientBuilder
.create()
.setConnectionTimeToLive(1000, TimeUnit.MILLISECONDS)
.build();


restTemplate.setRequestFactory(
new HttpComponentsClientHttpRequestFactory(httpClient));
}
}
}


// In your service that uses a RestTemplate
public MyRestService(RestTemplateBuilder builder ) {
restTemplate = builder
.customizers(new RestTemplateCustomizers.MaxConnectionTimeCustomizer())
.build();
}

Same problem for me on apache http client 4.5.5 adding default header

Connection: close

resolve the problem

This can happen if disableContentCompression() is set on a pooling manager assigned to your HttpClient, and the target server is trying to use gzip compression.

Although accepted answer is right, but IMHO is just a workaround.

To be clear: it's a perfectly normal situation that a persistent connection may become stale. But unfortunately it's very bad when the HTTP client library cannot handle it properly.

Since this faulty behavior in Apache HttpClient was not fixed for many years, I definitely would prefer to switch to a library that can easily recover from a stale connection problem, e.g. OkHttp.

Why?

  1. OkHttp pools http connections by default.
  2. It gracefully recovers from situations when http connection becomes stale and request cannot be retried due to being not idempotent (e.g. POST). I cannot say it about Apache HttpClient (mentioned NoHttpResponseException).
  3. Supports HTTP/2.0 from early drafts and beta versions.

When I switched to OkHttp, my problems with NoHttpResponseException disappeared forever.

I have faced same issue, I resolved by adding "connection: close" as extention,

Step 1: create a new class ConnectionCloseExtension

import com.github.tomakehurst.wiremock.common.FileSource;
import com.github.tomakehurst.wiremock.extension.Parameters;
import com.github.tomakehurst.wiremock.extension.ResponseTransformer;
import com.github.tomakehurst.wiremock.http.HttpHeader;
import com.github.tomakehurst.wiremock.http.HttpHeaders;
import com.github.tomakehurst.wiremock.http.Request;
import com.github.tomakehurst.wiremock.http.Response;


public class ConnectionCloseExtension extends ResponseTransformer {
@Override
public Response transform(Request request, Response response, FileSource files, Parameters parameters) {
return Response.Builder
.like(response)
.headers(HttpHeaders.copyOf(response.getHeaders())
.plus(new HttpHeader("Connection", "Close")))
.build();
}


@Override
public String getName() {
return "ConnectionCloseExtension";
}
}

Step 2: set extension class in wireMockServer like below,

final WireMockServer wireMockServer = new WireMockServer(options()
.extensions(ConnectionCloseExtension.class)
.port(httpPort));

Solution: change the ReuseStrategy to never

Since this problem is very complex and there are so many different factors which can fail I was happy to find this solution in another post: How to solve org.apache.http.NoHttpResponseException

Never reuse connections: configure in org.apache.http.impl.client.AbstractHttpClient:

httpClient.setReuseStrategy(new NoConnectionReuseStrategy());

The same can be configured on a org.apache.http.impl.client.HttpClientBuilder builder:

builder.setConnectionReuseStrategy(new NoConnectionReuseStrategy());

Use PoolingHttpClientConnectionManager instead of BasicHttpClientConnectionManager

BasicHttpClientConnectionManager will make an effort to reuse the connection for subsequent requests with the same route. It will, however, close the existing connection and re-open it for the given route.