一,前言
調(diào)用HTTP接口時(shí)經(jīng)常會(huì)使用Apache HttpClient進(jìn)行Http請(qǐng)求發(fā)送,平常只是簡單的使用,今天對(duì)具體的使用進(jìn)行了一個(gè)總結(jié),方便以后查詢,因?yàn)锳pache HttpClient每次版本升級(jí)API變動(dòng)都很大,這里使用的是以較新的版本:4.5.3
二,使用方法
(1)創(chuàng)建CloseableHttpClient
首先需要?jiǎng)?chuàng)建一個(gè)CloseableHttpClient,然后通過CloseableHttpClient進(jìn)行g(shù)et、post、put等請(qǐng)求方式的調(diào)用
//官網(wǎng)的示例
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpGet = new HttpGet("http://targethost/homepage");
CloseableHttpResponse response = httpclient.execute(httpGet);
try {
System.out.println(response.getStatusLine());
HttpEntity entity = response.getEntity();
// do something useful with the response body
// and ensure it is fully consumed
EntityUtils.consume(entity);
} finally {
response.close();
}
使用方法相對(duì)簡單,但是默認(rèn)情況下各種超時(shí)設(shè)置都為-1,即無限制,這樣會(huì)導(dǎo)致各種超時(shí)阻塞問題的發(fā)生,因此在線上的使用過程中應(yīng)該對(duì)一些常見的超時(shí)設(shè)置進(jìn)行自定義。
(2)超時(shí)設(shè)置
Appache HttpClient提供了一個(gè)配置類,用于請(qǐng)求的超時(shí)等其他配置
//HttpClient請(qǐng)求超時(shí)配置
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(CONNECT_TIMEOUT)
.setSocketTimeout(SOCKET_TIMEOUT)
.setConnectionRequestTimeout(CONNECTION_REQUEST_TIMEOUT)
.build();
源碼中的3個(gè)超時(shí)設(shè)置:
connectionRequestTimeout :從連接池中獲取可用連接的時(shí)間
connectTimeout :連接超時(shí)時(shí)間,客戶端請(qǐng)求服務(wù)器與服務(wù)器建立連接(三次握手)成功的最大接受時(shí)間
socketTimeout :請(qǐng)求獲取數(shù)據(jù)的超時(shí)時(shí)間,訪問一個(gè)接口指定時(shí)間內(nèi)無法返回?cái)?shù)據(jù),直接放棄此次調(diào)用
Builder() {
super();
this.staleConnectionCheckEnabled = false;
this.redirectsEnabled = true;
this.maxRedirects = 50;
this.relativeRedirectsAllowed = true;
this.authenticationEnabled = true;
this.connectionRequestTimeout = -1;
this.connectTimeout = -1;
this.socketTimeout = -1;
this.contentCompressionEnabled = true;
}
可以看到源碼中默認(rèn)賦值為-1,這里可以通過上述RequestConfig 進(jìn)行配置。
(3)連接池配置
建立一次連接是需要耗費(fèi)一定時(shí)間與資源的,常見的方式是通過連接池來提高效率,Appache HttpClient也提供了相關(guān)的連接池配置:
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
connectionManager.setMaxTotal(POOL_MAX_TOTAL);
connectionManager.setDefaultMaxPerRoute(POOL_MAX_PRE_ROUTE);
setMaxTotal()方法用來設(shè)置連接池的最大連接數(shù),即整個(gè)池子的大??;
setDefaultMaxPerRoute()方法來設(shè)置每一個(gè)路由的最大連接數(shù),這里的路由是指IP+PORT,例如連接池大小(MaxTotal)設(shè)置為300,路由連接數(shù)設(shè)置為200(DefaultMaxPerRoute),對(duì)于www.a.com與www.b.com兩個(gè)路由來說,發(fā)起服務(wù)的主機(jī)連接到每個(gè)路由的最大連接數(shù)(并發(fā)數(shù))不能超過200,兩個(gè)路由的總連接數(shù)不能超過300。
因此,最終我們可以這樣來獲取CloseableHttpClient:
httpClient = HttpClients.custom()
.setDefaultRequestConfig(requestConfig)
.setConnectionManager(connectionManager)
.build();
(4)請(qǐng)求參數(shù)設(shè)置
實(shí)際情況中我們請(qǐng)求接口服務(wù)時(shí)參數(shù)比較多,可以通過HttpClient提供的方法來方便的構(gòu)建請(qǐng)求參數(shù):
//get參數(shù)拼接,url為請(qǐng)求接口地址
URIBuilder builder = new URIBuilder(url);
//通過setParameter來設(shè)置參數(shù)key-value
builder.setParameter("params-1", "value-1");
builder.setParameter("params-2", "value-2");
HttpGet httpGet = new HttpGet(builder.build());
//post方式參數(shù)構(gòu)造
HttpPost httpPost = new HttpPost(url);
List<NameValuePair> values = new ArrayList<NameValuePair>();
values.add(new BasicNameValuePair("params-1", "value-1"));
values.add(new BasicNameValuePair("params-2", "value-2"));
httpPost.setEntity(new UrlEncodedFormEntity(values, "UTF-8"));
(5)返回結(jié)果處理
//這里以post為例
CloseableHttpResponse response = httpclient.execute(httpPost)
//消費(fèi)服務(wù)器響應(yīng)內(nèi)容
EntityUtils.toString(response.getEntity(), "UTF-8");
(6)連接釋放與關(guān)閉
//連接池關(guān)閉
CloseableHttpClient .close();
//下面兩個(gè)方法均可以釋放連接到連接池
EntityUtils.toString(response.getEntity(), "UTF-8");
EntityUtils.consume(response.getEntity());
這里需要注意的時(shí),在使用連接池的情況下要慎用CloseableHttpClient .close(),因?yàn)樵摲椒〞?huì)關(guān)閉整個(gè)連接池,源碼如下:
List<Closeable> closeablesCopy = closeables != null ? new ArrayList<Closeable>(closeables) : null;
if (!this.connManagerShared) {
if (closeablesCopy == null) {
closeablesCopy = new ArrayList<Closeable>(1);
}
//連接池管理類
final HttpClientConnectionManager cm = connManagerCopy;
if (evictExpiredConnections || evictIdleConnections) {
final IdleConnectionEvictor connectionEvictor = new IdleConnectionEvictor(cm,
maxIdleTime > 0 ? maxIdleTime : 10, maxIdleTimeUnit != null ? maxIdleTimeUnit : TimeUnit.SECONDS);
closeablesCopy.add(new Closeable() {
@Override
public void close() throws IOException {
connectionEvictor.shutdown();
}
});
connectionEvictor.start();
}
closeablesCopy.add(new Closeable() {
@Override
public void close() throws IOException {
//關(guān)閉連接池
cm.shutdown();
}
});
}
//最終使用的是InternalHttpClient執(zhí)行execute()方法
return new InternalHttpClient(
execChain,
connManagerCopy,
routePlannerCopy,
cookieSpecRegistryCopy,
authSchemeRegistryCopy,
defaultCookieStore,
defaultCredentialsProvider,
defaultRequestConfig != null ? defaultRequestConfig : RequestConfig.DEFAULT,
closeablesCopy);