有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

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

我第一次使用ApacheHttpComponents,特别是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分钟后死亡,所以这是15个1分钟的间隔)

为了发送请求,我将其包装在一些Java 8 lambda中,并利用try with resources,但我认为这并不重要:

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)

共 (2) 个答案

  1. # 1 楼答案

    我有一个类似的问题(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
    

    希望这对别人有帮助

  2. # 2 楼答案

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

    尽管如此,我仍然认为在您的情况下,最好的做法是在长时间不活动之前,使用HttpClientConnectionManager#closeIdleConnections从连接池中删除所有持久连接