推荐Springboot集成http连接池 https://blog.csdn.net/hellozpc/article/details/106861972
常用的http客户端有JDK原生的URLConnection、Netty的异步HTTP Client、Spring的RestTemplate、Spring Cloud中的Feign。
虽然RestTemplate、Feign使用极其方便,但是屏蔽了太多底层细节,不利于全局把控。
本文主要记载一下基于Apache HttpClient 的http连接池处理。网上很多文章都没有关注定期检测关闭无效连接这块功能。另外要注册jvm钩子在程序退出时关闭httpClient实例。
/**
* 业务层调用HttpTemplate#execute发送http post请求
* @author zhoupengcheng
*/
public class HttpTemplate {
private static Logger logger = LoggerFactory.getLogger(HttpTemplate.class);
private static final String DEFAULT_ACCEPTTYPE = "application/json;charset=utf-8";
private static final String DEFAULT_CHARSET = "utf-8";
/**
* 最大连接数
*/
private static final int MAX_CONNECTION_NUM = 20;
/**
* 单路由最大连接数
*/
private static final int MAX_PER_ROUTE = 20;
/**
* 连接超时时间,缺省为3秒钟
*/
private static final int DEFAULT_CONNECTION_TIMEOUT = 3000;
/**
* 读取超时时间,缺省为3秒钟
*/
private static final int DEFAULT_READ_TIMEOUT = 3000;
/**
* 连接池管理对象
*/
private PoolingHttpClientConnectionManager cm;
private CloseableHttpClient httpClient;
/**
* 反序列化工具
*/
private ObjectMapper objectMapper;
public HttpTemplate() {
init();
}
public void init() {
initObjectMapper();
initHttpClient();
}
private void initHttpClient() {
HttpClientBuilder builder = HttpClients.custom();
builder.disableCookieManagement();
builder.disableRedirectHandling();
builder.disableAutomaticRetries();
builder.setConnectionManagerShared(false)
.evictIdleConnections(1, TimeUnit.MINUTES)// 定期回收空闲连接
.evictExpiredConnections()// 定期回收过期连接
.setConnectionTimeToLive(1, TimeUnit.MINUTES) // 连接存活时间,如果不设置,则根据长连接信息决定
.setDefaultRequestConfig(
RequestConfig
.custom()
.setAuthenticationEnabled(false)
.setCircularRedirectsAllowed(false)
.setSocketTimeout(DEFAULT_READ_TIMEOUT)
.setConnectTimeout(DEFAULT_CONNECTION_TIMEOUT)
.setConnectionRequestTimeout(1000)
.setCookieSpec(CookieSpecs.IGNORE_COOKIES)
.build()) // 设置默认请求配置
.setConnectionReuseStrategy(DefaultConnectionReuseStrategy.INSTANCE) // 连接重用策略 是否能keepAlive
.setKeepAliveStrategy(DefaultConnectionKeepAliveStrategy.INSTANCE)// 长连接配置,即获取长连接生产多长时间
.setRetryHandler(new DefaultHttpRequestRetryHandler(0, false))// 重试次数 默认3次 此处禁用
.build();
Registry socketFactoryRegistry =
RegistryBuilder.create()
.register("https", PlainConnectionSocketFactory.getSocketFactory())
.register("http", new PlainConnectionSocketFactory()).build();
final int conExpire = 15;// 长连接闲置过期时间,可配置
cm = new PoolingHttpClientConnectionManager(socketFactoryRegistry, null, null, null, conExpire,
TimeUnit.SECONDS);
cm.setDefaultConnectionConfig(ConnectionConfig.DEFAULT);
cm.setMaxTotal(MAX_CONNECTION_NUM);
cm.setDefaultMaxPerRoute(MAX_PER_ROUTE);
// 设置长连接心跳检测,设置超时,禁用nagle算法
cm.setDefaultSocketConfig(SocketConfig.custom().setSoKeepAlive(true).setSoTimeout(DEFAULT_READ_TIMEOUT).setTcpNoDelay(true).build());
cm.setValidateAfterInactivity(-1);// 每次取重用的连接时禁止连接活性检测,可以提升性能
builder.setConnectionManager(cm);
builder.setConnectionManagerShared(false);
httpClient = builder.build();
// 过期检测
Thread staleCheckThread = new Thread(() -> {
while (true) {
try {
Thread.sleep(10000);
cm.closeExpiredConnections();
cm.closeIdleConnections(conExpire, TimeUnit.SECONDS);
} catch (Exception e) {
logger.error("stale check exception", e);
}
}
}, "HttpInvoker-coonection-stale-check-thread");
// 设置成为守护线程
staleCheckThread.setDaemon(true);
staleCheckThread.start();
// 程序退出时,关闭httpClient
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
try {
httpClient.close();
logger.info(">>>>httpClient closed<<<<");
} catch (Exception e) {
logger.warn("httpClient close exception", e);
}
}, "srsynchronize-httpInvoker-shutdown-thread"));
}
private void initObjectMapper() {
objectMapper = new ObjectMapper();
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
}
/**
* 暴露给业务层调用的方法
* @param path 请求url
* @param req 请求对象
* @param typeReference 响应对象
* @param
* @return
* @throws Exception
*/
public R execute(String path, Object req, TypeReference typeReference) throws Exception {
if (typeReference == null) {
typeReference = (TypeReference) new TypeReference() {
};
}
String reqBody = objectMapper.writeValueAsString(req);
logger.info("reqBody:{}", reqBody);
HttpPost httpPost = new HttpPost(path);
httpPost.setProtocolVersion(HttpVersion.HTTP_1_1);
httpPost.setHeader(HTTP.CONN_DIRECTIVE, HTTP.CONN_KEEP_ALIVE);
httpPost.setConfig(RequestConfig.custom().setSocketTimeout(DEFAULT_READ_TIMEOUT)
.setConnectTimeout(DEFAULT_CONNECTION_TIMEOUT).build());
httpPost.addHeader("Accept", DEFAULT_ACCEPTTYPE);
httpPost.addHeader("Content-Type", DEFAULT_ACCEPTTYPE);
HttpEntity httpEntity = new StringEntity(reqBody, DEFAULT_CHARSET);
httpPost.setEntity(httpEntity);
CloseableHttpResponse httpResponse = httpClient.execute(httpPost);
String responseBody = EntityUtils.toString(httpResponse.getEntity(), DEFAULT_CHARSET);
logger.info("resBody:{}", responseBody);
return objectMapper.readValue(responseBody, typeReference);
}
/**
* demo
*
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
HttpTemplate httpTemplate = new HttpTemplate();
Order order = new Order();
order.id = "1111111";
order.cancleRemarks = "擦擦擦";
order.createDate = LocalDateTime.now();
order.startLongitude = "1356.4343";
OrderResponse orderResponse = httpTemplate.execute("http://127.0.0.1:8888/ordersdispatch", order, new TypeReference() {
});
//不传具体的OrderResponse会使用默认的Response
Response response = httpTemplate.execute("http://127.0.0.1:8888/orderdispatch", order, null);
logger.info("orderResponse:{}", orderResponse);
logger.info("response:{}", response);
}
}
public class Order {
public String id;
public String orderCode;
public String startCityName;
public String startAddress;
public String startAddDtl;
public String startLongitude;
public String startLatitude;
public String endCityName;
public String endAddress;
public String endAddDtl;
public String endLongitude;
public String endLatitude;
public byte prodType;
public LocalDateTime orderDate;
public int orderDay;
public String flightNo;
public String remark;
public int orderState;
public String driverId;
public String driverUid;
public String driverMobile;
public String source;
public LocalDateTime createDate;
public LocalDateTime updateDate;
public String dispatchType;
public String driverRemark;
public LocalDateTime flightDate;
public String flightArrCode;
public String flightDepCode;
public String cancleRemarks;
public LocalDateTime endTime;
}
/**
* http响应实体
*/
public class Response {
public static int SUCCESS_CODE = 0;
public Integer retCode;
public String retMsg;
public static boolean isSuccess(Response res) {
return res != null && res.retCode == SUCCESS_CODE;
}
@Override
public String toString() {
return "Response{" +
"retCode=" + retCode +
", retMsg='" + retMsg + '\'' +
'}';
}
}
public class OrderResponse extends Response {
public List info;
}