100字范文,内容丰富有趣,生活中的好帮手!
100字范文 > http工具类(支持https 连接池和失败重试)

http工具类(支持https 连接池和失败重试)

时间:2019-03-14 19:37:59

相关推荐

http工具类(支持https 连接池和失败重试)

在实际项目中,经常会遇到调用外部(第三方)的接口,如果调用量较大的话,可能需要考虑连接池、失败重试、SSL证书等问题,以提升性能和稳定性。

以下代码是封装的小组件,供大家参考。

maven依赖

<dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpclient</artifactId><version>4.5.6</version></dependency>

HttpUtil.java代码:

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.HttpRequestRetryHandler;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.config.Registry;import org.apache.http.config.RegistryBuilder;import org.apache.http.conn.HttpClientConnectionManager;import org.apache.http.conn.socket.ConnectionSocketFactory;import org.apache.http.conn.socket.PlainConnectionSocketFactory;import org.apache.http.conn.ssl.DefaultHostnameVerifier;import org.apache.http.conn.ssl.SSLConnectionSocketFactory;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.BasicNameValuePair;import org.apache.http.util.EntityUtils;import org.springframework.util.CollectionUtils;import javax.exceptions.ServiceException;import .ssl.KeyManagerFactory;import .ssl.SSLContext;import java.io.IOException;import java.io.InputStream;import java.security.KeyStore;import java.security.SecureRandom;import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map;/*** http/https工具类。* 支持线程池管理、failRetry特性。*/public class HttpUtil {private static final String DEFAULT_CHARSET = "UTF-8";private static CloseableHttpClient httpClient;public static String get(final String url) throws Exception {return getExecute(url, null, getHttpClient(null), null);}public static String get(final String url, final Map<String, Object> paramMap) throws Exception {return getExecute(url, paramMap, getHttpClient(null), null);}public static String get(final String url, final Map<String, Object> paramMap, final Map<String, String> headers) throws Exception {return getExecute(url, paramMap, getHttpClient(null), headers);}/*** POST请求,支持失败重试。*/public static String post(final String url, final Map<String, Object> paramMap) throws Exception {return postExecute(url, paramMap, new HashMap<>(), getHttpClient(null));}/*** POST请求,支持失败重试。*/public static String postRetry(final String url, final Map<String, Object> paramMap, final Map<String, String> headers) throws Exception {return postExecute(url, paramMap, headers, getHttpClient(null));}/*** POST请求,支持失败重试。*/public static String postRetry(final String url, final String paramStr,final CertInfo certInfo, final Map<String, String> headers) throws Exception {return postExecute(url, paramStr, headers, getHttpClient(certInfo));}/*** POST请求,支持失败重试。*/public static String postRetry(final String url, final Map<String, Object> paramMap,final CertInfo certInfo, final Map<String, String> headers) throws Exception {return postExecute(url, paramMap, headers, getHttpClient(certInfo));}private static synchronized CloseableHttpClient getHttpClient(final CertInfo certInfo) {if (null == certInfo && null != httpClient) {return httpClient;} else {//https请求暂不走httpClient缓存,因为每次请求可能使用不同的证书。RequestConfig config = RequestConfig.custom().setSocketTimeout(Constants.HTTP_READ_TIMEOUT_MS).setConnectTimeout(Constants.HTTP_CONNECTION_TIMEOUT_MS).build();HttpClientConnectionManager connManager = getPoolConnManager(certInfo);HttpRequestRetryHandler retryHandler = RetryHandler.getHandler(Constants.DEFAULT_HTTP_RETRY_MAX_TIMES, Constants.DEFAULT_HTTP_RETRY_INTERVAL_MILLIS);httpClient = HttpClients.custom().setConnectionManager(connManager).setDefaultRequestConfig(config).setRetryHandler(retryHandler).build();//jvm关闭时,释放连接Runtime.getRuntime().addShutdownHook(new Thread(() -> {try {httpClient.close();} catch (IOException e) {//catched}}));return httpClient;}}/*** 获取 连接管理器实例 。* 支持http/https和连接池。*/private static HttpClientConnectionManager getPoolConnManager(final CertInfo certInfo) {Registry<ConnectionSocketFactory> registry;if (null != certInfo) {certInfo.validate();try {// 证书char[] password = certInfo.getPassword().toCharArray();InputStream certStream = certInfo.getCertStream();KeyStore ks = KeyStore.getInstance("PKCS12");ks.load(certStream, password);// 实例化密钥库 & 初始化密钥工厂KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());kmf.init(ks, password);// 创建 SSLContextSSLContext sslContext = SSLContext.getInstance("TLS");sslContext.init(kmf.getKeyManagers(), null, new SecureRandom());SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext,new String[]{"TLSv1"},null,new DefaultHostnameVerifier());registry = RegistryBuilder.<ConnectionSocketFactory>create().register("https", sslConnectionSocketFactory).register("http", PlainConnectionSocketFactory.getSocketFactory()).build();} catch (Exception e) {throw new ServiceException(ErrorCodeEnums.InternalServerError, "创建 SSLContext 失败,证书初始化失败");}} else {registry = RegistryBuilder.<ConnectionSocketFactory>create().register("http", PlainConnectionSocketFactory.getSocketFactory()).register("https", SSLConnectionSocketFactory.getSocketFactory()).build();}PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(registry);//http连接池: 最大连接数,默认20connectionManager.setMaxTotal(Constants.HTTP_POOL_MAX_TOTAL);//http连接池: 每个路由的并发连接,默认2connectionManager.setDefaultMaxPerRoute(Constants.HTTP_POOL_MAX_PER_ROUTE);return connectionManager;}/*** 执行POST请求。*/private static String postExecute(String url, String data, Map<String, String> headers,CloseableHttpClient closeableHttpClient) throws IOException {HttpPost httpPost = new HttpPost(url);if (!CollectionUtils.isEmpty(headers)) {headers.forEach(httpPost::addHeader);}StringEntity postEntity = new StringEntity(data, DEFAULT_CHARSET);httpPost.setEntity(postEntity);HttpResponse httpResponse = closeableHttpClient.execute(httpPost);int httpStatus = httpResponse.getStatusLine().getStatusCode();if (HttpStatus.SC_OK != httpStatus) {System.err.println(url + "接口调用失败,返回状态码:" + httpStatus);}HttpEntity httpEntity = httpResponse.getEntity();return EntityUtils.toString(httpEntity, DEFAULT_CHARSET);}private static String postExecute(String url, Map<String, Object> params, Map<String, String> headers,CloseableHttpClient closeableHttpClient) throws IOException {HttpPost httpPost = new HttpPost(url);if (headers != null && !headers.isEmpty()) {headers.forEach(httpPost::addHeader);}List<NameValuePair> pairList = new ArrayList<>(params.size());for (Map.Entry<String, Object> entry : params.entrySet()) {NameValuePair pair = new BasicNameValuePair(entry.getKey(), entry.getValue().toString());pairList.add(pair);}httpPost.setEntity(new UrlEncodedFormEntity(pairList, DEFAULT_CHARSET));HttpResponse httpResponse = closeableHttpClient.execute(httpPost);int httpStatus = httpResponse.getStatusLine().getStatusCode();if (HttpStatus.SC_OK != httpStatus) {System.err.println(url + "接口调用失败,返回状态码:" + httpStatus);}HttpEntity httpEntity = httpResponse.getEntity();return EntityUtils.toString(httpEntity, DEFAULT_CHARSET);}private static String getExecute(String url, Map<String, Object> params, CloseableHttpClient closeableHttpClient, Map<String, String> headers) throws IOException {CloseableHttpResponse response;if (!CollectionUtils.isEmpty(params)) {StringBuilder realUrl = new StringBuilder().append(url).append("?");for (String key : params.keySet()) {realUrl.append(key).append("=").append(params.get(key)).append("&");}url = realUrl.substring(0, realUrl.length() - 1);}HttpGet httpGet = new HttpGet(url);if (!CollectionUtils.isEmpty(headers)) {headers.forEach(httpGet::addHeader);}response = closeableHttpClient.execute(httpGet);int httpStatus = response.getStatusLine().getStatusCode();if (HttpStatus.SC_OK != httpStatus) {System.err.println(url + "接口调用失败,返回状态码:" + httpStatus);}return EntityUtils.toString(response.getEntity(), DEFAULT_CHARSET);}}

Constants 常量类:

public class Constants {// --------------------- http配置相关 start ------------------/*** HTTP连接超时时间。*/public static final int HTTP_CONNECTION_TIMEOUT_MS = 3000;/*** http read timeout*/public static final int HTTP_READ_TIMEOUT_MS = 25000;/*** http连接池: 最大连接数,默认20**/public static final int HTTP_POOL_MAX_TOTAL = 32;/*** http连接池: 每个路由的并发连接,默认2**/public static final int HTTP_POOL_MAX_PER_ROUTE = 16;/*** http失败重试: 默认最大重试次数。*/public static final int DEFAULT_HTTP_RETRY_MAX_TIMES = 2;/*** http失败重试: 默认每次的间隔时间。默认为1秒。*/public static final Integer DEFAULT_HTTP_RETRY_INTERVAL_MILLIS = 1000;// --------------------- http配置相关 end ------------------}

ErrorCodeEnums.java类:

public enum ErrorCodeEnums {InternalServerError(500, "服务器内部错误"),}

SSL证书类CertInfo.java:

import org.springframework.util.Assert;import java.io.InputStream;/*** 发起HTTPS/SSL使用的证书。** */public class CertInfo {//证书密码(口令)private String password;//证书文件private InputStream certStream;public String getPassword() {return password;}public CertInfo setPassword(String password) {this.password = password;return this;}public InputStream getCertStream() {return certStream;}public CertInfo setCertStream(InputStream certStream) {this.certStream = certStream;return this;}public void validate() {Assert.hasLength(password, "password不能为空");Assert.notNull(certStream, "certStream不能为空");}}

HTTP重试策略类:

import org.apache.http.HttpEntityEnclosingRequest;import org.apache.http.HttpRequest;import org.apache.http.NoHttpResponseException;import org.apache.http.client.HttpRequestRetryHandler;import org.apache.http.client.protocol.HttpClientContext;import org.apache.http.conn.ConnectTimeoutException;import org.apache.logging.log4j.LogManager;import org.apache.logging.log4j.Logger;import .ssl.SSLException;import .ssl.SSLHandshakeException;import java.io.IOException;import java.io.InterruptedIOException;import .UnknownHostException;/*** HTTP重试策略。** @author machunlin* @date /4/26*/public class RetryHandler {private static final Logger LOGGER = LogManager.getLogger(RetryHandler.class);/*** 自定义HTTP重试策略。** @param maxRetryTimes 最大重试次数* @param intervalMillis 重试间隔时间,毫秒* @return*/public static HttpRequestRetryHandler getHandler(final Integer maxRetryTimes, final Integer intervalMillis) {if (maxRetryTimes == null || maxRetryTimes <= 0) {throw new IllegalArgumentException("maxRetryTimes参数不合法:" + maxRetryTimes);}return (exception, executionCount, context) -> {if (executionCount > maxRetryTimes) {LOGGER.error(" 已超过最大重试次数。 当前为第{}次重试,最大重试次数为{}", executionCount, maxRetryTimes, exception);return false;}try {long interval = (null != intervalMillis) ? intervalMillis : Constants.DEFAULT_HTTP_RETRY_INTERVAL_MILLIS;long nextInterval = interval * executionCount;Thread.sleep(nextInterval);} catch (InterruptedException e) {//声明线程状态为"可中断的"。Thread.currentThread().interrupt();}if (exception instanceof UnknownHostException) {LOGGER.error("目标服务器不可达, Unknown host", exception);return false;}if (exception instanceof ConnectTimeoutException) {LOGGER.error("连接被拒绝,ConnectTimeout ", exception);return true;}if (exception instanceof InterruptedIOException) {LOGGER.error("连接超时, Timeout", exception);return true;}if (exception instanceof IOException) {LOGGER.error("网络异常, IOException", exception);return true;}if (exception instanceof SSLHandshakeException) {LOGGER.error("SSL握手异常,SSLHandshakeException ", exception);return false;}if (exception instanceof SSLException) {LOGGER.error("SSLException exception", exception);return false;}if (exception instanceof NoHttpResponseException) {// 如果服务器丢掉了连接,那么就重试return true;}if (exception instanceof Exception) {return true;}HttpClientContext clientContext = HttpClientContext.adapt(context);HttpRequest request = clientContext.getRequest();if (!(request instanceof HttpEntityEnclosingRequest)) {// 如果请求是幂等的,就再次尝试return true;}LOGGER.error("HttpRequestRetryHandler, return false");return false;};}}

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。