1. 依赖版本

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

2.创建连接池PoolingHttpClientConnectionManager

	private PoolingHttpClientConnectionManager getConnectionManager(HttpClientPoolConfig httpClientPoolConfig) {

		Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
				// 注册http和https请求
				.register("http", PlainConnectionSocketFactory.getSocketFactory())
				.register("https", SSLConnectionSocketFactory.getSocketFactory()).build();

		PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
		// 连接池最大连接数
		connectionManager.setMaxTotal(httpClientPoolConfig.getMaxTotalConnect());
		// 路由链接数,每个主机的并发
		connectionManager.setDefaultMaxPerRoute(httpClientPoolConfig.getMaxConnectPerRoute());

		//定时关闭过期的或空闲的连接,节省服务资源。
		new HttpClientConnectionMonitorThread(connectionManager, httpClientPoolConfig.getKey())
				.start();

		return connectionManager;
	}

3.配置RequestConfig的连接时间

	private RequestConfig getRequestConfig(HttpClientPoolConfig httpClientPoolConfig) {
		return RequestConfig.custom()
				.setConnectionRequestTimeout(httpClientPoolConfig.getConnectionRequestTimout())
				.setConnectTimeout(httpClientPoolConfig.getConnectTimeout())
				.setSocketTimeout(httpClientPoolConfig.getReadTimeout())
				.build();
	}

4.配置重试策略

private HttpRequestRetryHandler httpRequestRetryHandler(int retryCount) {
		return (e, i, httpContext) -> {
			// 超过最大重试次数,放弃请求
			if (i > retryCount) {
				log.info("重试超过{}次,放弃请求。", retryCount);
				return false;
			}

			// 针对特定异常类型判断是否需要重试
			if (e instanceof ConnectTimeoutException) {
				log.warn("连接超时,正在重试...");
				return true;
			}
			if (e instanceof NoHttpResponseException) {
				log.warn("服务器没有响应,正在重试...");
				return true;
			}
			if (e instanceof UnknownHostException) {
				log.warn("服务器不可达,正在重试...");
				return true;
			}
			if (e instanceof SSLException) {
				log.warn("SSL异常,正在重试...");
				return true;
			}
			if (e instanceof SocketTimeoutException) {
				log.warn("读取超时,正在重试...");
				return true;
			}
			if (e instanceof InterruptedIOException) {
				log.warn("请求被中断,不重试。");
				return false;
			}

			// 获取请求上下文,进一步判断请求类型
			HttpClientContext context = HttpClientContext.adapt(httpContext);
			HttpRequest request = context.getRequest();

			if (!(request instanceof HttpEntityEnclosingRequest)) {
				// 如果请求不是带有实体的请求(即幂等请求),可以重试
				log.info("非实体请求,正在重试...");
				return true;
			}

			// 如果是带有请求体的请求,避免重试
			log.info("实体请求,不重试。");
			return false;
		};
	}

注意事项

  1. 避免非池化的 HttpClient:每次创建新实例会增加资源开销,影响性能。

  2. 合理配置超时参数:设置超时值可以避免长时间等待。

  3. 线程安全:PoolingHttpClientConnectionManager 是线程安全的,多个线程可以安全地复用同一实例。

  4. 清理机制:确保空闲和过期连接的清理机制正常工作,避免连接池内存泄漏。

以上配置和实践适用于 4.5.13 版本,可以在高并发环境下显著提升性能和资源利用率。

5.代码示例

5.1 HttpClientPoolConfig配置类


import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

import javax.xml.bind.DatatypeConverter;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Map;

@Data
@Component
@ConfigurationProperties(prefix = "spring.http-client.pool")
public class HttpClientPoolConfig {

	/**
	 * 连接池的最大连接数
	 */
	private int maxTotalConnect = 128;

	/**
	 * 同路由的并发数
	 */
	private int maxConnectPerRoute = 64;

