1 修改代码解决:
# "TLSv1", "TLSv1.1", "TLSv1.2"
static {
// 初始化线程池
RequestConfig params = RequestConfig.custom().setConnectTimeout(3000).setConnectionRequestTimeout(1000).setSocketTimeout(4000)
.setExpectContinueEnabled(true).build();
SSLContext sslContext = createTLS12();
// 关闭了主机名验证: (原因:不关闭的情况下请求https需要公网域名,不能直接是ip地址的方式请求)
//SSLConnectionSocketFactory sslFactory = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);
//2020年3月23日 22:57 lifeng
SSLConnectionSocketFactory sslFactory = new SSLConnectionSocketFactory(sslContext, new String[]{"TLSv1", "TLSv1.1", "TLSv1.2"}, null, NoopHostnameVerifier.INSTANCE);
// 设置协议http和https对应的处理socket链接工厂的对象
Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", PlainConnectionSocketFactory.INSTANCE)
.register("https", sslFactory)
.build();
PoolingHttpClientConnectionManager pccm = new PoolingHttpClientConnectionManager(registry);
// 连接池最大并发连接数
pccm.setMaxTotal(300);
// 单路由最大并发数
pccm.setDefaultMaxPerRoute(100);
HttpRequestRetryHandler retryHandler = new HttpRequestRetryHandler() {
@Override
public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
// 重试1次,从1开始
if (executionCount > 1) {
return false;
}
if (exception instanceof NoHttpResponseException) {
LOGGER.info("[NoHttpResponseException has retry request:" + context.toString() + "][executionCount:" + executionCount + "]");
return true;
} else if (exception instanceof SocketException) {
LOGGER.info("[SocketException has retry request:" + context.toString() + "][executionCount:" + executionCount + "]");
return true;
}
return false;
}
};
httpclient = HttpClients.custom().setConnectionManager(pccm).setDefaultRequestConfig(params).setRetryHandler(retryHandler)
.build();
}
2 整个类的代码
package cn.richinfo.common.util;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import org.apache.http.HttpEntity;
import org.apache.http.HttpStatus;
import org.apache.http.NoHttpResponseException;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.RequestConfig;
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.methods.HttpUriRequest;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.ByteArrayEntity;
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.protocol.HttpContext;
import org.apache.http.ssl.SSLInitializationException;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.IOException;
import java.net.SocketException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Map;
import java.util.Map.Entry;
/**
* http请求工具类
*/
public class HttpClientUtilSSL {
private final static Logger LOGGER = LoggerFactory.getLogger(HttpClientUtilSSL.class);
private final static String UTF8 = "UTF-8";
public static CloseableHttpClient httpclient = null;
// /**
// * 参考 httpclient的 {@link org.apache.http.ssl.SSLContexts}<br/>
// *
// * Creates default SSL context based on system properties. This method obtains
// * default SSL context by calling {@code SSLContext.getInstance("Default")}.
// * Please note that {@code Default} algorithm is supported as of Java 6.
// * This method will fall back onto {@link #createDefault()} when
// * {@code Default} algorithm is not available.
// *
// * @return default system SSL context
// */
// public static SSLContext createSystemDefault() throws SSLInitializationException {
// SSLContext context = null;
// try {
// context = createDefault();
// } catch (final Exception ex) {
// ex.printStackTrace();
// //return createDefault();
// }
// return context;
// }
/**
* 参考 httpclient的 {@link org.apache.http.ssl.SSLContexts}<br/>
*
* Creates default factory based on the standard JSSE trust material
* ({@code cacerts} file in the security properties directory). System properties
* are not taken into consideration.
*
* @return the default SSL socket factory
*/
public static SSLContext createTLS12() {
try {
SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
// 创建信任管理器,绕过证书认证:(空实现所有方法)
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:
sslContext.init(null, new TrustManager[]{trustManager}, null);
return sslContext;
} catch (NoSuchAlgorithmException ex) {
throw new SSLInitializationException(ex.getMessage(), ex);
} catch (KeyManagementException ex) {
throw new SSLInitializationException(ex.getMessage(), ex);
}
}
static {
// 初始化线程池
RequestConfig params = RequestConfig.custom().setConnectTimeout(3000).setConnectionRequestTimeout(1000).setSocketTimeout(4000)
.setExpectContinueEnabled(true).build();
SSLContext sslContext = createTLS12();
// 关闭了主机名验证: (原因:不关闭的情况下请求https需要公网域名,不能直接是ip地址的方式请求)
// SSLConnectionSocketFactory sslFactory = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);
//2020年3月23日 22:57 lifeng
SSLConnectionSocketFactory sslFactory = new SSLConnectionSocketFactory(sslContext, new String[]{"TLSv1", "TLSv1.1", "TLSv1.2"}, null, NoopHostnameVerifier.INSTANCE);
// 设置协议http和https对应的处理socket链接工厂的对象
Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", PlainConnectionSocketFactory.INSTANCE)
.register("https", sslFactory)
.build();
PoolingHttpClientConnectionManager pccm = new PoolingHttpClientConnectionManager(registry);
// 连接池最大并发连接数
pccm.setMaxTotal(300);
// 单路由最大并发数
pccm.setDefaultMaxPerRoute(100);
HttpRequestRetryHandler retryHandler = new HttpRequestRetryHandler() {
@Override
public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
// 重试1次,从1开始
if (executionCount > 1) {
return false;
}
if (exception instanceof NoHttpResponseException) {
LOGGER.info("[NoHttpResponseException has retry request:" + context.toString() + "][executionCount:" + executionCount + "]");
return true;
} else if (exception instanceof SocketException) {
LOGGER.info("[SocketException has retry request:" + context.toString() + "][executionCount:" + executionCount + "]");
return true;
}
return false;
}
};
httpclient = HttpClients.custom().setConnectionManager(pccm).setDefaultRequestConfig(params).setRetryHandler(retryHandler)
.build();
}
/**
* 发送POST请求
*
* @param urlToRequest 请求地址
* @param parameters Form形式参数键值对
* @param headers 请求头设置的集合键值对
* @param connectionRequestTimeout 请求超时
* @param connectTimeout 连接超时
* @param socketTimeout socket超时
* @return
* @throws RuntimeException 当请求阻塞,或关闭流异常时
* @throws IOException 当请求阻塞,或关闭流异常时
*/
public static String post(String urlToRequest, Map<String, String> parameters, Map<String, String> headers, Integer connectionRequestTimeout,
Integer connectTimeout, Integer socketTimeout) {
Long startTs = System.currentTimeMillis();
JSONObject json = new JSONObject();
if (parameters != null && !parameters.isEmpty()) {
for (Entry<String, String> entry : parameters.entrySet()) {
json.put(entry.getKey(), String.valueOf(entry.getValue()));
}
}
// LOGGER.info("post-req:url:{},param:{}", urlToRequest, JSONUtil.toJsonStr(parameters));
HttpPost post = new HttpPost(urlToRequest);
RequestConfig requestConfig = getRequestConfig(connectionRequestTimeout, connectTimeout, socketTimeout);
post.setConfig(requestConfig);
if (headers != null && !headers.isEmpty()) {
// LOGGER.info("请求头的属性集合 headers: [{}]" + JSONUtil.toJsonStr(headers));
for (Entry<String, String> entity : headers.entrySet()) {
post.setHeader(entity.getKey(), entity.getValue());
}
}
post.setEntity(new StringEntity(json.toString(), "UTF-8"));
CloseableHttpResponse response = null;
try {
response = invoke(post);
} catch (IOException e) {
// 异常后必须释放链接,否则并发下会一直获取不到链接
post.releaseConnection();
LOGGER.error("[HttpClientUtils][invoke][method:" + post.getMethod() + " URI:" + post.getURI() + "] is request exception", e);
}
String result = getResponseString(response);
Long endTs = System.currentTimeMillis();
Long currentMethodCallTime = endTs - startTs;
if (currentMethodCallTime > 5000) {
LOGGER.warn("url:{},call time {} ms", urlToRequest, currentMethodCallTime);
LOGGER.info("所有存活线程=" + Thread.getAllStackTraces().size());
}
LOGGER.info("post-rps:{}", result);
return result;
}
/**
* 构建请求超时等配置
* @param connectionRequestTimeout
* @param connectTimeout
* @param socketTimeout
* @return
*/
private static RequestConfig getRequestConfig(Integer connectionRequestTimeout, Integer connectTimeout, Integer socketTimeout) {
RequestConfig.Builder builder = RequestConfig.custom();
if (connectionRequestTimeout != null) {
builder = builder.setConnectionRequestTimeout(connectionRequestTimeout);
}
if (connectTimeout != null) {
builder = builder.setConnectTimeout(connectTimeout);
}
if (socketTimeout != null) {
builder = builder.setSocketTimeout(socketTimeout);
}
return builder.build();
}
/**
* 发送POST请求
*
* @param urlToRequest 接口服务地址
* @param parameters 发送的字符串报文参数
* @param headers 请求头设置的集合
* @param connectionRequestTimeout 请求超时设定值
* @param connectTimeout 连接超时设定值
* @param socketTimeout socket超时设定值
* @return
* @throws RuntimeException 当请求阻塞,或关闭流异常时
* @throws IOException 当请求阻塞,或关闭流异常时
*/
public static CloseableHttpResponse post(String urlToRequest, String parameters, Map<String, String> headers, Integer connectionRequestTimeout,
Integer connectTimeout, Integer socketTimeout) {
Long startTs = System.currentTimeMillis();
//LOGGER.info("post-request: 请求地址url: {}, 请求参数param: {}", urlToRequest, parameters);
HttpPost post = new HttpPost(urlToRequest);
RequestConfig requestConfig = getRequestConfig(connectionRequestTimeout, connectTimeout, socketTimeout);
post.setConfig(requestConfig);
if (headers != null && !headers.isEmpty()) {
for (Entry<String, String> entity : headers.entrySet()) {
post.setHeader(entity.getKey(), entity.getValue());
}
}
post.setEntity(new StringEntity(parameters, UTF8));
CloseableHttpResponse response = null;
try {
response = invoke(post);
} catch (IOException e) {
// 异常后必须释放链接,否则并发下会一直获取不到链接
post.releaseConnection();
LOGGER.error("[HttpClientUtils][invoke][method:" + post.getMethod() + " URI:" + post.getURI() + "] is request exception", e);
}
Long endTs = System.currentTimeMillis();
Long currentMethodCallTime = endTs - startTs;
if (currentMethodCallTime > 5000) {
LOGGER.warn("请求地址url: {}, 调用耗时call time: {} ms, 所有存活线程: {}", urlToRequest, currentMethodCallTime, Thread.getAllStackTraces().size());
}
return response;
}
/**
* 发送POST请求
*
* @param urlToRequest 接口服务地址
* @param parameters 发送的字符串报文参数
* @param headers 请求头设置的集合
* @param connectionRequestTimeout 请求超时设定值
* @param connectTimeout 连接超时设定值
* @param socketTimeout socket超时设定值
* @return
* @throws RuntimeException 当请求阻塞,或关闭流异常时
* @throws IOException 当请求阻塞,或关闭流异常时
*/
public static int postGSSync(String urlToRequest, String parameters, Map<String, String> headers, Integer connectionRequestTimeout,
Integer connectTimeout, Integer socketTimeout) {
Long startTs = System.currentTimeMillis();
LOGGER.info("post-req:url:[{}],param:[{}]", urlToRequest, JSONUtil.toJsonStr(parameters));
HttpPost post = new HttpPost(urlToRequest);
RequestConfig requestConfig = getRequestConfig(connectionRequestTimeout, connectTimeout, socketTimeout);
post.setConfig(requestConfig);
if (headers != null && !headers.isEmpty()) {
for (Entry<String, String> entity : headers.entrySet()) {
post.setHeader(entity.getKey(), entity.getValue());
}
}
post.setEntity(new StringEntity(parameters, UTF8));
int result = -1;
try {
result = invoke200(post);
} catch (IOException e) {
// 异常后必须释放链接,否则并发下会一直获取不到链接
post.releaseConnection();
LOGGER.error("[HttpClientUtils][invoke][method:" + post.getMethod() + " URI:" + post.getURI() + "] is request exception", e);
}
Long endTs = System.currentTimeMillis();
Long currentMethodCallTime = endTs - startTs;
if (currentMethodCallTime > 5000) {
LOGGER.warn("url:[{}],call time [{}] ms", urlToRequest, currentMethodCallTime);
LOGGER.info("所有存活线程=" + Thread.getAllStackTraces().size());
}
LOGGER.info("post-rps:{}", result);
return result;
}
/**
* 发送POST请求:小红点推送接口只返回状态码204
*
* @param urlToRequest 接口服务地址
* @param parameters 发送的字符串报文参数
* @param headers 请求头设置的集合
* @param connectionRequestTimeout 请求超时设定值
* @param connectTimeout 连接超时设定值
* @param socketTimeout socket超时设定值
* @return
* @throws RuntimeException 当请求阻塞,或关闭流异常时
* @throws IOException 当请求阻塞,或关闭流异常时
*/
public static int postRedPointSync(String urlToRequest, String parameters, Map<String, String> headers, Integer connectionRequestTimeout,
Integer connectTimeout, Integer socketTimeout) {
Long startTs = System.currentTimeMillis();
LOGGER.info("post-req:url:[{}],param:[{}]", urlToRequest, JSONUtil.toJsonStr(parameters));
HttpPost post = new HttpPost(urlToRequest);
RequestConfig requestConfig = getRequestConfig(connectionRequestTimeout, connectTimeout, socketTimeout);
post.setConfig(requestConfig);
if (headers != null && !headers.isEmpty()) {
for (Entry<String, String> entity : headers.entrySet()) {
post.setHeader(entity.getKey(), entity.getValue());
}
}
post.setEntity(new StringEntity(parameters, UTF8));
int result = -1;
try {
result = invoke204(post);
} catch (IOException e) {
// 异常后必须释放链接,否则并发下会一直获取不到链接
post.releaseConnection();
LOGGER.error("[HttpClientUtils][invoke][method:" + post.getMethod() + " URI:" + post.getURI() + "] is request exception", e);
}
Long endTs = System.currentTimeMillis();
Long currentMethodCallTime = endTs - startTs;
if (currentMethodCallTime > 5000) {
LOGGER.warn("url:[{}],call time [{}] ms", urlToRequest, currentMethodCallTime);
LOGGER.info("所有存活线程数:[{}]", Thread.getAllStackTraces().size());
}
LOGGER.info("post-rps:[{}]", result);
return result;
}
/**
* 发送 POST 请求
*
* @param urlToRequest 请求地址
* @param parameters 请求 Byte Array 报文参数
* @param headers 设置请求头集合参数
* @param connectionRequestTimeout 请求超时
* @param connectTimeout 连接超时
* @param socketTimeout socket超时
* @return
* @throws RuntimeException 当请求阻塞,或关闭流异常时
* @throws IOException 当请求阻塞,或关闭流异常时
*/
public static String post(String urlToRequest, byte[] parameters, Map<String, String> headers, Integer connectionRequestTimeout,
Integer connectTimeout, Integer socketTimeout) {
Long startTs = System.currentTimeMillis();
LOGGER.info("post-req:url:[{}], param:[{}]", urlToRequest, JSONUtil.toJsonStr(parameters));
HttpPost post = new HttpPost(urlToRequest);
RequestConfig requestConfig = getRequestConfig(connectionRequestTimeout, connectTimeout, socketTimeout);
post.setConfig(requestConfig);
if (headers != null && !headers.isEmpty()) {
for (Entry<String, String> entity : headers.entrySet()) {
post.setHeader(entity.getKey(), entity.getValue());
}
}
post.setEntity(new ByteArrayEntity(parameters));
CloseableHttpResponse response = null;
try {
response = invoke(post);
} catch (IOException e) {
// 异常后必须释放链接,否则并发下会一直获取不到链接
post.releaseConnection();
LOGGER.error("[HttpClientUtils][invoke][method:" + post.getMethod() + " URI:" + post.getURI() + "] is request exception", e);
}
String result = getResponseString(response);
Long endTs = System.currentTimeMillis();
Long currentMethodCallTime = endTs - startTs;
if (currentMethodCallTime > 5000) {
LOGGER.warn("url:[{}],call time [{}] ms", urlToRequest, currentMethodCallTime);
LOGGER.info("所有存活线程数: [{}]", Thread.getAllStackTraces().size());
}
LOGGER.info("post-rps:[{}]", result);
return result;
}
/**
* 发送 GET 请求
*
* @param urlToRequest 请求地址
* @param headers 请求头设置参数集合
* @return
* @throws RuntimeException 当请求阻塞,或关闭流异常时
* @throws IOException 当请求阻塞,或关闭流异常时
*/
public static String get(String urlToRequest, Map<String, String> headers) {
Long startTs = System.currentTimeMillis();
HttpGet httpGet = new HttpGet(urlToRequest);
if (headers != null && !headers.isEmpty()) {
for (Entry<String, String> entity : headers.entrySet()) {
httpGet.setHeader(entity.getKey(), entity.getValue());
}
}
CloseableHttpResponse response = null;
try {
response = invoke(httpGet);
} catch (IOException e) {
// 异常后必须释放链接,否则并发下会一直获取不到链接
httpGet.releaseConnection();
LOGGER.error("[HttpClientUtils][invoke][method:" + httpGet.getMethod() + " URI:" + httpGet.getURI() + "] is request exception", e);
}
String result = getResponseString(response);
Long endTs = System.currentTimeMillis();
Long currentMethodCallTime = endTs - startTs;
if (currentMethodCallTime > 5000) {
LOGGER.warn("url:[{}],call time [{}] ms", urlToRequest, currentMethodCallTime);
LOGGER.info("所有存活线程数:[{}]" + Thread.getAllStackTraces().size());
}
LOGGER.info("get-rps:[{}]", result);
return result;
}
/**
* 执行请求
*
* @param request
* @return
*/
private static CloseableHttpResponse invoke(HttpUriRequest request) throws IOException {
CloseableHttpResponse response = null;
System.setProperty("javax.net.debug", "ssl,handshake"); //查看SSL握手流程日志
response = httpclient.execute(request);
return response;
}
/**
* 获取响应字符串
* @param response
* @return
* @throws IOException
*/
public static String getResponseString(CloseableHttpResponse response) {
String res = null;
if (response != null && response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
HttpEntity entity = response.getEntity();
try {
if (entity != null) {
res = EntityUtils.toString(entity, UTF8);
}
// 确保响应已经被消费,以释放连接
EntityUtils.consume(response.getEntity());
} catch (IOException e) {
throw new RuntimeException("httpclient获取响应字符串异常", e);
} finally {
if (response != null) {
try {
response.close();
} catch (IOException e) {
LOGGER.error("[HttpClientUtils][method:getResponseString] is closed exception", e);
e.printStackTrace();
}
}
}
}
return res;
}
/**
* 执行请求
*
* @param request
* @return
*/
private static int invoke200(HttpUriRequest request) throws IOException {
CloseableHttpResponse response = null;
int isOk = -1;
try {
response = httpclient.execute(request);
if (response != null && (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK)) {
isOk = HttpStatus.SC_OK;
}
// 确保响应已经被消费,以释放连接
EntityUtils.consume(response.getEntity());
} finally {
if (response != null) {
try {
response.close();
} catch (IOException e) {
LOGGER.error("[HttpClientUtils][invoke][method:" + request.getMethod() + " URI:" + request.getURI() + "] is closed exception", e);
}
}
}
return isOk;
}
/**
* 执行请求
*
* @param request
* @return
*/
private static int invoke204(HttpUriRequest request) throws IOException {
CloseableHttpResponse response = null;
int isOK = -1;
try {
response = httpclient.execute(request);
if (response != null && (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK)) {
isOK = HttpStatus.SC_OK;
} else {
isOK = response.getStatusLine().getStatusCode();
}
// 确保响应已经被消费,以释放连接
EntityUtils.consume(response.getEntity());
} finally {
if (response != null) {
try {
response.close();
} catch (IOException e) {
LOGGER.error("[HttpClientUtils][invoke][method:" + request.getMethod() + " URI:" + request.getURI() + "] is closed exception", e);
}
}
}
return isOK;
}
}