在 HttpClient 请求之间长时间休眠时连接关闭

Connection closed when sleeping long durations in between HttpClient requests

我是第一次使用 Apache HttpComponents,特别是 HttpClient。我有一个相当基本的用例。我每 N 秒轮询一些第三方服务器(在这种情况下,他们的 API 期待 POST)。

CloseableHttpClient httpclient = HttpClients.custom().
    setDefaultCookieStore(cookieStore).build();

然后我在循环中执行一些 POST 请求...使用:

while (!someCondition) {
    HttpPost httpPost = ...
    httpclient.execute(httpPost)
    Thread.sleep(SOME_TIME)
}

我注意到如果我睡了更长的时间,比如 3 分钟,那么我就不会从服务器收到响应,并且连接每次都会断开:

DEBUG [org.apache.http.wire] http-outgoing-1 << "end of stream"
DEBUG [org.apache.http.impl.conn.DefaultManagedHttpClientConnection] http-outgoing-1: Close connection
DEBUG [org.apache.http.impl.conn.DefaultManagedHttpClientConnection] http-outgoing-1: Shutdown connection
DEBUG [org.apache.http.impl.execchain.MainClientExec] Connection discarded
DEBUG [org.apache.http.impl.conn.DefaultManagedHttpClientConnection] http-outgoing-1: Close connection

我不相信这是服务器。我可能没有正确使用 HttpComponents 或者配置错误。如果我将它设置为较短的持续时间,如 1 分钟,它可以正常工作(我确实注意到它在 运行 ~15 分钟后死亡 - 所以这是十五分钟的间隔)。

为了发送请求,我将其包装在一些 Java 8 个 lambda 表达式中并利用资源尝试,但我认为这无关紧要:

private <R> R sendRequest(HttpUriRequest request, Function<String, R> func) {
    try {
        try (CloseableHttpResponse resp = this.httpclient.execute(request)) {
            HttpEntity entity = resp.getEntity();

            String responseString = EntityUtils.toString(entity);

            R result = null;
            if (func != null) {
                result = func.apply(responseString);
            }

            EntityUtils.consume(entity);
            return result;
        }
    } catch (IOException e) {
        e.printStackTrace();
        throw new RuntimeException("Error sending request: " + request, e);
    }
}

实际异常是:

Caused by: org.apache.http.NoHttpResponseException: myserver.com:80 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:107)

对于 HTTP 服务器来说,为了节省资源,关闭空闲时间超过最长不活动时间的持续连接是非常普遍和自然的。在您的特定情况下,如果客户端每 30 秒生成一个请求,服务器将保持连接处于活动状态,而如果连接保持空闲时间更长,它最终会在服务器端关闭(并在客户端变为半关闭或 'stale'边)。下次客户端从池中租用连接并尝试使用它来执行请求时,执行失败并出现 I/O 异常。这是完全正常的,可以预料。 HttpClient 尝试通过执行所谓的过时连接检查来缓解此问题,以查明连接是否已被对端端点关闭或是否已失效。 'stale' 检查相对昂贵,应谨慎使用。从 4.4 版开始,HttpClient 试图通过有选择地执行检查来提高开箱即用的性能。 4.4 版中存在一个缺陷,它基本上完全禁用了连接验证。请升级到版本 4.4.1,问题应该会消失。

说了这么多我仍然认为在你的情况下最好的行动方案是通过使用 HttpClientConnectionManager#closeIdleConnections

在长时间不活动之前从连接池中驱逐所有持久连接

我遇到了类似的问题(NoHttpResponseException:myserver:port 无法响应),所有 GET 请求都在工作,所有 POST 和 PUT 请求都失败了。这是由于 HTTP 客户端默认压缩请求主体,而服务器不期望它被压缩并将请求视为空主体。

这些是服务器端的日志:

[2017-03-01 11:32:51,074][WARN ][http.netty][host-elasticsearch] Caught exception while handling client http traffic, closing connection [details]
TransportException[Support for compressed content is disabled. You can enable it with http.compression=true]

[2017-03-01 11:32:51,074][WARN ][http.netty] [host-elasticsearch] Caught exception while handling client http traffic, closing connection [details]
java.lang.IllegalStateException: received HttpChunk without HttpMessage

我正在使用 jerseyClient (Dropwizard) 并解决了这个问题,只需在 yaml 配置中设置:

jersey-client:
  gzipEnabled: false

希望这对某人有所帮助。