HttpClient:Timeout waiting for connection from pool
项目中访问外网http接口的逻辑,然后就直接用Apache的HttpClient包做了一个工具类进行调用,但项目上线后发现有大bug,报错:response stream exception,org.apache.http.conn.ConnectionPoolTimeoutException: Timeout waiting for connection from pool,错误是Apache包中直接报出的,说明是我调用方法或流程存在问题。
问题代码:
此为我包装的工具类
@Slf4j
public class HttpUtils {
private static CloseableHttpClient httpclient = HttpClients.createDefault();
private static final String CHARSET = "UTF-8";
public static CloseableHttpResponse httpPost(String url, String jsonArg, Map<String,String> header) {
HttpPost httppost = new HttpPost(url);
HttpEntity httpEntity = new StringEntity(jsonArg, CHARSET);
httppost.setEntity(httpEntity);
if (header != null) {
for (Map.Entry<String, String> entry : header.entrySet()) {
httppost.setHeader(entry.getKey(), entry.getValue());
}
}
httppost.setHeader("Content-Type", "application/json");
log.info("request url:{},request headers:{},request body:{}", url,
Lists.newArrayList(httppost.getAllHeaders()).toString(), jsonArg);
CloseableHttpResponse response;
try {
response = httpclient.execute(httppost);
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
return response;
}
} catch (IOException e) {
log.error("response stream exception,{}", e);
}
return null;
}
}
问题重现:
写循环调用本地返回不为200的http接口,如下
public class TestMain {
private static final String URL = "http://127.0.0.1:8080/";
public static void main(String[] args) {
for (int i = 1; i <= 20; i++) {
CloseableHttpResponse response = HttpUtils.httpPost(URL, null, null);
if (response == null) {
System.out.println("第" + i + "次-------------请求失败---------");
} else {
System.out.println("第" + i + "次" + response.getStatusLine().getStatusCode());
}
}
}
}
结果发现错误重现,如下:
从第四次请求开始出现Timeout waiting for connection from pool
用netstat命令查看本机TCP占用,发现均是“已建立连接”的状态
问题分析:
连接池Timeout 是因为没有连接被释放,或者并发不够用
我的项目没有并发,初步确定是没有释放
那么为什么连接池没有释放连接呢?
初步思考:
1. 既然是连接池,肯定会有自动回收连接的机制
2. 肯定是我的代码处理,导致没有回收
3. 代码里需要关闭的有两个对象,一个是CloseableHttpClient,一个是CloseableHttpResponse
查看代码发现只有当response.getStatusLine().getStatusCode() == HttpStatus.SC_OK 也就http返回码为200时才对response进行返回上一层进行处理,否则直接无视response
问题验证:
无论什么情况都对response进行显示关闭,代码如下,增加response的关闭
结果确实没有再出现Timeout waiting for connection from pool错误
问题结论:
1. 因为我只有当返回码为200时才把response返回给上一层进行处理,response处理之后会认为消费用而自动释放,如没有被消费则一直保持连接。只要获取了response的输入流,即认为被消费,如下的工具类的实现就是对输入流的处理。其实最终的原因就是http的输入流没有被处理而资源不释放。
String entity = EntityUtils.toString(response.getEntity(), "UTF-8");
2. 当连接成功,但返回码不是200时,比如404。我的代码是直接无视response,这样的话response没有被消费,而连接会一直为此保存。当连接不成功时,其实就没有response的生成,连接则会自动回收。
3. 在以前的直接用java的http工具类时,因为是直接对输入流处理,所以都会主动去关闭连接关闭流,而不存在此问题