|
@@ -0,0 +1,342 @@
|
|
|
|
|
+package top.lvzhiqiang.util;
|
|
|
|
|
+
|
|
|
|
|
+import com.alibaba.fastjson.JSONObject;
|
|
|
|
|
+import com.alibaba.fastjson.parser.Feature;
|
|
|
|
|
+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.CloseableHttpResponse;
|
|
|
|
|
+import org.apache.http.client.methods.HttpPost;
|
|
|
|
|
+import org.apache.http.client.methods.HttpRequestBase;
|
|
|
|
|
+import org.apache.http.client.protocol.HttpClientContext;
|
|
|
|
|
+import org.apache.http.config.Registry;
|
|
|
|
|
+import org.apache.http.config.RegistryBuilder;
|
|
|
|
|
+import org.apache.http.conn.ConnectTimeoutException;
|
|
|
|
|
+import org.apache.http.conn.routing.HttpRoute;
|
|
|
|
|
+import org.apache.http.conn.socket.ConnectionSocketFactory;
|
|
|
|
|
+import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
|
|
|
|
|
+import org.apache.http.conn.socket.PlainConnectionSocketFactory;
|
|
|
|
|
+import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
|
|
|
|
|
+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.BasicNameValuePair;
|
|
|
|
|
+import org.apache.http.pool.PoolStats;
|
|
|
|
|
+import org.apache.http.protocol.HttpContext;
|
|
|
|
|
+import org.apache.http.util.EntityUtils;
|
|
|
|
|
+
|
|
|
|
|
+import javax.net.ssl.SSLException;
|
|
|
|
|
+import javax.net.ssl.SSLHandshakeException;
|
|
|
|
|
+import java.io.IOException;
|
|
|
|
|
+import java.io.InterruptedIOException;
|
|
|
|
|
+import java.net.URI;
|
|
|
|
|
+import java.net.UnknownHostException;
|
|
|
|
|
+import java.util.*;
|
|
|
|
|
+import java.util.concurrent.Executors;
|
|
|
|
|
+import java.util.concurrent.ScheduledExecutorService;
|
|
|
|
|
+import java.util.concurrent.TimeUnit;
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * HTTP工具类
|
|
|
|
|
+ *
|
|
|
|
|
+ * @author shiyong
|
|
|
|
|
+ * 2021/7/29 10:45
|
|
|
|
|
+ */
|
|
|
|
|
+@Slf4j
|
|
|
|
|
+public class HttpUtils {
|
|
|
|
|
+ private final int connectTimeout = 10000;
|
|
|
|
|
+
|
|
|
|
|
+ private final int socketTimeout = 60000;
|
|
|
|
|
+
|
|
|
|
|
+ private final String encodingFormat = "UTF-8";
|
|
|
|
|
+
|
|
|
|
|
+ private CloseableHttpClient httpClient = null;
|
|
|
|
|
+ private final static Object syncLock = new Object();// 相当于线程锁,用于线程安全
|
|
|
|
|
+ private PoolingHttpClientConnectionManager cm = null;
|
|
|
|
|
+ private ScheduledExecutorService monitorExecutor;
|
|
|
|
|
+ private boolean isShowUsePoolLog = true;
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 发送POST请求获取JSONObject格式结果
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param url 接口地址
|
|
|
|
|
+ * @param headers 请求头
|
|
|
|
|
+ * @param param 请求参数
|
|
|
|
|
+ * @return com.alibaba.fastjson.JSONObject
|
|
|
|
|
+ * @author shiyong
|
|
|
|
|
+ * 2021/7/29 14:34
|
|
|
|
|
+ */
|
|
|
|
|
+ public JSONObject getJSONObjectByPost(String url, Map<String, String> headers, JSONObject param) throws IOException {
|
|
|
|
|
+ String content = httpPost(url, headers, param);
|
|
|
|
|
+ if (StringUtils.isEmpty(content)) {
|
|
|
|
|
+ return new JSONObject();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return JSONObject.parseObject(content, Feature.OrderedField);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 发送POST请求获取String格式结果
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param url 接口地址
|
|
|
|
|
+ * @param headers 请求头
|
|
|
|
|
+ * @param param 请求参数
|
|
|
|
|
+ * @return java.lang.String
|
|
|
|
|
+ * @author shiyong
|
|
|
|
|
+ * 2021/7/29 14:35
|
|
|
|
|
+ */
|
|
|
|
|
+ public String getStringByPost(String url, Map<String, String> headers, JSONObject param) throws IOException {
|
|
|
|
|
+ // 创建httpClient对象
|
|
|
|
|
+ CloseableHttpClient httpClient = HttpClients.createDefault();
|
|
|
|
|
+
|
|
|
|
|
+ // 创建http对象
|
|
|
|
|
+ HttpPost httpPost = new HttpPost(url);
|
|
|
|
|
+ RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(connectTimeout)
|
|
|
|
|
+ .setSocketTimeout(socketTimeout).build();
|
|
|
|
|
+ httpPost.setConfig(requestConfig);
|
|
|
|
|
+ httpPost.addHeader("Content-Type", "application/json");
|
|
|
|
|
+ packageHeader(headers, httpPost);
|
|
|
|
|
+ httpPost.setEntity(new StringEntity(param.toJSONString(), ContentType.APPLICATION_JSON));
|
|
|
|
|
+
|
|
|
|
|
+ // 创建httpResponse对象
|
|
|
|
|
+ CloseableHttpResponse httpResponse = null;
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 执行请求
|
|
|
|
|
+ httpResponse = httpClient.execute(httpPost);
|
|
|
|
|
+
|
|
|
|
|
+ if (null == httpResponse || null == httpResponse.getStatusLine()
|
|
|
|
|
+ || null == httpResponse.getEntity()) {
|
|
|
|
|
+ return "";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return EntityUtils.toString(httpResponse.getEntity(), encodingFormat);
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ // 释放资源
|
|
|
|
|
+ release(httpResponse, httpClient);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 封装请求头
|
|
|
|
|
+ * @param headers 请求头参数
|
|
|
|
|
+ * @param httpMethod 请求方式
|
|
|
|
|
+ * @author shiyong
|
|
|
|
|
+ * 2019/11/23 19:21
|
|
|
|
|
+ */
|
|
|
|
|
+ private static void packageHeader(Map<String, String> headers, HttpRequestBase httpMethod) {
|
|
|
|
|
+ if (headers != null) {
|
|
|
|
|
+ Set<Map.Entry<String, String>> entrySet = headers.entrySet();
|
|
|
|
|
+
|
|
|
|
|
+ for (Map.Entry<String, String> entry : entrySet) {
|
|
|
|
|
+ // 设置到请求头到HttpRequestBase对象中
|
|
|
|
|
+ httpMethod.setHeader(entry.getKey(), entry.getValue());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 释放资源
|
|
|
|
|
+ * @param httpResponse HTTP响应
|
|
|
|
|
+ * @param httpClient HTTP客户端
|
|
|
|
|
+ * @author shiyong
|
|
|
|
|
+ * 2019/11/23 19:28
|
|
|
|
|
+ */
|
|
|
|
|
+ private void release(CloseableHttpResponse httpResponse, CloseableHttpClient httpClient) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ if (httpResponse != null) {
|
|
|
|
|
+ httpResponse.close();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (httpClient != null) {
|
|
|
|
|
+ httpClient.close();
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (IOException e) {
|
|
|
|
|
+ log.error("释放HTTP请求资源失败!", e);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public String httpPost(String url, Map<String, String> headers, JSONObject param) {
|
|
|
|
|
+ HttpPost httpPost = new HttpPost(url);
|
|
|
|
|
+
|
|
|
|
|
+ return httpMethod(httpPost, url, headers, param);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private String httpMethod(HttpPost httpPost, String url, Map<String, String> headers, JSONObject param) {
|
|
|
|
|
+ CloseableHttpResponse response = null;
|
|
|
|
|
+ HttpEntity entity = null;
|
|
|
|
|
+ try {
|
|
|
|
|
+ final RequestConfig requestConfig = RequestConfig.custom()
|
|
|
|
|
+ .setConnectionRequestTimeout(2000)// 设定从连接池获取可用连接的时间
|
|
|
|
|
+ .setConnectTimeout(3000)// 设定连接服务器超时时间
|
|
|
|
|
+ .setSocketTimeout(90000)// 设定获取数据的超时时间
|
|
|
|
|
+ .build();
|
|
|
|
|
+ httpPost.setConfig(requestConfig);
|
|
|
|
|
+ // httpBase.setHeader("Connection", "close");
|
|
|
|
|
+ httpPost.addHeader("Content-Type", "application/json");
|
|
|
|
|
+ packageHeader(headers, httpPost);
|
|
|
|
|
+ httpPost.setEntity(new StringEntity(param.toJSONString(), ContentType.APPLICATION_JSON));
|
|
|
|
|
+ response = getHttpClient(url).execute(httpPost, HttpClientContext.create());
|
|
|
|
|
+ entity = response.getEntity();
|
|
|
|
|
+ return EntityUtils.toString(entity, "UTF-8");
|
|
|
|
|
+ } catch (final Exception e) {
|
|
|
|
|
+ e.printStackTrace();
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 关闭HttpEntity的流,如果手动关闭了InputStream in = entity.getContent();这个流,也可以不调用这个方法
|
|
|
|
|
+ EntityUtils.consume(entity);
|
|
|
|
|
+ if (response != null) {
|
|
|
|
|
+ response.close();
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (final Exception e) {
|
|
|
|
|
+ e.printStackTrace();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static void setPostParams(final HttpPost httpost, final Map<String, Object> params) {
|
|
|
|
|
+ final List<BasicNameValuePair> nvps = new ArrayList<>();
|
|
|
|
|
+ final Set<String> keySet = params.keySet();
|
|
|
|
|
+ for (final String key : keySet) {
|
|
|
|
|
+ nvps.add(new BasicNameValuePair(key, params.get(key).toString()));
|
|
|
|
|
+ }
|
|
|
|
|
+ try {
|
|
|
|
|
+ httpost.setEntity(new UrlEncodedFormEntity(nvps, "UTF-8"));
|
|
|
|
|
+ } catch (final Exception e) {
|
|
|
|
|
+ e.printStackTrace();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static void setParams(final HttpRequestBase httpbase, final Map<String, String> params) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ if (params != null && params.size() > 0) {
|
|
|
|
|
+ final List<BasicNameValuePair> nvps = new ArrayList<>();
|
|
|
|
|
+ final Set<String> keySet = params.keySet();
|
|
|
|
|
+ for (final String key : keySet) {
|
|
|
|
|
+ nvps.add(new BasicNameValuePair(key, params.get(key)));
|
|
|
|
|
+ }
|
|
|
|
|
+ final String param = EntityUtils
|
|
|
|
|
+ .toString(new UrlEncodedFormEntity(nvps, "UTF-8"));
|
|
|
|
|
+ httpbase.setURI(new URI(httpbase.getURI().toString() + "?" + param));
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (final Exception e) {
|
|
|
|
|
+ e.printStackTrace();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public CloseableHttpClient getHttpClient(final String url) {
|
|
|
|
|
+ String hostname = url.split("/")[2];
|
|
|
|
|
+ int port = 80;
|
|
|
|
|
+ if (hostname.contains(":")) {
|
|
|
|
|
+ final String[] arr = hostname.split(":");
|
|
|
|
|
+ hostname = arr[0];
|
|
|
|
|
+ port = Integer.parseInt(arr[1]);
|
|
|
|
|
+ }
|
|
|
|
|
+ if (httpClient == null) {
|
|
|
|
|
+ log.info("1****第一次创建httpClient");
|
|
|
|
|
+ // 多线程下多个线程同时调用getHttpClient容易导致重复创建httpClient对象的问题,所以加上了同步锁
|
|
|
|
|
+ synchronized (syncLock) {
|
|
|
|
|
+ if (httpClient == null) {
|
|
|
|
|
+ // 开启监控线程,对异常和空闲线程进行关闭
|
|
|
|
|
+ monitorExecutor = Executors.newScheduledThreadPool(1);
|
|
|
|
|
+ monitorExecutor.scheduleAtFixedRate(new TimerTask() {
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public void run() {
|
|
|
|
|
+ // 关闭异常连接
|
|
|
|
|
+ cm.closeExpiredConnections();
|
|
|
|
|
+ // 关闭空闲的连接
|
|
|
|
|
+ cm.closeIdleConnections(60, TimeUnit.SECONDS);
|
|
|
|
|
+ final PoolStats poolStats = cm.getTotalStats();
|
|
|
|
|
+ final int usePoolNum = poolStats.getAvailable() + poolStats.getLeased()
|
|
|
|
|
+ + poolStats.getPending();
|
|
|
|
|
+ if (isShowUsePoolLog) {
|
|
|
|
|
+ log.info("***********》关闭异常+空闲连接! 空闲连接:"
|
|
|
|
|
+ + poolStats.getAvailable() + " 持久连接:" + poolStats.getLeased() + " 最大连接数:"
|
|
|
|
|
+ + poolStats.getMax() + " 阻塞连接数:" + poolStats.getPending());
|
|
|
|
|
+ }
|
|
|
|
|
+ isShowUsePoolLog = usePoolNum != 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ }, 60, 60, TimeUnit.SECONDS);
|
|
|
|
|
+
|
|
|
|
|
+ httpClient = createHttpClient(1000, 100, 100, hostname, port);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ log.info("3****获取已有的httpClient");
|
|
|
|
|
+ }
|
|
|
|
|
+ return httpClient;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private CloseableHttpClient createHttpClient(
|
|
|
|
|
+ final int maxTotal,
|
|
|
|
|
+ final int maxPerRoute,
|
|
|
|
|
+ final int maxRoute,
|
|
|
|
|
+ final String hostname,
|
|
|
|
|
+ final int port) {
|
|
|
|
|
+ final ConnectionSocketFactory plainsf = PlainConnectionSocketFactory.getSocketFactory();
|
|
|
|
|
+ final LayeredConnectionSocketFactory sslsf = SSLConnectionSocketFactory.getSocketFactory();
|
|
|
|
|
+ final Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory> create()
|
|
|
|
|
+ .register("http", plainsf).register("https", sslsf).build();
|
|
|
|
|
+ cm = new PoolingHttpClientConnectionManager(registry);
|
|
|
|
|
+ // 将最大连接数增加
|
|
|
|
|
+ cm.setMaxTotal(maxTotal);
|
|
|
|
|
+ // 将每个路由基础的连接增加
|
|
|
|
|
+ cm.setDefaultMaxPerRoute(maxPerRoute);
|
|
|
|
|
+ final HttpHost httpHost = new HttpHost(hostname, port);
|
|
|
|
|
+ // 将目标主机的最大连接数增加
|
|
|
|
|
+ cm.setMaxPerRoute(new HttpRoute(httpHost), maxRoute);
|
|
|
|
|
+ // 请求重试处理
|
|
|
|
|
+ final HttpRequestRetryHandler httpRequestRetryHandler = new HttpRequestRetryHandler() {
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public boolean retryRequest(
|
|
|
|
|
+ final IOException exception,
|
|
|
|
|
+ final int executionCount,
|
|
|
|
|
+ final HttpContext context) {
|
|
|
|
|
+ if (executionCount >= 2) {// 如果已经重试了2次,就放弃
|
|
|
|
|
+ log.info("*******》重试了2次,就放弃");
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (exception instanceof NoHttpResponseException) {// 如果服务器丢掉了连接,那么就重试
|
|
|
|
|
+ log.info("*******》服务器丢掉连接,重试");
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (exception instanceof SSLHandshakeException) {// 不要重试SSL握手异常
|
|
|
|
|
+ log.info("*******》不要重试SSL握手异常");
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (exception instanceof InterruptedIOException) {// 超时
|
|
|
|
|
+ log.info("*******》 中断");
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (exception instanceof UnknownHostException) {// 目标服务器不可达
|
|
|
|
|
+ log.info("*******》目标服务器不可达");
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (exception instanceof ConnectTimeoutException) {// 连接被拒绝
|
|
|
|
|
+ log.info("*******》连接超时被拒绝");
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (exception instanceof SSLException) {// SSL握手异常
|
|
|
|
|
+ log.info("*******》SSL握手异常");
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ final HttpClientContext clientContext = HttpClientContext.adapt(context);
|
|
|
|
|
+ final HttpRequest request = clientContext.getRequest();
|
|
|
|
|
+ // 如果请求是幂等的,就再次尝试
|
|
|
|
|
+ return !(request instanceof HttpEntityEnclosingRequest);
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ final CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(cm)
|
|
|
|
|
+ .setRetryHandler(httpRequestRetryHandler).build();
|
|
|
|
|
+
|
|
|
|
|
+ return httpClient;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|