httpclient4.4簡單初始化httpclient的方式:
HttpClient httpClient = HttpClientBuilder.create().build();
ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
restTemplate = new RestTemplate(requestFactory);
上面是沒有使用池子進行初始化httpclient,在自己做demo的時候使用這種方式完全沒有問題的,但是這種方式建立連接比較的浪費時間和資源,尤其生產環境訪問量大,併發數多的情況,如果沒有管理這些連接的工具,總是存在不安全的隱患。像一些比較耗資源的中間件連接都是有配置連接池的,例如redis,數據庫,線程池等等,都是有相關池子的配置的,功能幾乎一樣的——爲了避免頻繁建立連接造成的時間、資源等浪費。
本博客提供了一個使用池子構建httpclient的工具類,該工具類不光對連接池進行了設置,還有其他相關配置,下面這塊是核心代碼:
public static CloseableHttpClient getHttpClinet() throws Exception {
CloseableHttpClient httpClient = HttpClients.custom()
//設置ssl工廠
.setSSLSocketFactory(sslConnectionSocketFactory)
//設置連接管理方式-連接池
.setConnectionManager(poolingHttpClientConnectionManager)
//設置http請求規則
.setDefaultRequestConfig(getDefaultRequestConfig())
//設置keep-Alive
.setKeepAliveStrategy(getKeepAliveStrategy())
.build();
return httpClient;
}
合理的初始化一個httpClient需要設置4個配置:
- 設置ssl工廠,訪問https時是必備的,訪問http時是不需要的
- 設置連接管理方式-連接池(連接方式有很多種:SimpleHttpConnectionManager、MultiThreadedHttpConnectionManager、poolingHttpClientConnectionManager),本工具類使用poolingHttpClientConnectionManager
- 設置keep-Alive超時時間,在高頻的情況下這個是有必要的,因爲httpclient是要放回池子裏的,如果調用的服務端沒有設置或者設置過長的keep-alive時間,會把這個未關閉長連接的httpclient返回池子,如果再次使用會報The server ***** failed to respond.的錯誤。
工具類:
裏面的註釋很詳細,也做了相應的優化 ,可以直接使用
package com.sgcc.base.https;
import java.io.IOException;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.apache.commons.collections.MapUtils;
import org.apache.http.Consts;
import org.apache.http.HeaderElement;
import org.apache.http.HeaderElementIterator;
import org.apache.http.HeaderIterator;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
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.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.ConnectionKeepAliveStrategy;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContextBuilder;
import org.apache.http.conn.ssl.TrustStrategy;
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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.sgcc.Exception.BusinessException;
/**
* @Title: HttpsPoolUtil.java
* @Description: TODO(用一句話描述該文件做什麼)
* @date 2019年12月24日 下午6:09:11
* @version V1.0
*/
public class HttpsPoolUtil {
private static Logger log = LoggerFactory.getLogger(HttpsPoolUtil.class);
private static final String _HTTP = "http";
private static final String _HTTPS = "https";
//配置連接池獲取超時時間
private static final int CONNECTION_REQUEST_TIMEOUT= 1 * 1000;
//配置客戶端連接服務器超時時間
private static final int CONNECT_TIMEOUT = 3 * 1000;
//配置服務器響應超時時間
private static final int SOCKET_TIMEOUT = 20 * 1000;
//默認返回null串
private static String EMPTY_STR = "";
private static SSLConnectionSocketFactory sslConnectionSocketFactory = null;
//連接池管理類
private static PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = null;
//管理Https連接的上下文類
private static SSLContextBuilder sslContextBuilder = null;
static {
try {
sslContextBuilder = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
@Override
public boolean isTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
//忽略http校驗
return true;
}
});
sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContextBuilder.build(), new String[] { "SSLv3", "TLSv1", "TLSv1.2" }, null, null);
//註冊兩種請求形式
Registry<ConnectionSocketFactory> registryBuilder = RegistryBuilder.<ConnectionSocketFactory>create()
.register(_HTTP, new PlainConnectionSocketFactory())
.register(_HTTPS, sslConnectionSocketFactory).build();
poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager(registryBuilder);
//最大連接數
poolingHttpClientConnectionManager.setMaxTotal(500);
//最大併發數
poolingHttpClientConnectionManager.setDefaultMaxPerRoute(100);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (KeyStoreException e) {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
}
}
/**
* @Description: http初始化連接配置
* @date 2019年12月25日 下午5:09:17
* @param httpRequestBase
*/
private static RequestConfig getDefaultRequestConfig() {
RequestConfig requestConfig = RequestConfig.custom()
/*
* 從連接池中獲取連接的超時時間,假設:連接池中已經使用的連接數等於setMaxTotal,新來的線程在等待1*1000
* 後超時,錯誤內容:org.apache.http.conn.ConnectionPoolTimeoutException: Timeout waiting for connection from pool
*/
.setConnectionRequestTimeout(CONNECTION_REQUEST_TIMEOUT)
/*
* 這定義了通過網絡與服務器建立連接的超時時間。
* Httpclient包中通過一個異步線程去創建與服務器的socket連接,這就是該socket連接的超時時間,
* 此處設置爲2秒。假設:訪問一個IP,192.168.10.100,這個IP不存在或者響應太慢,那麼將會返回
* java.net.SocketTimeoutException: connect timed out
*/
.setConnectTimeout(CONNECT_TIMEOUT)
/*
* 指的是連接上一個url,獲取response的返回等待時間,假設:url程序中存在阻塞、或者response
* 返回的文件內容太大,在指定的時間內沒有讀完,則出現
* java.net.SocketTimeoutException: Read timed out
*/
.setSocketTimeout(SOCKET_TIMEOUT)
.build();
return requestConfig;
}
/**
* @Description: http初始化keep-Alive配置
* @date 2019年12月25日 下午5:09:17
* @param httpRequestBase
*/
public static ConnectionKeepAliveStrategy getKeepAliveStrategy(){
ConnectionKeepAliveStrategy myStrategy = new ConnectionKeepAliveStrategy() {
@Override
public long getKeepAliveDuration(HttpResponse response, HttpContext context) {
HeaderElementIterator it = new BasicHeaderElementIterator
(response.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 20 * 1000;//如果沒有約定,則默認定義時長爲20s
}
};
return myStrategy;
}
/**
* @Description: 從池子中獲取獲取httpclient連接
* @date 2019年12月25日 下午5:12:43
* @return
* @throws Exception
*/
public static CloseableHttpClient getHttpClinet() throws Exception {
CloseableHttpClient httpClient = HttpClients.custom()
//設置ssl工廠
.setSSLSocketFactory(sslConnectionSocketFactory)
//設置連接管理方式-連接池
.setConnectionManager(poolingHttpClientConnectionManager)
//設置http請求規則
.setDefaultRequestConfig(getDefaultRequestConfig())
//設置keep-Alive
.setKeepAliveStrategy(getKeepAliveStrategy())
.build();
return httpClient;
}
/**
* @Description: post請求——JSON格式
* @date 2019年12月25日 下午2:10:34
* @param url
* @param json
* @return
*/
public static String postJSON(String url, String json) {
HttpPost httpPost = new HttpPost(url);
StringEntity entity = new StringEntity(json, Consts.UTF_8);//解決中文亂碼問題
entity.setContentType("application/json;charset=UTF-8");
httpPost.setEntity(entity);
return getResult(httpPost);
}
/**
* @Description: post請求——form格式
* @date 2019年12月25日 下午2:10:34
* @param url
* @param json
* @return
*/
public static String postForm(String url, Map<String,String> params) {
HttpPost httpPost = new HttpPost(url);
//拼裝參數,設置編碼格式
if (MapUtils.isNotEmpty(params)) {
List<NameValuePair> paramList = new ArrayList<>();
for (Map.Entry<String, String> stringStringEntry : params.entrySet()) {
paramList.add(new BasicNameValuePair(stringStringEntry.getKey(), stringStringEntry.getValue()));
}
UrlEncodedFormEntity urlEncodedFormEntity = new UrlEncodedFormEntity(paramList, Consts.UTF_8);
httpPost.setEntity(urlEncodedFormEntity);
}
return getResult(httpPost);
}
/**
* @Description: 通用版處理http請求
* @date 2019年12月25日 下午4:56:35
* @param request
* @return
*/
private static String getResult(HttpRequestBase request) {
/**
* 獲取httpClient
*/
CloseableHttpClient httpClient = null;
try {
//獲取httpClient
httpClient = getHttpClinet();
} catch (Exception e) {
log.error("【新版http】獲取httpClient失敗:請求地址:{},異常信息:", request.getURI(),e);
throw new BusinessException(-1, "獲取httpClient失敗");
}
/**
* 發起http請求,並處理響應結果
*/
String resultStr = null;
CloseableHttpResponse httpResponse = null;
try {
//發起http請求
httpResponse = httpClient.execute(request);
int statusCode = httpResponse.getStatusLine().getStatusCode();
//響應成功
if (statusCode == HttpStatus.SC_OK) {
HttpEntity httpResponseEntity = httpResponse.getEntity();
resultStr = EntityUtils.toString(httpResponseEntity);
log.info("【新版http】請求正常,請求地址:{},響應結果:{}", request.getURI(), resultStr);
return resultStr;
}
//響應失敗,打印http異常信息
StringBuffer stringBuffer = new StringBuffer();
HeaderIterator headerIterator = httpResponse.headerIterator();
while (headerIterator.hasNext()) {
stringBuffer.append("\t" + headerIterator.next());
}
log.info("【新版http】異常信息:請求地址:{},響應狀態:{},請求返回結果:{}", request.getURI(), statusCode, stringBuffer);
} catch (Exception e) {
log.error("【新版http】發生異常:請求地址:{},異常信息:", request.getURI(), e);
throw new BusinessException(-1, "http請求失敗");
} finally {
//關閉httpResponse
if (httpResponse != null) {
try {
httpResponse.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return EMPTY_STR;
}
}