一、問題
二、解決思路
package com.yuedu.util;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.*;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.*;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.routing.HttpRoute;
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.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import javax.net.ssl.*;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.io.UnsupportedEncodingException;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
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;
/**
* HttpClient工具類
* @author 鹹魚
* @date 2019-03-18 17:12
*/
@Slf4j
public class HttpClientUtil {
/**
* 逾時時間
*/
private static final int TIMEOUT = 30 * 1000;
/**
* 最大連接配接數
*/
private static final int MAX_TOTAL = 200;
/**
* 每個路由的預設最大連接配接數
*/
private static final int MAX_PER_ROUTE = 40;
/**
* 目标主機的最大連接配接數
*/
private static final int MAX_ROUTE = 100;
/**
* 通路失敗時最大重試次數
*/
private static final int MAX_RETRY_TIME = 5;
private static CloseableHttpClient httpClient = null;
private static final Object SYNC_LOCK = new Object();
private static final String DEFAULT_CHARSET = "UTF-8";
private static void config(HttpRequestBase httpRequestBase) {
//配置請求的逾時時間
RequestConfig requestConfig = RequestConfig.custom()
.setConnectionRequestTimeout(TIMEOUT)
.setConnectTimeout(TIMEOUT)
.setSocketTimeout(TIMEOUT)
.build();
httpRequestBase.setConfig(requestConfig);
}
/**
* 擷取HttpClient對象
*/
private static CloseableHttpClient getHttpClient(String url) throws NoSuchAlgorithmException, KeyManagementException {
String hostName = url.split("/")[2];
int port = 80;
if (hostName.contains(":")) {
String[] attr = hostName.split(":");
hostName = attr[0];
port = Integer.parseInt(attr[1]);
}
if (httpClient == null) {
synchronized (SYNC_LOCK) {
if (httpClient == null) {
httpClient = createHttpClient(MAX_TOTAL, MAX_PER_ROUTE, MAX_ROUTE, hostName, port);
}
}
}
return httpClient;
}
/**
* 建立HttpClient對象
*/
private static CloseableHttpClient createHttpClient(int maxTotal, int maxPerRoute, int maxRoute,
String hostName, int port) throws KeyManagementException, NoSuchAlgorithmException {
PlainConnectionSocketFactory plainsf = PlainConnectionSocketFactory.getSocketFactory();
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(createIgnoreVerifySSL());
Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", plainsf)
.register("https", sslsf)
.build();
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(registry);
//增加最大連接配接數
cm.setMaxTotal(maxTotal);
//增加每個路由的預設最大連接配接
cm.setDefaultMaxPerRoute(maxPerRoute);
//增加目标主機的最大連接配接數
cm.setMaxPerRoute(new HttpRoute(new HttpHost(hostName, port)), maxRoute);
//請求重試
HttpRequestRetryHandler httpRequestRetryHandler = (exception, executionCount, context) -> {
//若重試5次,放棄
if (executionCount >= MAX_RETRY_TIME) {
return false;
}
//若伺服器丢掉了連接配接,那就重試
if (exception instanceof NoHttpResponseException) {
return true;
}
//不重試SSL握手異常
if (exception instanceof SSLHandshakeException) {
return false;
}
//逾時
if (exception instanceof InterruptedIOException) {
return false;
}
//目标伺服器不可達
if (exception instanceof UnknownHostException) {
return false;
}
//SSL握手異常
if (exception instanceof SSLException) {
return false;
}
HttpClientContext clientContext = HttpClientContext.adapt(context);
HttpRequest request = clientContext.getRequest();
//若請求時幂等的,就再次嘗試
return !(request instanceof HttpEntityEnclosingRequest);
};
return HttpClients.custom().setConnectionManager(cm)
.setRetryHandler(httpRequestRetryHandler)
.build();
}
/**
* HttpClient配置SSL繞過https證書(因為我的網站是有https證書的,是以在通路https網站時,會自動讀取我的證書,
* 和目标網站不符,會報錯),是以這裡需要繞過https證書
*/
private static SSLContext createIgnoreVerifySSL() throws NoSuchAlgorithmException, KeyManagementException {
SSLContext sslContext = SSLContext.getInstance("SSLv3");
// 實作一個X509TrustManager接口,用于繞過驗證,不用修改裡面的方法
X509TrustManager trustManager = new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
}
@Override
public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
};
sslContext.init(null, new TrustManager[] {trustManager}, null);
return sslContext;
}
private static void setPostParams(HttpPost httpPost, Map<String, Object> params) {
List<NameValuePair> nameValuePairs = new ArrayList<>();
params.forEach((key, value) -> nameValuePairs.add(new BasicNameValuePair(key, value.toString())));
try {
httpPost.setEntity(new UrlEncodedFormEntity(nameValuePairs, DEFAULT_CHARSET));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
/**
* post請求,預設編碼格式為UTF-8
* @param url 請求位址
* @param params 請求參數
* @return 響應正文
*/
public static String doPost(String url, Map<String, Object> params) {
return doPost(url, params, DEFAULT_CHARSET);
}
/**
* post請求
* @param url 請求位址
* @param params 請求參數
* @param charset 字元編碼
* @return 響應正文
*/
public static String doPost(String url, Map<String, Object> params, String charset) {
HttpPost httpPost = new HttpPost(url);
config(httpPost);
setPostParams(httpPost, params);
return getResponse(url, httpPost, charset);
}
/**
* get請求,預設編碼UTF-8
* @param url 請求位址
* @return 響應正文
*/
public static String doGet(String url) {
return doGet(url, DEFAULT_CHARSET);
}
/**
* get請求
* @param url 請求位址
* @param charset 字元編碼
* @return 響應正文
*/
public static String doGet(String url, String charset) {
HttpGet httpGet = new HttpGet(url);
config(httpGet);
return getResponse(url, httpGet, charset);
}
/**
* 發起請求,擷取響應
* @param url 請求位址
* @param httpRequest 請求對象
* @param charset 字元編碼
* @return 響應正文
*/
private static String getResponse(String url, HttpRequestBase httpRequest, String charset) {
CloseableHttpResponse response = null;
try {
response = getHttpClient(url).execute(httpRequest, HttpClientContext.create());
HttpEntity httpEntity = response.getEntity();
String result = EntityUtils.toString(httpEntity, charset);
EntityUtils.consume(httpEntity);
return result;
} catch (IOException | NoSuchAlgorithmException | KeyManagementException e) {
log.error("網絡通路異常!", e);
} finally {
try {
if (response != null) {
response.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
}