最近使用HttpClient 4.5 使用 CloseableHttpClient 發起連接后,使用CloseableHttpResponse 接受返回結果,結果就報錯了,上網查了下,有位stackoverflow的大兄弟說,只要將:
CloseableHttpClient httpClient = HttpClients.createDefault();
改為:
CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(connManager).setConnectionManagerShared(true).build();
就可以整正常執行了,於是,用之,果然不報錯了,但是為什么呢?以下是大兄弟的原文解釋:
I was having a similar error when I came across this thread and this seemed to fix the issue for me. I know this is an old question, but adding thoughts for others for future reference.
I'm not 100% sure as to why this fix works as the documentation around this is pretty awful. It was a lot of trial and error with what I was testing to get to this solution. From what I can
gather though, this fix works because it is then using a shared connection pool in the background, which means that connections remain open for use.
關鍵是最后一句話:大概意思是,后台使用一個共享連接池,供剩下打開的連接去使用
原文地址:https://stackoverflow.com/questions/41744410/executorservice-performing-rest-requests
感謝下這位大兄弟
apache 官方的建議是,創建連接池,並為每一個接口URL分配一個線程,去執行,還給出了許多高並發訪問的編碼技巧
原文:https://hc.apache.org/httpcomponents-client-ga/tutorial/html/connmgmt.html
那么,使用HttpClient 4.5連接池的正確姿勢是什么呢?
原作者地址:https://my.oschina.net/xlj44400/blog/711341
HttpClient簡介
HttpClient 是 Apache Jakarta Common 下的子項目,可以用來提供高效的、最新的、功能豐富的支持 HTTP 協議的客戶端編程工具包,並且它支持 HTTP 協議最新的版本和建議。HttpClient支持的功能如下:
- 支持Http0.9、Http1.0和Http1.1協議。
- 實現了Http全部的方法(GET,POST,PUT,HEAD 等)。
- 支持HTTPS協議。
- 支持代理服務器。
- 提供安全認證方案。
- 提供連接池以便重用連接。
- 連接管理器支持多線程應用。支持設置最大連接數,同時支持設置每個主機的最大連接數,發現並關閉過期的連接。
- 在http1.0和http1.1中利用KeepAlive保持長連接。
以前是commons-httpclient,后面被Apache HttpComponents取代,目前版本4.5.x,我們現在用的就是4.5版本
HttpClient連接池使用
為什么要用Http連接池:
1、降低延遲:如果不采用連接池,每次連接發起Http請求的時候都會重新建立TCP連接(經歷3次握手),用完就會關閉連接(4次揮手),如果采用連接池則減少了這部分時間損耗
2、支持更大的並發:如果不采用連接池,每次連接都會打開一個端口,在大並發的情況下系統的端口資源很快就會被用完,導致無法建立新的連接
- 默認http協議:
private static final Charset CHAR_SET = Charset.forName("utf-8");
private static PoolingHttpClientConnectionManager cm;
public void init() {
cm = new PoolingHttpClientConnectionManager();
cm.setMaxTotal(50);
cm.setDefaultConnectionConfig(ConnectionConfig.custom()
.setCharset(CHAR_SET).build());
SocketConfig socketConfig = SocketConfig.custom().setSoTimeout(30000)
.setSoReuseAddress(true).build();
cm.setDefaultSocketConfig(socketConfig);
// HttpProtocolParams.setContentCharset(httpParams, "UTF-8");
// HttpClientParams.setCookiePolicy(httpParams, "ignoreCookies");
// HttpConnectionParams.setConnectionTimeout(httpParams, 30000);
// HttpConnectionParams.setSoTimeout(httpParams, 30000);
httpClient = HttpClientBuilder.create().setConnectionManager(cm)
.build();
}
public CloseableHttpClient getHttpClient() {
int timeout=2;
RequestConfig config = RequestConfig.custom()
.setConnectTimeout(timeout * 1000) //設置連接超時時間,單位毫秒
//.setConnectionRequestTimeout(timeout * 1000) //設置從connect Manager獲取Connection 超時時間,單位毫秒
.setSocketTimeout(timeout * 1000).build(); //請求獲取數據的超時時間,單位毫秒
CloseableHttpClient _httpClient = HttpClients.custom()
.setConnectionManager(cm).setDefaultRequestConfig(config)
.build();
if(cm!=null&&cm.getTotalStats()!=null) { //打印連接池的狀態
LOGGER.info("now client pool {}",cm.getTotalStats().toString());
}
return _httpClient;
}
public String post(String url, Map<String, String> params) {
HttpPost post = new HttpPost(url);
String resp = null;
try {
if(params != null){
List<NameValuePair> nvps = new ArrayList<NameValuePair>();
for (Map.Entry<String, String> param : params.entrySet()) {
nvps.add(new BasicNameValuePair(param.getKey(), param.getValue()));
}
post.setEntity(new UrlEncodedFormEntity(nvps, CHAR_SET));
}
try {
HttpResponse response = httpClient.execute(post);
InputStream input = response.getEntity().getContent();
resp = IOUtils.toString(input);
} catch (ClientProtocolException e) {
LOGGER.error(e.getMessage(), e);
} catch (IOException e) {
LOGGER.error(e.getMessage(), e);
} catch (Exception e) {
LOGGER.error(e.getMessage(), e);
}
} finally {
if (post != null)
post.releaseConnection();
}
return resp;
}
- https協議:
public class HttpConnectionManager {
PoolingHttpClientConnectionManager cm = null;
public void init() {
LayeredConnectionSocketFactory sslsf = null;
try {
sslsf = new SSLConnectionSocketFactory(SSLContext.getDefault());
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory> create()
.register("https", sslsf)
.register("http", new PlainConnectionSocketFactory())
.build();
cm =new PoolingHttpClientConnectionManager(socketFactoryRegistry);
cm.setMaxTotal(200);
cm.setDefaultMaxPerRoute(20);
}
public CloseableHttpClient getHttpClient() {
CloseableHttpClient httpClient = HttpClients.custom()
.setConnectionManager(cm)
.build();
/*
//如果不采用連接池就是這種方式獲取連接
CloseableHttpClient httpClient = HttpClients.createDefault();
*/
return httpClient;
}
}
- httpClient使用
catch (Exception e) {
logger.error("ufile send error e:",e);
try {
if (resEntity != null && resEntity.getContent() != null) {
resEntity.getContent().close();
}
} catch (IllegalStateException | IOException e1) {
logger.error("ufile send error e1:",e1);
} finally {
if (getMethod!=null) {
getMethod.releaseConnection();
}
/*if (httpClient!=null) { //連接池使用的時候不能關閉連接,否則下次使用會拋異常 java.lang.IllegalStateException: Connection pool shut down
try {
httpClient.close();
} catch (IOException e2) {
logger.error("ufile httpclient close error e2:",e2);
}
}*/
}
}
-
連接池使用注意事項:
1. 連接池中連接都是在發起請求的時候建立,並且都是長連接 2. HttpResponse input.close();作用就是將用完的連接釋放,下次請求可以復用,這里特別注意的是,如果不使用in.close();而僅僅使用httpClient.close();結果就是連接會被關閉,並且不能被復用,這樣就失去了采用連接池的意義。 3. 連接池釋放連接的時候,並不會直接對TCP連接的狀態有任何改變,只是維護了兩個Set,leased和avaliabled,leased代表被占用的連接集合,avaliabled代表可用的連接的集合,釋放連接的時候僅僅是將連接從leased中remove掉了,並把連接放到avaliabled集合中
打印的狀態:
INFO c.m.p.u.h.HttpClientUtils[72] - now client pool [leased: 0; pending: 0; available: 0; max: 50]
leased :the number of persistent connections tracked by the connection manager currently being used to execute requests.
available :the number idle persistent connections.
pending : the number of connection requests being blocked awaiting a free connection.
max: the maximum number of allowed persistent connections.
HttpClient 4.5超時設置
4.5版本中,這兩個參數的設置都抽象到了RequestConfig中,由相應的Builder構建,具體的例子如下:
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpGet = new HttpGet("http://stackoverflow.com/");
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(5000).setConnectionRequestTimeout(1000)
.setSocketTimeout(5000).build();
httpGet.setConfig(requestConfig);
CloseableHttpResponse response = httpclient.execute(httpGet);
System.out.println("得到的結果:" + response.getStatusLine());//得到請求結果
HttpEntity entity = response.getEntity();//得到請求回來的數據
- setConnectTimeout:設置連接超時時間,單位毫秒。
ConnectTimeoutException - setConnectionRequestTimeout:設置從connect Manager獲取Connection 超時時間,單位毫秒。這個屬性是新加的屬性,因為目前版本是可以共享連接池的。
ConnectionPoolTimeout - setSocketTimeout:請求獲取數據的超時時間,單位毫秒。 如果訪問一個接口,多少時間內無法返回數據,就直接放棄此次調用。
SocketTimeoutException - 上面3個時間4.5版本默認是-1,就是不限,如果不設置就會一直等待
