【问题标题】:How to get persistent HttpConnection with Apache HttpClient?如何使用 Apache HttpClient 获得持久的 HttpConnection?
【发布时间】:2018-02-13 15:17:30
【问题描述】:

在我的测试应用程序中,我使用 Apache HttpClient 对同一主机执行 连续 HttpGet 请求,但在每次下一个请求时,结果表明之前的 HttpConnection 已关闭,而新的创建了 HttpConnection。

我使用相同的 HttpClient 实例并且不关闭响应。从每个实体中我得到 InputStream,用 Scanner 读取它,然后关闭 Scanner。我测试了 KeepAliveStrategy,它返回 true。请求之间的时间不超过 keepAlive 或 connectionTimeToLive 持续时间。

谁能告诉我这种行为的原因是什么?

更新

我找到了解决方案。为了使 HttpConnecton 保持活动状态,有必要在构建 HttpClient 时设置 HttpClientConnectionManager。我用过 BasicHttpClientConnectionManager

ConnectionKeepAliveStrategy keepAliveStrat = new DefaultConnectionKeepAliveStrategy() {
   @Override
   public long getKeepAliveDuration(HttpResponse response, HttpContext context)
   {
      long keepAlive = super.getKeepAliveDuration(response, context);
      if (keepAlive == -1)
         keepAlive = 120000;
      return keepAlive;
   }
};
HttpClientConnectionManager connectionManager = new BasicHttpClientConnectionManager();
try (CloseableHttpClient httpClient = HttpClients.custom()
            .setConnectionManager(connectionManager) // without this setting connection is not kept alive 
            .setDefaultCookieStore(store)
            .setKeepAliveStrategy(keepAliveStrat)
            .setConnectionTimeToLive(120, TimeUnit.SECONDS)
            .setUserAgent(USER_AGENT)
            .build())
{   
   HttpClientContext context = new HttpClientContext();
   RequestConfig config = RequestConfig.custom()
           .setCookieSpec(CookieSpecs.DEFAULT)
           .setSocketTimeout(10000)
           .setConnectTimeout(10000)
           .build();
   context.setRequestConfig(config);
   HttpGet httpGet = new HttpGet(uri);
   CloseableHttpResponse response = httpClient.execute(httpGet, context);
   HttpConnection conn = context.getConnection();
   HttpEntity entity = response.getEntity();
   try (Scanner in = new Scanner(entity.getContent(), ENC))
   {
      // do something
   }
   System.out.println("open=" + conn.isOpen()); // now open=true 

   HttpGet httpGet2 = new HttpGet(uri2); // on the same host with other path

   // and so on
} 

更新 2

一般来说,使用conn.isOpen() 检查连接不是检查连接状态的正确方法,因为:“在内部,HTTP 连接管理器使用 ManagedHttpClientConnection 的实例作为管理连接状态和控制执行的真实连接的代理I/O 操作的数量。如果托管连接被其使用者释放或显式关闭,则底层连接将与其代理分离并返回给管理器。即使服务使用者仍然持有对代理的引用例如,它不再能够有意或无意地执行任何 I/O 操作或更改实际连接的状态。” (HttpClent Tutorial)

正如@oleg 指出的那样,跟踪连接的正确方法是使用logger

【问题讨论】:

    标签: java apache-httpclient-4.x


    【解决方案1】:

    首先,您需要确保您使用的远程服务器支持保持连接。只需检查远程服务器是否在每个响应中都返回标头Connection: Keep-AliveConnection: Closed。对于Close 案例there is nothing,您可以这样做。您可以使用this online tool 进行此类检查。

    接下来,您需要实现this manual 的第 #2.6 段中定义的ConnectionKeepAliveStrategy。请注意,您可以使用现有的DefaultConnectionKeepAliveStrategy since HttpClient version 4.0,这样您的HttpClient 将构造如下:

    HttpClient client = HttpClients.custom()
        .setKeepAliveStrategy(DefaultConnectionKeepAliveStrategy.INSTANCE)
        .build();
    

    这将确保您的HttpClient 实例在服务器支持的情况下将通过保持活动机制重用相同的连接。

    【讨论】:

    • 感谢您的回答。服务器返回 Connection: keep-alive,我使用 ConnectionKeepAliveStrategy。这就是为什么我不明白连接关闭的原因......
    • 能否请您发布一个构造http客户端然后发送连续gets的方法或一段代码?
    【解决方案2】:

    您的应用程序必须关闭响应对象,以确保正确释放底层连接的资源。在响应关闭时,HttpClient 保持有效连接处于活动状态并将它们返回给连接管理器(连接池)。

    我怀疑您的代码只是泄漏了连接,并且每个请求都以新创建的连接结束,而所有先前的连接都继续堆积在内存中。

    【讨论】:

    • oleg: “您的应用程序必须关闭响应对象,以确保正确释放底层连接的资源。”来自官方HttpClient Tutorial: “关闭内容流和关闭响应的区别在于前者会尝试通过消耗实体内容来保持底层连接处于活动状态,而后者会立即关闭并丢弃连接。” i> 这就是我关闭 InputStream 而不是响应本身的原因。
    • 我知道教程说什么。我没有看到您的代码在任何地方关闭 InputStream。我确实看到 Scanner 正在关闭,但我不确定这是否也会导致 InputStream 关​​闭。关闭响应可确保连接返回到池无论如何,这是最佳做法。
    • 如果您有理由确定您的代码不会泄漏连接,请按照此处hc.apache.org/httpcomponents-client-4.5.x/logging.html 的描述在打开连接管理/请求执行日志记录的情况下运行它,并查看释放后连接是否保持活动状态去游泳池
    • oleg: “我确实看到 Scanner 正在关闭,但我不确定这是否也会导致 InputStream 关​​闭。” 来自Java API Specification“当 Scanner 关闭时,如果源实现 Closeable 接口,它将关闭其输入源。” 我尝试关闭响应,但这无助于保持连接活动。
    • 无论如何,您的代码仍然应该在 try-finally 中关闭 CloseableHttpResponse。请生成会话的上下文/线路日志并将其发布在此处或 users@hc.apache.org。
    【解决方案3】:

    来自HttpClient website的示例:

    // In order to ensure correct deallocation of system resources
    // the user MUST call CloseableHttpResponse#close() from a finally clause.
    // Please note that if response content is not fully consumed the underlying
    // connection cannot be safely re-used and will be shut down and discarded
    // by the connection manager. 
    

    所以正如@oleg 所说,您需要在检查连接状态之前关闭 HttpResponse。

    【讨论】:

    • 我试过了。结果是一样的......此外,HttpClient 教程指出关闭响应将关闭底层连接(参见我上面的评论)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-06-26
    • 1970-01-01
    • 2019-04-09
    • 1970-01-01
    • 2012-08-23
    • 1970-01-01
    相关资源
    最近更新 更多