一、背景
业务开发中,经常会遇到通过http/https向下游服务发送请求。每次都要重复造轮子写HttpClient的逻辑,而且性能、功能参差不齐。这里分享一个高性能的、带连接池的通用Http客户端工具。
请尊重作者劳动成果,转载请标明原文链接:https://www.cnblogs.com/waterystone/p/11551280.html
二、特点
- 基于apache的高性能Http客户端org.apache.http.client.HttpClient;
- 连接池的最大连接数默认是20,可通过系统变量-Dadu.common.http.max.total=200指定;
- 连接池的每个路由的最大连接数默认是2,可通过系统变量-Dadu.common.http.max.per.route=10指定;
- 可设置超时,通过HttpOptions进行设置;
- 可重试,通过HttpOptions进行设置。
三、源码
参考:https://github.com/waterystone/adu-test/blob/master/src/main/java/com/adu/utils/HttpClientUtil.java
1 package com.adu.utils; 2 3 import com.adu.Constants; 4 import com.adu.handler.HttpRequestRetryHandler; 5 import com.adu.model.HttpOptions; 6 import org.apache.http.HttpEntity; 7 import org.apache.http.NameValuePair; 8 import org.apache.http.client.config.RequestConfig; 9 import org.apache.http.client.entity.UrlEncodedFormEntity; 10 import org.apache.http.client.methods.CloseableHttpResponse; 11 import org.apache.http.client.methods.HttpGet; 12 import org.apache.http.client.methods.HttpPost; 13 import org.apache.http.client.methods.HttpRequestBase; 14 import org.apache.http.client.utils.URIBuilder; 15 import org.apache.http.client.utils.URLEncodedUtils; 16 import org.apache.http.config.Registry; 17 import org.apache.http.config.RegistryBuilder; 18 import org.apache.http.conn.socket.ConnectionSocketFactory; 19 import org.apache.http.conn.socket.PlainConnectionSocketFactory; 20 import org.apache.http.conn.ssl.SSLConnectionSocketFactory; 21 import org.apache.http.entity.StringEntity; 22 import org.apache.http.impl.client.CloseableHttpClient; 23 import org.apache.http.impl.client.HttpClients; 24 import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; 25 import org.apache.http.message.BasicNameValuePair; 26 import org.apache.http.util.EntityUtils; 27 import org.slf4j.Logger; 28 import org.slf4j.LoggerFactory; 29 30 import javax.net.ssl.SSLContext; 31 import java.nio.charset.StandardCharsets; 32 import java.util.ArrayList; 33 import java.util.List; 34 import java.util.Map; 35 import java.util.Objects; 36 import java.util.stream.Collectors; 37 38 /** 39 * 带有连接池的Http客户端工具类。具有如下特点: 40 * <ol> 41 * <li>基于apache的高性能Http客户端{@link org.apache.http.client.HttpClient};</li> 42 * <li>连接池的最大连接数默认是20,可通过系统变量-Dzzarch.common.http.max.total=200指定;</li> 43 * <li>连接池的每个路由的最大连接数默认是2,可通过系统变量-Dzzarch.common.http.max.per.route=10指定;</li> 44 * <li>可设置超时,通过{@link HttpOptions}进行设置;</li> 45 * <li>可重试,通过{@link HttpOptions}进行设置;</li> 46 * </ol> 47 * 48 * @author duyunjie 49 * @date 2019-09-18 16:33 50 */ 51 public class HttpClientUtil { 52 private static final Logger logger = LoggerFactory.getLogger(HttpClientUtil.class); 53 54 /** 55 * HttpClient 连接池 56 */ 57 private static PoolingHttpClientConnectionManager CONNECTION_MANAGER = initPoolingHttpClientConnectionManager(); 58 59 public static String httpGet(String url) throws Exception { 60 return httpGet(url, null, null, null); 61 } 62 63 64 public static String httpGet(String url, HttpOptions httpOptions) throws Exception { 65 return httpGet(url, null, null, httpOptions); 66 } 67 68 69 public static String httpGet(String url, Map<String, ?> params) throws Exception { 70 return httpGet(url, null, params, null); 71 } 72 73 74 public static String httpGet(String url, Map<String, ?> params, HttpOptions httpOptions) throws Exception { 75 return httpGet(url, null, params, httpOptions); 76 } 77 78 79 public static String httpGet(String url, Map<String, ?> headers, Map<String, ?> params) throws Exception { 80 return httpGet(url, headers, params, null); 81 } 82 83 /** 84 * 发送 HTTP GET请求 85 * 86 * @param url 87 * @param headers 请求头 88 * @param params 请求参数 89 * @param httpOptions 配置参数,如重试次数、超时时间等。 90 * @return 91 * @throws Exception 92 */ 93 public static String httpGet(String url, Map<String, ?> headers, Map<String, ?> params, HttpOptions httpOptions) throws Exception { 94 // 装载请求地址和参数 95 URIBuilder ub = new URIBuilder(); 96 ub.setPath(url); 97 98 // 转换请求参数 99 List<NameValuePair> pairs = convertParams2NVPS(params); 100 if (!pairs.isEmpty()) { 101 ub.setParameters(pairs); 102 } 103 HttpGet httpGet = new HttpGet(ub.build()); 104 105 // 设置请求头 106 if (Objects.nonNull(headers)) { 107 for (Map.Entry<String, ?> param : headers.entrySet()) { 108 httpGet.addHeader(param.getKey(), String.valueOf(param.getValue())); 109 } 110 } 111 112 return doHttp(httpGet, httpOptions); 113 } 114 115 116 public static String httpPost(String url, Map<String, ?> params) throws Exception { 117 return httpPost(url, null, params, null); 118 } 119 120 public static String httpPost(String url, Map<String, ?> params, HttpOptions httpOptions) throws Exception { 121 return httpPost(url, null, params, httpOptions); 122 } 123 124 125 public static String httpPost(String url, Map<String, ?> headers, Map<String, ?> params) throws Exception { 126 return httpPost(url, headers, params, null); 127 } 128 129 /** 130 * 发送 HTTP POST请求 131 * 132 * @param url 133 * @param headers 请求头 134 * @param params 请求参数 135 * @param httpOptions 配置参数,如重试次数、超时时间等。 136 * @return 137 * @throws Exception 138 */ 139 public static String httpPost(String url, Map<String, ?> headers, Map<String, ?> params, HttpOptions httpOptions) throws Exception { 140 HttpPost httpPost = new HttpPost(url); 141 142 // 转换请求参数 143 List<NameValuePair> pairs = convertParams2NVPS(params); 144 if (!pairs.isEmpty()) { 145 httpPost.setEntity(new UrlEncodedFormEntity(pairs, StandardCharsets.UTF_8.name())); 146 } 147 148 // 设置请求头 149 if (Objects.nonNull(headers)) { 150 for (Map.Entry<String, ?> param : headers.entrySet()) { 151 httpPost.addHeader(param.getKey(), String.valueOf(param.getValue())); 152 } 153 } 154 155 return doHttp(httpPost, httpOptions); 156 } 157 158 159 /** 160 * 发送 HTTP POST请求,参数格式JSON 161 * <p>请求参数是JSON格式,数据编码是UTF-8</p> 162 * 163 * @param url 164 * @param param 165 * @return 166 * @throws Exception 167 */ 168 public static String httpPostJson(String url, String param, HttpOptions httpOptions) throws Exception { 169 HttpPost httpPost = new HttpPost(url); 170 171 // 设置请求头 172 httpPost.addHeader("Content-Type", "application/json; charset=UTF-8"); 173 174 // 设置请求参数 175 httpPost.setEntity(new StringEntity(param, StandardCharsets.UTF_8.name())); 176 177 return doHttp(httpPost, httpOptions); 178 } 179 180 /** 181 * 发送 HTTP POST请求,参数格式XML 182 * <p>请求参数是XML格式,数据编码是UTF-8</p> 183 * 184 * @param url 185 * @param param 186 * @return 187 * @throws Exception 188 */ 189 public static String httpPostXml(String url, String param, HttpOptions httpOptions) throws Exception { 190 HttpPost httpPost = new HttpPost(url); 191 192 // 设置请求头 193 httpPost.addHeader("Content-Type", "application/xml; charset=UTF-8"); 194 195 // 设置请求参数 196 httpPost.setEntity(new StringEntity(param, StandardCharsets.UTF_8.name())); 197 198 return doHttp(httpPost, httpOptions); 199 } 200 201 202 /** 203 * 转换请求参数,将Map键值对拼接成QueryString字符串 204 * 205 * @param params 206 * @return 207 */ 208 public static String convertParams2QueryStr(Map<String, ?> params) { 209 List<NameValuePair> pairs = convertParams2NVPS(params); 210 211 return URLEncodedUtils.format(pairs, StandardCharsets.UTF_8.name()); 212 } 213 214 /** 215 * 转换请求参数 216 * 217 * @param params 218 * @return 219 */ 220 public static List<NameValuePair> convertParams2NVPS(Map<String, ?> params) { 221 if (params == null) { 222 return new ArrayList<>(); 223 } 224 225 return params.entrySet().stream().map(param -> new BasicNameValuePair(param.getKey(), String.valueOf(param.getValue()))).collect(Collectors.toList()); 226 } 227 228 /** 229 * 发送 HTTP 请求 230 * 231 * @param request 232 * @return 233 * @throws Exception 234 */ 235 private static String doHttp(HttpRequestBase request, HttpOptions httpOptions) throws Exception { 236 if (Objects.isNull(httpOptions)) {//如果为空,则用默认的。 237 httpOptions = new HttpOptions(); 238 } 239 // 设置超时时间 240 if (Objects.nonNull(httpOptions.getTimeoutMs())) { 241 request.setConfig(RequestConfig.custom().setSocketTimeout(httpOptions.getTimeoutMs()).build()); 242 } 243 244 //设置重试策略 245 HttpRequestRetryHandler httpRequestRetryHandler = null; 246 if (Objects.nonNull(httpOptions.getRetryCount())) { 247 httpRequestRetryHandler = new HttpRequestRetryHandler(httpOptions.getRetryCount()); 248 } 249 250 // 通过连接池获取连接对象 251 CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(CONNECTION_MANAGER).setRetryHandler(httpRequestRetryHandler).build(); 252 return doRequest(httpClient, request); 253 254 } 255 256 /** 257 * 处理Http/Https请求,并返回请求结果 258 * <p>注:默认请求编码方式 UTF-8</p> 259 * 260 * @param httpClient 261 * @param request 262 * @return 263 * @throws Exception 264 */ 265 private static String doRequest(CloseableHttpClient httpClient, HttpRequestBase request) throws Exception { 266 String result = null; 267 CloseableHttpResponse response = null; 268 269 try { 270 // 获取请求结果 271 response = httpClient.execute(request); 272 // 解析请求结果 273 HttpEntity entity = response.getEntity(); 274 // 转换结果 275 result = EntityUtils.toString(entity, StandardCharsets.UTF_8.name()); 276 // 关闭IO流 277 EntityUtils.consume(entity); 278 } finally { 279 if (null != response) { 280 response.close(); 281 } 282 } 283 284 return result; 285 } 286 287 288 /** 289 * 初始化连接池 290 * 291 * @return 292 */ 293 private static PoolingHttpClientConnectionManager initPoolingHttpClientConnectionManager() { 294 // 初始化连接池,可用于请求HTTP/HTTPS(信任所有证书) 295 PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(getRegistry()); 296 297 // 整个连接池的最大连接数 298 String maxTotal = System.getProperty(Constants.SYSTEM_PROPERTY_KEY_HTTP_MAX_TOTAL); 299 if (Objects.nonNull(maxTotal)) { 300 connectionManager.setMaxTotal(Integer.valueOf(maxTotal)); 301 } 302 303 // 每个路由的最大连接数 304 String maxPerRoute = System.getProperty(Constants.SYSTEM_PROPERTY_KEY_HTTP_MAX_PER_ROUTE); 305 if (Objects.nonNull(maxPerRoute)) { 306 connectionManager.setDefaultMaxPerRoute(Integer.valueOf(maxPerRoute)); 307 } 308 309 logger.info("[ZZARCH_COMMON_SUCCESS_initPoolingHttpClientConnectionManager]maxTotal={},maxPerRoute={}", maxTotal, maxPerRoute); 310 return connectionManager; 311 } 312 313 314 /** 315 * 获取 HTTPClient注册器 316 * 317 * @return 318 * @throws Exception 319 */ 320 private static Registry<ConnectionSocketFactory> getRegistry() { 321 try { 322 return RegistryBuilder.<ConnectionSocketFactory>create().register("http", new PlainConnectionSocketFactory()).register("https", new SSLConnectionSocketFactory(SSLContext.getDefault())).build(); 323 } catch (Exception e) { 324 logger.error("[ERROR_getRegistry]", e); 325 } 326 327 return null; 328 } 329 330 }
end