	/**
	 * 从连接池获取连接的超时时间,不宜过长,默认3s
	 */
	private int connectionRequestTimout = 3 * 1000;

	/**
	 * 客户端和服务器建立连接超时,默认5s
	 */
	private int connectTimeout = 5 * 1000;

	/**
	 * 指客户端从服务器读取数据包的间隔超时时间,不是总读取时间,默认30s
	 */
	private int readTimeout = 30 * 1000;

	/**
	 * 字符集
	 */
	private String charset = "UTF-8";

	/**
	 * 重试次数,默认2次
	 */
	private int retryTimes = 2;

	/**
	 * 针对不同的地址,特别设置不同的长连接保持时间
	 */
	private Map<String, Integer> keepAliveTargetHost;

	/**
	 * 针对不同的地址,特别设置不同的长连接保持时间,单位 s
	 */
	private int keepAliveTime = 60;


	/**
	 * HTTP代理配置
	 */
	private HttpProxy httpProxy;

	@Override
	public String toString() {
		return "HttpConfig{" +
				"maxTotalConnect=" + maxTotalConnect +
				", maxConnectPerRoute=" + maxConnectPerRoute +
				", connectionRequestTimout=" + connectionRequestTimout +
				", connectTimeout=" + connectTimeout +
				", readTimeout=" + readTimeout +
				", httpProxy=" + (httpProxy == null ? "null" : httpProxy.toString()) +
				'}';
	}

	public String getKey() {
		String key = this.toString();
		try {
			MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
			byte[] digestBytes = messageDigest.digest(key.getBytes(StandardCharsets.UTF_8));
			key = DatatypeConverter.printHexBinary(digestBytes);
		} catch (Exception ignore) {
			// 此处异常无需处理,当出现异常时直接返回this.toString()对工具功能无任何影响
		}
		return key;
	}

	@Data
	@Configuration
	@ConfigurationProperties(prefix = "proxy")
	public class HttpProxy {

		/**
		 * 代理主机名
		 */
		private String httpProxyHost;

		/**
		 * 代理端口
		 */
		private Integer httpProxyPort;

		/**
		 * 登录凭证-用户名
		 */
		private String userName;

		/**
		 * 登录凭证-密码
		 */
		private String password;

		@Override
		public String toString() {
			return "HttpProxy{" +
					"httpProxyHost='" + httpProxyHost + '\'' +
					", httpProxyPort=" + httpProxyPort +
					", userName='" + userName + '\'' +
					", password='" + password + '\'' +
					'}';
		}
	}
}

5.2 HttpUtil方法


import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.text.UnicodeUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.*;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
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.client.utils.URIBuilder;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.ConnectTimeoutException;
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.entity.StringEntity;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
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.util.EntityUtils;

import javax.net.ssl.SSLException;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.net.SocketTimeoutException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.UnknownHostException;
import java.nio.charset.Charset;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

@Slf4j
public class HttpUtil {

	/**
	 * http请求方式
	 */
	public enum Https {

		/**
		 * post
		 */
		POST,

		/**
		 * get
		 */
		GET;

	}

	/**
	 * 编码格式 发送编码格式统一用 utf-8
	 */
	private static final String CHARSET_UTF8 = "UTF-8";

	private static final Object LOCK = new Object();

	private static final Map<String, CloseableHttpClient> HTTP_CLIENTS = new ConcurrentHashMap<>();


	private static final HttpClientPoolConfig DEFAULT_REST_CONFIG = new HttpClientPoolConfig();

	private CloseableHttpClient httpClient;

	private static HttpUtil getInstance() {
		return getInstance(DEFAULT_REST_CONFIG);
	}

	private static HttpUtil getInstance(HttpClientPoolConfig httpClientPoolConfig) {
		return new HttpUtil(httpClientPoolConfig);
	}


	private HttpUtil(HttpClientPoolConfig httpClientPoolConfig) {
		init(httpClientPoolConfig);
	}

