当PoolingHttpClientConnectionManager中的MaxPoolSize太小时,Apache的HttpClient停止工作

时间:2016-10-19 14:23:45

标签: java apache-httpclient-4.x swingworker

我有SwingWorker获取网址列表(可以是几百个)。在doInBackground()方法中,它循环显示这些网址,每次CloseableHttpClient创建,由PoolingHttpClientConnectionManagerHttpGet管理。 然后,client将执行httpGet并将内容(图像)写入文件(如果可能!并非所有URL都有效,有些URL返回404)。

这适用于大约100个请求,直到达到connectionManager的maxTotal(或defaultMaxPerRoute)。然后一切都停止,客户端的执行停止,没有抛出异常。

所以我想,我将maxTotaldefaultMaxPerRoute设置为1000并将其检出。当我尝试下载1500张图片时,它可以工作,但它只是感觉错了!我想重用客户端,而不是在池中有1000个客户端。

套接字或ConnectionTimeouts不起作用,调试不会告诉我发生了什么,并且在HttpClients没有工作的情况下创建新的PoolingHttpClientConnectionManager。关闭客户端和/或结果也不起作用。

我应该如何管理客户端或设置池以确保我的SwingWorker甚至可以下载数千张图片?

我会尝试将SwingWorker代码分解为重要部分: (几乎忘了,它实际上是一个循环的对象列表,每个对象有3个URL)

// this is how I init the connectionManager (outside the swing worker)
this.connectionManager = new PoolingHttpClientConnectionManager();
this.connectionManager.setMaxTotal(100);
this.connectionManager.setDefaultMaxPerRoute(100);

// this is where the images are downloaded in the swing worker
@Override
protected Void doInBackground() throws Exception{
    for(MyUrls myUrls : myUrlsList){
        client = HttpClients.custom().setConnectionManager(connectionManager).build();         
        for(MyImage image : myUrls.getImageList()){
            File outputFile = null;
            HttpEntity entity = null;
            switch(image.getImageSize()){
                case 1:
                    HttpGet httpGet = new HttpGet(image.getUrl()));
                    httpGet.setConfig(RequestConfig.custom().setSocketTimeout(1000).setConnectTimeout(1000).build());  // doesn't change anything
                    response = client.execute(httpGet);
                    if(response.getStatusLine().getStatusCode() >= 200 && response.getStatusLine().getStatusCode() < 300){
                        entity = response.getEntity();
                        if(entity != null){
                            String contentType = entity.getContentType().getValue();
                            String extension = "." + (contentType.contains("/") ? contentType.substring(contentType.indexOf("/") + 1) : "jpg");
                            outputFile = new File(image.getName()+extension);
                        }
                    }else{
                        System.err.println("download of "+image.getName()+extension+" failed: " + response.getStatusLine().getStatusCode());
                    }
                    break;
                case 2:
                   // the other cases are pretty much the same
            }
            if(entity != null && outputFile != null){
                try(InputStream inputStream = entity.getContent(); FileOutputStream outputStream = new FileOutputStream(outputFile)){
                    byte[] buffer = new byte[1024];
                    int bytesRead;
                    while((bytesRead = inputStream.read(buffer)) != -1){
                        outputStream.write(buffer, 0, bytesRead);
                    }
                }
            }
        }
    }
}

1 个答案:

答案 0 :(得分:4)

您可能正在泄漏连接,因为您只有在可以正确转储到文件输出流的OK响应(在200到300范围内)时才能正确处理它们。

长话短说:总是处置实体

处置实体(&#34;内容&#34; resposne)是释放连接对象的唯一方法。 为此,您可以调用EntityUtils.consume(responseEntity))和/或关闭响应对象(调用response.close())和/或关闭或读取实体的流(调用{{ 1}})在每个请求/响应周期的stream.close()子句中。

您的代码实际上消耗了响应的流,但仅限于某些情况(主要是:请求成功时),因此正确使用API​​,但不是其他(例如404响应),从不消费也不释放。

您也可以使用finally API变体,它将为您提供所有这些 - 我建议您使用它。

如果你不总是这样做,HTTPClient认为连接是活动的,它永远不会被释放,也不会被重用。在某些时候,您将最终得到一个空连接池或系统错误(例如打开太多文件)。

请注意,它不仅适用于200 OK响应,而且每次通话都是如此(即使404通常有响应机构,如果您不使用该机身,则连接将不会释放)。

用户指南中的提示

我建议您参考&#34;基础知识&#34;用户指南的一部分:https://hc.apache.org/httpcomponents-client-ga/tutorial/html/fundamentals.html

  

以下是最简单形式的请求执行过程示例:

ResponseHandler

非常重要的部分是最后关闭中的close()。

  

1.1.5。确保释放低水平资源

     

为了确保正确释放系统资源,必须关闭与实体关联的内容流或响应本身

并且:

  

1.1.6。消费实体内容

     

使用实体内容的推荐方法是使用HttpEntity#getContent()或HttpEntity#writeTo(OutputStream)方法。 HttpClient还附带了EntityUtils类,它公开了几种静态方法,以便更容易地从实体中读取内容或信息。可以使用此类中的方法检索字符串/字节数组中的整个内容主体,而不是直接读取java.io.InputStream。但是,强烈建议不要使用EntityUtils,除非响应实体来自受信任的HTTP服务器,并且已知其长度有限。

关于响应处理程序变体:

  

1.1.8。响应处理程序

     

处理响应的最简单和最方便的方法是使用ResponseHandler接口,该接口包含handleResponse(HttpResponse响应)方法。这种方法完全避免了用户担心连接管理。

堆栈溢出提示

您还可以阅读: Why did the author use EntityUtils.consume(httpEntity);?

旁注

拥有多线程连接管理器的关键是,您可以拥有一个(且只有一个)客户端,并为您的整个应用程序共享它。您不需要为每个请求构建一个客户端,并让它们共享一个连接管理器。虽然你可以。
如果您使用缓存,cookie,身份验证,单个客户端可以提供帮助...在为每个请求创建新客户端时,无法使用。

  

1.2.1。 HttpClient线程安全性

     

HttpClient实现应该是线程安全的。建议将此类的同一实例重用于多个请求执行。