HttpClient异常 "org.apache.http.conn.ConnectionPoolTimeoutException:等待连接超时"

58

我正在尝试使用以下代码向我的服务器发送请求,但第三个请求总是失败。

import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.HttpVersion;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.CoreConnectionPNames;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONTokener;

public class HttpClientTest {
    private HttpClient client;

    public HttpClientTest() {
        HttpParams params = new BasicHttpParams();
        params.setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, 15000);  
        params.setParameter(CoreConnectionPNames.SO_TIMEOUT, 15000);

        HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
        HttpProtocolParams.setContentCharset(params, "utf-8");
        HttpProtocolParams.setUseExpectContinue(params, true);
        ThreadSafeClientConnManager cm = new ThreadSafeClientConnManager();
        cm.setMaxTotal(100);
        client = new DefaultHttpClient(cm, params);

        while (true) {
            HttpPost mPost = new HttpPost("http://myip/myservice");

            JSONObject json = new JSONObject();
            try {
                json.put("serialNumber", "abcd");
            } catch (JSONException e1) {
                e1.printStackTrace();
            }
            StringEntity s = null;
            try {
                s = new StringEntity(json.toString());
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }               
            s.setContentEncoding("UTF-8");
            s.setContentType("application/json");
            mPost.setEntity(s);

            JSONObject response = null;

            System.out.println("HttpClientTest ---> send post");
            HttpResponse mHttpResponse;
            try {
                mHttpResponse = client.execute(mPost);
                System.out.println("HttpClientTest  ---> get response");
                if(mHttpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK){
                    HttpEntity entity = mHttpResponse.getEntity(); 
                    ContentType contentType = ContentType.getOrDefault(entity);
                    Charset charset = contentType.getCharset();
                    response = new JSONObject(new JSONTokener(new InputStreamReader(entity.getContent(), charset)));

                    System.out.println("HttpClientTest ---> get result:" + response.toString());
                } else {
                    mPost.abort();
                    break;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        HttpClientTest t = new HttpClientTest();
    }
}

以下是异常情况:

org.apache.http.conn.ConnectionPoolTimeoutException: Timeout waiting for connection
    at org.apache.http.impl.conn.tsccm.ConnPoolByRoute.getEntryBlocking(ConnPoolByRoute.java:417)
    at org.apache.http.impl.conn.tsccm.ConnPoolByRoute$1.getPoolEntry(ConnPoolByRoute.java:300)
    at org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager$1.getConnection(ThreadSafeClientConnManager.java:224)
    at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:401)
    at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:820)
    at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:754)
    at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:732)
    at com.i360r.client.takeaway.network.HttpClientTest.<init>(HttpClientTest.java:68)
    at com.i360r.client.takeaway.network.HttpClientTest.main(HttpClientTest.java:88)
10个回答

85

我曾经遇到了同样的问题,并找到了解决方法。 这个超时是由于连接泄漏导致的。在我的情况下,我正在使用 httpDelete 方法而没有消耗响应。相反,我正在检查响应的状态。

解决方法是,需要消耗响应实体。为确保正确释放系统资源,必须关闭与实体关联的内容流。

所以我使用了 EntityUtils.consumeQuietly(response.getEntity()); 确保实体内容被完全消耗,并且如果存在,则关闭内容流。


1
在使用 Fluent HTTP Client 时遇到了类似的问题,当忽略响应时。仍需调用 execute().discardContent() 以避免连接泄漏。 - rrhartjr
16
非常恼人...如果你不使用,图书馆应该丢弃它...或者至少警告一下... - Nicholas Terry
1
response.close()怎么样? - Karussell
1
如果出现异常,那么响应对象将为空怎么办?在这种情况下,我们如何释放连接? - Jaraws
1
请参考其他答案,使用 CloseableHttpClientCloseableHttpResponse 是首选方法。 - Gaël J

37

我已经解决了这个问题!在 finally 块中添加 mPost.releaseConnection()

 try {
 } catch (Exception e) {
 } finally {
  mPost.releaseConnection();
 }

更新包org.apache.httpcomponents至4.2.1版本。


13

只需将获取响应的代码放到try-with-resources语句块中,并使用CloseableHttpResponse替换HttpResponse,就像这样:

try(final CloseableHttpResponse mHttpResponse = client.execute(mPost);)
{
 System.out.println("HttpClientTest  ---> get response");
 ....remainder code
对象将自动被消耗和关闭。

希望这可以帮助你!


2
客户端变量必须是 CloseableHttpClient 的实例才能正常工作。 - J. Allen

6

使用CloseableHttpClient代替HttpClient。这样你将得到一个CloseableHttpResponse,而不是支持close()HttpResponse。因此,当你完成响应后,只需关闭它,无需关闭连接。

CloseableHttpResponse response = closableHttpClient.execute(httpPost, context);

使用它来完成您需要的操作:

response.close();

虽然这可能是真的,但似乎它更像是对其他一个或多个答案的评论,而不是直接回答问题,因为它并没有直接回答问题。我建议要么将其编辑为回答问题的形式,要么将其作为对不同答案的评论,或者创建自己的自我回答问题,并在此处评论中链接到该问题。 - DaveyDaveDave

4

我们遇到了同样的问题,由于连接池的默认值为DefaultMaxPerRoute,因此我们受到了限制,该值为2。我们的API会使用相同的URI但是不同的body触发不同的调用。一旦我们将其显式设置为更高的值,如下所示:

PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
    cm.setMaxTotal(40);
    cm.setDefaultMaxPerRoute(20);

我们能够解决它。

3

如果你正在使用 DropWizard 0.6.2 并且同时使用 ApacheHttpClient,那么可能会出现这种情况。在幕后,DropWizard 会创建一个带有默认配置的 MultiThreadedHttpConnectionManager,而该默认配置只允许每次最多同时进行 2 个 HTTP 连接 更多信息请参见此处

因此,在这种配置下,如果你的服务器正在被淹没并且不断向同一主机发出请求,则最多只能同时运行 2 个连接!


1

在使用Spark读写Parquet文件时,我们遇到了以下错误:
com.amazonaws.SdkClientException: Unable to execute HTTP request: Timeout waiting for connection from pool

最终我们发现这是与将Hadoop升级到版本3.1.3有关。

在这个新版本的Hadoop中,属性名称已经从fs.s3a.connection.maximum更改为spark.hadoop.fs.s3a.connection.maximum

在我们的Spark配置中重命名该属性后,问题得到解决。


1

我遇到了同样的错误。结果发现需要关闭响应。

CloseableHttpResponse response = null;
try {
       response = ##some code
} catch(Exception e) {
  if (response != null) {
      response.close();
  }
}

我想在这里最好使用try with resource。 - Nitish Bhardwaj

0
在我的情况下,消耗实体内容流的bufferedReader没有被关闭。关闭它解决了错误。

0

正如Rama所说,这可能是连接泄漏的迹象。

releaseConnection()可以帮助解决此问题,但您可能需要找出根本原因。

此外,这可能是因为没有可供租用的连接,这可能发生在HttpClient没有timeout配置的情况下。在这种情况下,releaseConnection()将无法帮助。


如果出现异常,那么响应对象将为空怎么办?如果我没有使用try with resources,那么在这种情况下如何释放连接? - Jaraws

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接