【工具】- HttpClient篇
阿新 • • 發佈:2020-07-21
簡介
- 對於
httpclient
,相信很多人或多或少接觸過,對於httpclient
的使用姿勢,相信很多人會有疑問?下面這邊會通過程式碼說明
package xxx;
import org.apache.commons.codec.Charsets;
import org.apache.http.HeaderElement;
import org.apache.http.HeaderElementIterator;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.conn.ConnectionKeepAliveStrategy;
import org.apache.http.conn.HttpClientConnectionManager;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicHeaderElementIterator;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HTTP;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
public class HttpClientUtil implements AutoCloseable {
private volatile static HttpClientUtil httpClientUtil;
private final CloseableHttpClient httpClient;
private final PoolingHttpClientConnectionManager connMgr;
private final ThreadLocal<HttpClientContext> httpContext = ThreadLocal.withInitial(HttpClientContext::create);
private final IdleConnectionEvictThread evictThread;
public HttpClientUtil() {
// 自定義keep-alive策略,keep-alive使得tcp連線可以被複用。
// 但預設的keep-alive時長為無窮大,不符合現實,所以需要改寫,定義一個更短的時間
// 如果伺服器http響應頭包含 "Keep-Alive:timeout=" ,則使用timeout=後面指定的值作為keep-alive的時長,否則預設60秒
ConnectionKeepAliveStrategy strategy = (httpResponse, httpContext) -> {
HeaderElementIterator it = new BasicHeaderElementIterator(httpResponse.headerIterator(HTTP.CONN_KEEP_ALIVE));
while (it.hasNext()) {
HeaderElement he = it.nextElement();
String param = he.getName();
String value = he.getValue();
if (value != null && param.equalsIgnoreCase("timeout")) {
return Long.parseLong(value) * 1000;
}
}
return 60 * 1000;
};
// 自定義連線池,連線池可以連線可以使連線可以被複用。
// 連線的複用需要滿足:Keep-alive + 連線池。keep-alive使得連線能夠保持存活,不被系統銷燬;連線池使得連線可以被程式重複引用
// 預設的連線池,DefaultMaxPerRoute只有5個,MaxTotal只有10個,不滿足實際的生產需求
connMgr = new PoolingHttpClientConnectionManager();
// 最大連線數500
connMgr.setMaxTotal(500);
// 同一個ip:port的請求,最大連線數200
connMgr.setDefaultMaxPerRoute(200);
// 啟動執行緒池空閒連線、超時連線監控執行緒
evictThread = new IdleConnectionEvictThread(connMgr);
evictThread.start();
// 定義請求超時配置
RequestConfig requestConfig = RequestConfig.custom()
.setConnectionRequestTimeout(5000) // 從連線池裡獲取連線的超時時間
.setConnectTimeout(2000) // 建立TCP的超時時間
.setSocketTimeout(10000) // 讀取資料包的超時時間
.build();
httpClient = HttpClients.custom()
.setConnectionManager(connMgr)
.setKeepAliveStrategy(strategy)
.setDefaultRequestConfig(requestConfig)
.build();
}
/**
* 因為HttpClient是執行緒安全的,可以提供給多個執行緒複用,同時連線池的存證的目的就是為了可以複用連線,所以提供單例模式
*/
private static class HttpClientUtilHolder {
private static final HttpClientUtil INSTANCE = new HttpClientUtil();
}
public static HttpClientUtil getInstance() {
return HttpClientUtilHolder.INSTANCE;
}
public PoolingHttpClientConnectionManager getConnMgr() {
return connMgr;
}
public HttpContext getHttpContext() {
return httpContext.get();
}
public CloseableHttpClient getHttpClient() {
return httpClient;
}
/**
* http get操作
* @param url 請求地址
* @param headers 請求頭
* @return 返回響應內容
*/
public String get(String url, Map<String, String> headers) {
HttpGet httpGet = new HttpGet(url);
if (headers != null) {
for (Map.Entry<String, String> header : headers.entrySet()) {
httpGet.setHeader(header.getKey(), header.getValue());
}
}
CloseableHttpResponse response = null;
try {
response = httpClient.execute(httpGet, httpContext.get());
HttpEntity entity = response.getEntity();
return EntityUtils.toString(entity, Charsets.UTF_8);
} catch (Exception ex) {
ex.printStackTrace();
} finally {
if (response != null) {
try {
//上面的EntityUtils.toString會呼叫inputStream.close(),進而也會觸發連線釋放回連線池,但因為httpClient.execute可能拋異常,所以得在finally顯示調一次,確保連線一定被釋放
response.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
return null;
}
/**
* form表單post
*
* @param url 請求地址
* @param params 引數內容
* @param headers http頭資訊
* @return http文字格式響應內容
*/
public String postWithForm(String url, Map<String, String> params, Map<String, String> headers) {
HttpPost httpPost = new HttpPost(url);
if (headers != null) {
for (Map.Entry<String, String> header : headers.entrySet()) {
httpPost.setHeader(header.getKey(), header.getValue());
}
}
List<NameValuePair> formParams = new ArrayList<>();
if (params != null) {
for (Map.Entry<String, String> param : params.entrySet()) {
formParams.add(new BasicNameValuePair(param.getKey(), param.getValue()));
}
}
UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(formParams, Charsets.UTF_8);
httpPost.setEntity(formEntity);
CloseableHttpResponse response = null;
try {
response = httpClient.execute(httpPost, httpContext.get());
HttpEntity entity = response.getEntity();
return EntityUtils.toString(entity, Charsets.UTF_8);
} catch (Exception ex) {
ex.printStackTrace();
} finally {
if (response != null) {
try {
response.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
return null;
}
/**
* json內容post
*
* @param url 請求地址
* @param data json報文
* @param headers http頭
* @return http文字格式響應內容
*/
public String postWithBody(String url, String data, Map<String, String> headers) {
HttpPost httpPost = new HttpPost(url);
if (headers != null) {
for (Map.Entry<String, String> header : headers.entrySet()) {
httpPost.setHeader(header.getKey(), header.getValue());
}
}
StringEntity stringEntity = new StringEntity(data, ContentType.create("plain/text", Charsets.UTF_8));
httpPost.setEntity(stringEntity);
httpPost.setHeader("Content-type", "application/json");
CloseableHttpResponse response = null;
try {
response = httpClient.execute(httpPost, httpContext.get());
HttpEntity entity = response.getEntity();
return EntityUtils.toString(entity, Charsets.UTF_8);
} catch (Exception ex) {
ex.printStackTrace();
} finally {
if (response != null) {
try {
response.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
return null;
}
/**
* HttpClientUtil類回收時,通過httpClient.close(),可以間接的關閉連線池,從而關閉連線池持有的所有tcp連線
* 與response.close()的區別:response.close()只是把某個請求持有的tcp連線放回連線池,而httpClient().close()是銷燬整個連線池
*
* @throws IOException
*/
@Override
public void close() throws IOException {
httpClient.close();
evictThread.shutdown();
}
/**
* 定義一個連線監控執行緒類,用以從連線池裡關閉過期的連線(即伺服器已經關閉的連線),以及在30秒內處於空閒的連線;每5秒鐘處理一次
*/
static class IdleConnectionEvictThread extends Thread {
private final HttpClientConnectionManager connMgr;
private volatile boolean shutdown;
public IdleConnectionEvictThread(HttpClientConnectionManager connMgr) {
super();
this.connMgr = connMgr;
setDaemon(true);
}
@Override
public void run() {
try {
while (!shutdown) {
synchronized (this) {
wait(5000);
connMgr.closeExpiredConnections();
connMgr.closeIdleConnections(30, TimeUnit.SECONDS);
}
}
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
/**
* 關閉監控執行緒
*/
public void shutdown() {
shutdown = true;
// 監控執行緒可能還處於wait()狀態,通過notifyAll()喚醒,及時退出while迴圈
synchronized (this) {
notifyAll();
}
}
}
}
參考書籍:httpclient-tutorial.pdf