	private void init(HttpClientPoolConfig httpClientPoolConfig) {
		if (httpClientPoolConfig == null) {
			// 如果传入的配置为null则给个默认配置
			httpClientPoolConfig = DEFAULT_REST_CONFIG;
		}

		String configKey = httpClientPoolConfig.getKey();
		// 判断内存中是否已存在对应配置的RestTemplate实例,当不存在对应配置的实例时执行内部逻辑,构建一个新的RestTemplate实例
		if (!HTTP_CLIENTS.containsKey(configKey)) {
			// 加锁
			synchronized (LOCK) {
				// 二次判断内存中是否存在对应实例
				if (!HTTP_CLIENTS.containsKey(configKey)) {
					HttpClientBuilder httpClientBuilder = HttpClients.custom()
							.setConnectionManager(getConnectionManager(httpClientPoolConfig))
							.setDefaultRequestConfig(getRequestConfig(httpClientPoolConfig))
							.setRetryHandler(httpRequestRetryHandler(httpClientPoolConfig.getRetryTimes()))
							.setKeepAliveStrategy(connectionKeepAliveStrategy(httpClientPoolConfig))
							.evictExpiredConnections();

					HttpClientPoolConfig.HttpProxy httpProxy = httpClientPoolConfig.getHttpProxy();
					if (httpProxy != null) {
						configHttpProxy(httpClientBuilder, httpClientPoolConfig);
					}

					CloseableHttpClient client = httpClientBuilder
							.build();
					HTTP_CLIENTS.put(configKey, client);
				}
			}
		}

		this.httpClient = HTTP_CLIENTS.get(configKey);
	}

	private PoolingHttpClientConnectionManager getConnectionManager(HttpClientPoolConfig httpClientPoolConfig) {

		Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
				// 注册http和https请求
				.register("http", PlainConnectionSocketFactory.getSocketFactory())
				.register("https", SSLConnectionSocketFactory.getSocketFactory()).build();

		PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
		// 连接池最大连接数
		connectionManager.setMaxTotal(httpClientPoolConfig.getMaxTotalConnect());
		// 路由链接数,每个主机的并发
		connectionManager.setDefaultMaxPerRoute(httpClientPoolConfig.getMaxConnectPerRoute());

		//定时关闭过期的或空闲的连接,节省服务资源。
		new HttpClientConnectionMonitorThread(connectionManager, httpClientPoolConfig.getKey())
				.start();

		return connectionManager;
	}

	/**
	 * 请求器的配置
	 * @return
	 */
	private RequestConfig getRequestConfig(HttpClientPoolConfig httpClientPoolConfig) {
		return RequestConfig.custom()
				.setConnectionRequestTimeout(httpClientPoolConfig.getConnectionRequestTimout())
				.setConnectTimeout(httpClientPoolConfig.getConnectTimeout())
				.setSocketTimeout(httpClientPoolConfig.getReadTimeout())
				.build();
	}

	private HttpClientBuilder configHttpProxy(HttpClientBuilder httpClientBuilder, HttpClientPoolConfig httpClientPoolConfig){
		HttpClientPoolConfig.HttpProxy httpProxy = httpClientPoolConfig.getHttpProxy();
		if (httpProxy != null) {
			String httpProxyHost = httpProxy.getHttpProxyHost();
			Integer httpProxyPort = httpProxy.getHttpProxyPort();
			HttpHost proxy = new HttpHost(httpProxyHost, httpProxyPort);
			httpClientBuilder.setProxy(proxy);

			String userName = httpProxy.getUserName();
			String password = httpProxy.getPassword();
			if (userName != null && password != null) {
				CredentialsProvider provider = new BasicCredentialsProvider();
				provider.setCredentials(new AuthScope(proxy), new UsernamePasswordCredentials(userName, password));
				httpClientBuilder.setDefaultCredentialsProvider(provider);
			}
		}
		return httpClientBuilder;
	}

