推荐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; }