	private ConnectionKeepAliveStrategy connectionKeepAliveStrategy(HttpClientPoolConfig httpClientPoolConfig) {
		return (response, context) -> {
			// Honor 'keep-alive' header
			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 && "timeout".equalsIgnoreCase(param)) {
					try {
						return Long.parseLong(value) * 1000;
					}
					catch (NumberFormatException ignore) {
						log.error("解析长连接过期时间异常", ignore);
					}
				}
			}
			HttpHost target = (HttpHost) context.getAttribute(HttpClientContext.HTTP_TARGET_HOST);
			// 如果请求目标地址,单独配置了长连接保持时间,使用该配置
			Optional<Map.Entry<String, Integer>> any = Optional
					.ofNullable(httpClientPoolConfig.getKeepAliveTargetHost()).orElseGet(HashMap::new).entrySet()
					.stream().filter(e -> e.getKey().equalsIgnoreCase(target.getHostName())).findAny();
			// 否则使用默认长连接保持时间
			return any.map(en -> en.getValue() * 1000L).orElse(httpClientPoolConfig.getKeepAliveTime() * 1000L);
		};
	}

	private HttpRequestRetryHandler httpRequestRetryHandler(int retryCount) {
		return (e, i, httpContext) -> {
			// 超过最大重试次数,放弃请求
			if (i > retryCount) {
				log.info("重试超过{}次,放弃请求。", retryCount);
				return false;
			}

			// 针对特定异常类型判断是否需要重试
			if (e instanceof ConnectTimeoutException) {
				log.warn("连接超时,正在重试...");
				return true;
			}
			if (e instanceof NoHttpResponseException) {
				log.warn("服务器没有响应,正在重试...");
				return true;
			}
			if (e instanceof UnknownHostException) {
				log.warn("服务器不可达,正在重试...");
				return true;
			}
			if (e instanceof SSLException) {
				log.warn("SSL异常,正在重试...");
				return true;
			}
			if (e instanceof SocketTimeoutException) {
				log.warn("读取超时,正在重试...");
				return true;
			}
			if (e instanceof InterruptedIOException) {
				log.warn("请求被中断,不重试。");
				return false;
			}

			// 获取请求上下文,进一步判断请求类型
			HttpClientContext context = HttpClientContext.adapt(httpContext);
			HttpRequest request = context.getRequest();

			if (!(request instanceof HttpEntityEnclosingRequest)) {
				// 如果请求不是带有实体的请求(即幂等请求),可以重试
				log.info("非实体请求,正在重试...");
				return true;
			}

			// 如果是带有请求体的请求,避免重试
			log.info("实体请求,不重试。");
			return false;
		};
	}

	/**
	 * 创建访问的地址 拼接url
	 * @param url 基础url
	 * @param params 请求参数
	 * @return URI
	 */
	private static URI getUrl(String url, Map<String, Object> params, Charset charset)
			throws HttpUtilClosableException {
		// 创建访问的地址
		try {
			URIBuilder uriBuilder = new URIBuilder(url).setCharset(charset);
			if (params != null) {
				Set<Map.Entry<String, Object>> entrySet = params.entrySet();
				for (Map.Entry<String, Object> entry : entrySet) {
					String key = entry.getKey();
					Object value = entry.getValue();
					if (value != null) {
						uriBuilder.setParameter(key, String.valueOf(value));
					}
				}
			}
			return uriBuilder.build();
		}
		catch (URISyntaxException e) {
			throw new HttpUtilClosableException(e.getMessage(), e);
		}
	}

	/**
	 * 设置请求头
	 * @param httpRequest http请求
	 * @param headers 请求头参数
	 */
	private static HttpRequestBase setHeader(HttpRequestBase httpRequest, Map<String, String> headers) {
		// 设置请求头
		if (headers != null) {
			Set<Map.Entry<String, String>> entrySet = headers.entrySet();
			for (Map.Entry<String, String> entry : entrySet) {
				// 设置请求头到 HttpRequestBase
				httpRequest.setHeader(entry.getKey(), entry.getValue());
			}
		}
		return httpRequest;
	}

	/**
	 * get请求方法 带请求头和请求参数
	 * @param url 请求地址
	 * @param headers 请求头
	 * @param params 请求参数
	 * @return HttpGet
	 */
	private static HttpGet createHttpGetRequest(String url, Map<String, String> headers, Map<String, Object> params, Charset charset)
			throws HttpUtilClosableException {
		// 创建访问的地址
		URI uri = getUrl(url, params, charset);
		// 创建http对象
		HttpGet httpGet = new HttpGet(uri);
		// 设置请求头
		return (HttpGet) setHeader(httpGet, headers);
	}

	/**
	 * post请求 (key-value格式)
	 * @param url 请求地址
	 * @param headers 请求头
	 * @param params 请求参数
	 * @return HttpPost
	 */
	private static HttpPost createHttpPostRequest(String url, Map<String, String> headers, Map<String, Object> params,
												  Object jsonBody, HttpEntity httpEntity, String contentType, Charset charset)
			throws HttpUtilClosableException {

		// 第一步:根据参数决定URI
		URI uri = getUrl(url, params, charset);
		HttpPost httpPost = new HttpPost(uri);

		// 第二步:设置HTTP请求体(Body)
		if (httpEntity != null) {
			httpPost.setEntity(httpEntity);
		} else if (jsonBody != null) {
			setJsonBody(httpPost, jsonBody, contentType, charset);
		} else if (params != null) {
			setParamsBody(httpPost, params, charset);
		}

		// 第三步:设置请求头
		return (HttpPost) setHeader(httpPost, headers);
	}

	// 设置JSON请求体的辅助方法
	private static void setJsonBody(HttpPost httpPost, Object jsonBody, String contentType, Charset charset) {
		// 将jsonBody转换为字符串
		String json = (jsonBody instanceof String) ? (String) jsonBody : JSONUtil.toJsonStr(jsonBody);

		// 默认设置Content-Type为application/json
		httpPost.setHeader(HTTP.CONTENT_TYPE, "application/json");

		// 设置请求体内容
		StringEntity entity = new StringEntity(json, charset);
		if (contentType != null) {
			entity.setContentType(contentType);
		}
		httpPost.setEntity(entity);
	}

	// 设置表单参数请求体的辅助方法
	private static void setParamsBody(HttpPost httpPost, Map<String, Object> params, Charset charset) {
		List<NameValuePair> nvp = new ArrayList<>();
		for (Map.Entry<String, Object> entry : params.entrySet()) {
			if (entry.getValue() != null) {
				nvp.add(new BasicNameValuePair(entry.getKey(), String.valueOf(entry.getValue())));
			}
		}
		httpPost.setEntity(new UrlEncodedFormEntity(nvp, charset));
	}

	public static Builder builder() {
		return new Builder();
	}

	public static Builder builder(HttpClientPoolConfig httpClientPoolConfig) {
		return new Builder(httpClientPoolConfig);
	}

	/**
	 * 资源关闭
	 */
	public static void close(HttpUtil httpUtil) {
		try {
			if (httpUtil.httpClient != null) {
				httpUtil.httpClient.close();
			}
		}
		catch (IOException e) {
			log.error("Exception:", e);
		}
	}

	/**
	 * 构造http请求函数
	 */
	public static class Builder {

		/**
		 * 请求参数(key-value格式)
		 */
		private Map<String, Object> params;

		/**
		 * 参数编码格式
		 */
		private Charset charset = Charset.forName(CHARSET_UTF8);

		/**
		 * 是否打印请求响应日志.
		 */
		private Boolean logging = false;

		/**
		 * 请求参数(json格式)
		 */
		private Object jsonBody;

		private HttpEntity httpEntity;

		/**
		 * 请求头
		 */
		private Map<String, String> headers;

		/**
		 * 参数返回格式
		 */
		private String contentType;

		/**
		 * http请求
		 */
		private HttpUriRequest httpRequest;

		/**
		 * http响应
		 */
		private CloseableHttpResponse httpResponse;

		/**
		 * HttpUtils工具类实例化
		 */
		private HttpUtil httpUtil;

		/**
		 * 初始化
		 */
		private Builder() {
			this.httpUtil = HttpUtil.getInstance();
		}

		private Builder(HttpClientPoolConfig httpClientPoolConfig) {
			this.httpUtil = HttpUtil.getInstance(httpClientPoolConfig);
		}

		/**
		 * 添加参数
		 * @param key
		 * @param value
		 * @return
		 */
		public Builder addParams(String key, Object value) {
			if (this.params == null) {
				this.params = new HashMap<>(5);
			}
			this.params.put(key, value);
			return this;
		}

		public Builder setLogging(boolean logging) {
			this.logging = logging;
			return this;
		}

		/**
		 * 添加参数
		 * @param params
		 * @return
		 */
		public Builder addParams(Map<String, Object> params) {
			if (this.params == null) {
				this.params = new HashMap<>(5);
			}
			this.params.putAll(params);
			return this;
		}

		/**
		 * 设置参数编码格式
		 * @param charset
		 * @return
		 */
		public Builder setCharset(String charset) {
			this.charset = Charset.forName(charset);
			return this;
		}

		/**
		 * 添加参数
		 */
		public Builder setJsonBody(Object object) {
			this.jsonBody = object;
			return this;
		}

		public Builder setEntity(HttpEntity httpEntity) {
			this.httpEntity = httpEntity;
			return this;
		}

		/**
		 * 添加请求头
		 */
		public Builder addHeaders(String key, String value) {
			if (this.headers == null) {
				this.headers = new HashMap<>(5);
			}
			this.headers.put(key, value);
			return this;
		}

		/**
		 * 设置返回格式
		 */
		public Builder setContentType(String contentType) {
			this.contentType = contentType;
			return this;
		}

		/**
		 * http请求
		 * @param url 请求地址
		 * @param https 请求方法
		 * @return
		 */
		public Builder doHttp(String url, Https https) {
			switch (https) {
			case POST:
				return doPost(url);
			case GET:
				return doGet(url);
			default:
				return this;
			}
		}

		/**
		 * get请求方法 带请求头和请求参数
		 * @param url 请求地址
		 * @return HttpGet
		 */
		public Builder doGet(String url) {
			try {
				this.httpRequest = HttpUtil.createHttpGetRequest(url, this.headers, this.params, this.charset);
			}
			catch (HttpUtilClosableException e) {
				log.error("Exception:", e);
			}
			return execute();
		}

		/**
		 * post请求 (key-value格式)
		 * @param url 请求地址
		 * @return HttpPost
		 */
		public Builder doPost(String url) {
			try {
				this.httpRequest = HttpUtil.createHttpPostRequest(url, this.headers, this.params, this.jsonBody, this.httpEntity,
						this.contentType, this.charset);
			}
			catch (HttpUtilClosableException e) {
				log.error("Exception:", e);
			}
			return execute();
		}

		public Builder execute(HttpUriRequest request) {
			try {
				CloseableHttpClient httpClient = this.httpUtil.httpClient;
				this.traceRequest();
				this.httpResponse = httpClient.execute(request);
			}
			catch (IOException e) {
				log.error("Exception:", e);
			}
			return callback();
		}

		/**
		 * http请求
		 * @return
		 */
		private Builder execute() {
			if (this.httpRequest != null) {
				try {
					// 请求client
					CloseableHttpClient httpClient = this.httpUtil.httpClient;;
					traceRequest();
					this.httpResponse = httpClient.execute(this.httpRequest);
				}
				catch (IOException e) {
					log.error("Exception:", e);
				}
			}
			return callback();
		}

		/**
		 * 响应参数校验
		 * @return
		 */
		private Builder callback() {
			if (this.httpResponse != null) {
				// 响应状态
				StatusLine status = this.httpResponse.getStatusLine();
				if (status.getStatusCode() != HttpStatus.SC_OK) {
					try {
						log.error("HttpUtil请求异常状态码>>" + status.getStatusCode());
						throw new HttpUtilClosableException(status.getReasonPhrase());
					}
					catch (HttpUtilClosableException e) {
						log.error("Exception:", e);
					}
					finally {
						this.httpResponse = null;
					}
				}
			}
			return this;
		}

		/**
		 * 获取请求连接
		 * @return
		 */
		public String getUrl() {
			if (this.httpResponse != null) {
				return this.httpRequest.getURI().toString();
			}
			return null;
		}

		/**
		 * 获取响应头
		 * @param map
		 * @param keys
		 * @return
		 */
		public Builder getHeaders(Map<String, String> map, String... keys) {
			if (this.httpResponse != null) {
				for (String key : keys) {
					Header[] headers = this.httpResponse.getHeaders(key);
					for (Header header : headers) {
						map.put(header.getName(), header.getValue());
					}
				}
			}
			return this;
		}

		/**
		 * 获取响应头
		 * @param map
		 * @return
		 */
		public Builder getHeaders(Map<String, String> map) {
			if (this.httpResponse != null) {
				Header[] headers = this.httpResponse.getAllHeaders();
				for (Header header : headers) {
					map.put(header.getName(), header.getValue());
				}
			}
			return this;
		}

		/**
		 * 返回数据 byte
		 * @return
		 */
		public byte[] toByte() {
			byte[] bytes = null;
			if (this.httpResponse != null) {
				try {
					bytes = EntityUtils.toByteArray(this.httpResponse.getEntity());
				}
				catch (IOException e) {
					log.error("Exception:", e);
				}
				finally {
					try {
						this.httpResponse.close();
					}
					catch (IOException e) {
						log.error("Exception:", e);
					}
				}
			}
			return bytes;
		}

		/**
		 * 返回数据 InputStream
		 * @return
		 */
		public InputStream toInput() {
			InputStream input = null;
			if (this.httpResponse != null) {
				try {
					byte[] bytes = EntityUtils.toByteArray(this.httpResponse.getEntity());
					// 使用InputStream对象时,再从bytes转化回来
					input = new ByteArrayInputStream(bytes);
				}
				catch (IOException e) {
					log.error("Exception:", e);
				}
				finally {
					try {
						this.httpResponse.close();
					}
					catch (IOException e) {
						log.error("Exception:", e);
					}
				}
			}
			return input;
		}

		/**
		 * 返回数据 String
		 * @return
		 */
		public String toStr() {
			return toStr(CHARSET_UTF8);
		}

		/**
		 * 返回数据 String
		 * @param defaultCharset
		 * @return
		 */
		public String toStr(String defaultCharset) {
			String str = null;
			if (this.httpResponse != null) {
				try {
					str = EntityUtils.toString(this.httpResponse.getEntity(), defaultCharset);
					traceResponse(str);
				}
				catch (IOException e) {
					log.error("Exception:", e);
				}
				finally {
					try {
						this.httpResponse.close();
					}
					catch (IOException e) {
						log.error("Exception:", e);
					}
				}
			}
			return str;
		}

		/**
		 * 返回参数 map格式
		 * @return
		 */
		public <T> Map<String, T> toMap() {
			Map<String, T> map = null;
			if (this.httpResponse != null) {
				try {
					map = (Map<String, T>) JSONUtil.parseObj(this.toStr());
				}
				catch (Exception e) {
					log.error("Exception:", e);
				}
			}
			return map;
		}

		/**
		 * 返回参数 clazz
		 * @return
		 */
		public <T> T toObject(Class<T> clazz) {
			T t = null;
			if (this.httpResponse != null) {
				try {
					t = JSONUtil.toBean(this.toStr(), clazz);
				}
				catch (Exception e) {
					log.error("Exception:", e);
				}
			}
			return t;
		}

		/**
		 * 返回参数 list
		 * @return
		 */
		public <T> List<T> toArray(Class<T> clazz) {
			List<T> array = null;
			if (this.httpResponse != null) {
				try {
					JSONUtil.toList(toStr(), clazz);
				}
				catch (Exception e) {
					log.error("Exception:", e);
				}
			}
			return array;
		}

		/**
		 * 返回参数 response
		 * @return
		 */
		public <S> Result<S> toResponseObject(Class<S> cls) {
			Result<S> result = null;
			if (this.httpResponse != null) {
				try {
					result = Result.data(JSONUtil.toBean(this.toStr(), cls));
				}
				catch (Exception e) {
					log.error("Exception:", e);
				}
			}
			return result;
		}

		/**
		 * 返回参数 response array
		 * @return
		 */
		public <S> Result<List<S>> toResponseArray(Class<S> cls) {
			Result<List<S>> result = null;
			if (this.httpResponse != null) {
				try {
					result = Result.data(JSONUtil.toList(this.toStr(), cls));
				}
				catch (Exception e) {
					log.error("Exception:", e);
				}
			}
			return result;
		}

		/**
		 * 释放资源
		 */
		public void close() {
			if (this.httpResponse != null) {
				try {
					this.httpResponse.close();
				}
				catch (IOException e) {
					log.error("Exception:", e);
				}
			}
		}

		private void traceRequest() {
			if (this.logging) {
				log.info("===========================request begin================================================");
				log.info("Request URI: {}", this.httpRequest.getURI());
				log.info("Request Method: {}", this.httpRequest.getMethod());
				if (CollUtil.isNotEmpty(this.params)) {
					log.info("Request Param: {}", JSONUtil.toJsonStr(this.params));
				}
				if (ObjectUtil.isNotEmpty(this.jsonBody)) {
					log.info("Request Body: {}", JSONUtil.toJsonStr(this.jsonBody));
				}
				log.info("==========================request end================================================");
			}
		}

		private void traceResponse(String responseStr) {
			if (this.logging) {
				log.info("============================response begin==========================================");
				log.info("Status code  : {}", this.httpResponse.getStatusLine().getStatusCode());
				log.info("Status text  : {}", this.httpResponse.getStatusLine().getReasonPhrase());
				log.info("ResponseBody : {}", UnicodeUtil.toString(responseStr));
				log.info("=======================response end=================================================");
			}
		}
	}

	/********************************** 【内部资源回收】- 开始 **********************************/

	/**
	 * 连接池监控线程。作用:定时关闭过期的或空闲的连接,节省服务资源
	 */
	private static class HttpClientConnectionMonitorThread extends Thread {

		private final PoolingHttpClientConnectionManager connManager;
		private volatile boolean shutdown = false;

		public HttpClientConnectionMonitorThread(PoolingHttpClientConnectionManager connManager, String configKey) {
			super();
			this.setName("http-connection-monitor-" + configKey);
			this.setDaemon(true);
			this.connManager = connManager;
		}

		@Override
		public void run() {
			while (!shutdown) {
				try {
					synchronized (this) {
						// 等待5秒(官方示例)
						wait(5000);

						// 关闭过期的链接
						connManager.closeExpiredConnections();

						// 选择关闭 空闲30秒的链接
						connManager.closeIdleConnections(30, TimeUnit.SECONDS);
					}
				} catch (InterruptedException ex) {
					// 发生异常时进行捕获,不会导致回收线程中断,会继续下一轮回收工作
					Thread.currentThread().interrupt();
				}
			}
		}
	}
	/********************************** 【内部资源回收】- 结束 **********************************/
}

更多推荐