Java

一、介绍

在工作中需要在后台调用各种上传、下载、以及第三方服务接口,经过研究决定使用 HttpClient,封装了一个 HttpClient 工具类,简单轻松的实现get、post、put、delete 以及上传、下载请求,在此分享给大家。

二、实践应用

基于 HttpClient4.5.5 版本进行开发,也是现在最新的版本,之所以要提供版本说明,是因为 HttpClient 3 版本和 HttpClient 4 版本 API 差别还是很多大的,把 HttpClient 3 版本的代码拿到 HttpClient 4 上面运行不起来,会报错的。所以在使用之前,一定要注意 HtppClient 的版本问题。

2.1、引用 HttpClient 依赖包

  1. <dependency>
  2. <groupId>org.apache.httpcomponents</groupId>
  3. <artifactId>httpclient</artifactId>
  4. <version>4.5.6</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.apache.httpcomponents</groupId>
  8. <artifactId>httpcore</artifactId>
  9. <version>4.4.10</version>
  10. </dependency>
  11. <dependency>
  12. <groupId>org.apache.httpcomponents</groupId>
  13. <artifactId>httpmime</artifactId>
  14. <version>4.5.6</version>
  15. </dependency>
  16. <dependency>
  17. <groupId>com.alibaba</groupId>
  18. <artifactId>fastjson</artifactId>
  19. <version>1.2.68</version>
  20. </dependency>

2.2、编写工具类(重点)

本次采用单利模式来初始化客户端,并用线程池来管理,同时支持http和https协议,项目启动之后,无需手动关闭httpClient客户端

  1. import com.alibaba.fastjson.JSONObject;
  2. import org.apache.commons.lang3.StringUtils;
  3. import org.apache.commons.lang3.exception.ExceptionUtils;
  4. import org.apache.http.HttpEntity;
  5. import org.apache.http.HttpEntityEnclosingRequest;
  6. import org.apache.http.HttpStatus;
  7. import org.apache.http.client.config.RequestConfig;
  8. import org.apache.http.client.methods.*;
  9. import org.apache.http.client.utils.HttpClientUtils;
  10. import org.apache.http.config.Registry;
  11. import org.apache.http.config.RegistryBuilder;
  12. import org.apache.http.conn.socket.ConnectionSocketFactory;
  13. import org.apache.http.conn.socket.PlainConnectionSocketFactory;
  14. import org.apache.http.conn.ssl.NoopHostnameVerifier;
  15. import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
  16. import org.apache.http.conn.ssl.TrustStrategy;
  17. import org.apache.http.entity.ContentType;
  18. import org.apache.http.entity.StringEntity;
  19. import org.apache.http.entity.mime.HttpMultipartMode;
  20. import org.apache.http.entity.mime.MultipartEntityBuilder;
  21. import org.apache.http.entity.mime.content.StringBody;
  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.ssl.SSLContexts;
  26. import org.apache.http.util.EntityUtils;
  27. import org.slf4j.Logger;
  28. import org.slf4j.LoggerFactory;
  29. import javax.net.ssl.SSLContext;
  30. import java.io.ByteArrayOutputStream;
  31. import java.io.IOException;
  32. import java.io.InputStream;
  33. import java.nio.charset.Charset;
  34. import java.util.Map;
  35. import java.util.Objects;
  36. public class HttpUtils {
  37. private static final Logger log = LoggerFactory.getLogger(HttpUtils.class);
  38. private HttpUtils() {}
  39. //多线程共享实例
  40. private static CloseableHttpClient httpClient;
  41. static {
  42. SSLContext sslContext = createSSLContext();
  43. SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);
  44. // 注册http套接字工厂和https套接字工厂
  45. Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory> create()
  46. .register("http", PlainConnectionSocketFactory.INSTANCE)
  47. .register("https", sslsf)
  48. .build();
  49. // 连接池管理器
  50. PoolingHttpClientConnectionManager connMgr = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
  51. connMgr.setMaxTotal(300);//连接池最大连接数
  52. connMgr.setDefaultMaxPerRoute(300);//每个路由最大连接数,设置的过小,无法支持大并发
  53. connMgr.setValidateAfterInactivity(5 * 1000); //在从连接池获取连接时,连接不活跃多长时间后需要进行一次验证
  54. // 请求参数配置管理器
  55. RequestConfig requestConfig = RequestConfig.custom()
  56. .setConnectTimeout(60000)
  57. .setSocketTimeout(60000)
  58. .setConnectionRequestTimeout(60000)
  59. .build();
  60. // 获取httpClient客户端
  61. httpClient = HttpClients.custom()
  62. .setConnectionManager(connMgr)
  63. .setDefaultRequestConfig(requestConfig)
  64. .build();
  65. }
  66. /**
  67. * GET请求
  68. * @param url
  69. * @return
  70. */
  71. public static String getUrl(String url) {
  72. return sendHttp(HttpMethod.GET, url, null, null);
  73. }
  74. /**
  75. * GET请求/带头部的信息
  76. * @param url
  77. * @param header
  78. * @return
  79. */
  80. public static String getUrl(String url, Map<String, String> header) {
  81. return sendHttp(HttpMethod.GET, url, header, null);
  82. }
  83. /**
  84. * POST请求/无参数
  85. * @param url
  86. * @return
  87. */
  88. public static String postJson(String url) {
  89. return postJson(url, null, null);
  90. }
  91. /**
  92. * POST请求/有参数
  93. * @param url
  94. * @param param
  95. * @return
  96. */
  97. public static String postJson(String url, String param) {
  98. return postJson(url, null, param);
  99. }
  100. /**
  101. * POST请求/无参数带头部
  102. * @param url
  103. * @param header
  104. * @return
  105. */
  106. public static String postJson(String url, Map<String, String> header) {
  107. return postJson(url, header, null);
  108. }
  109. /**
  110. * POST请求/有参数带头部
  111. * @param url
  112. * @param header
  113. * @param params
  114. * @return
  115. */
  116. public static String postJson(String url, Map<String, String> header, String params) {
  117. return sendHttp(HttpMethod.POST, url, header, params);
  118. }
  119. /**
  120. * 上传文件流
  121. * @param url
  122. * @param header
  123. * @param param
  124. * @param fileName
  125. * @param inputStream
  126. * @return
  127. */
  128. public static RequestResult postUploadFileStream(String url, Map<String, String> header, Map<String, String> param, String fileName, InputStream inputStream) {
  129. byte[] stream = inputStream2byte(inputStream);
  130. return postUploadFileStream(url, header, param, fileName, stream);
  131. }
  132. /**
  133. * 上传文件
  134. * @param url 上传地址
  135. * @param header 请求头部
  136. * @param param 请求表单
  137. * @param fileName 文件名称
  138. * @param stream 文件流
  139. * @return
  140. */
  141. public static RequestResult postUploadFileStream(String url, Map<String, String> header, Map<String, String> param, String fileName, byte[] stream) {
  142. String infoMessage = new StringBuilder().append("request postUploadFileStream,url:").append(url).append(",header:").append(header.toString()).append(",param:").append(JSONObject.toJSONString(param)).append(",fileName:").append(fileName).toString();
  143. log.info(infoMessage);
  144. RequestResult result = new RequestResult();
  145. if(StringUtils.isBlank(fileName)){
  146. log.warn("上传文件名称为空");
  147. throw new RuntimeException("上传文件名称为空");
  148. }
  149. if(Objects.isNull(stream)){
  150. log.warn("上传文件流为空");
  151. throw new RuntimeException("上传文件流为空");
  152. }
  153. CloseableHttpResponse response = null;
  154. try {
  155. ContentType contentType = ContentType.MULTIPART_FORM_DATA.withCharset("UTF-8");
  156. HttpPost httpPost = new HttpPost(url);
  157. if (Objects.nonNull(header) && !header.isEmpty()) {
  158. for (Map.Entry<String, String> entry : header.entrySet()) {
  159. httpPost.setHeader(entry.getKey(), entry.getValue());
  160. if(log.isDebugEnabled()){
  161. log.debug(entry.getKey() + ":" + entry.getValue());
  162. }
  163. }
  164. }
  165. MultipartEntityBuilder builder = MultipartEntityBuilder.create();
  166. builder.setCharset(Charset.forName("UTF-8"));//使用UTF-8
  167. builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);//设置浏览器兼容模式
  168. if (Objects.nonNull(param) && !param.isEmpty()) {
  169. for (Map.Entry<String, String> entry : param.entrySet()) {
  170. if (log.isDebugEnabled()) {
  171. log.debug(entry.getKey() + ":" + entry.getValue());
  172. }
  173. builder.addPart(entry.getKey(), new StringBody(entry.getValue(), contentType));
  174. }
  175. }
  176. builder.addBinaryBody("file", stream, contentType, fileName);//封装上传文件
  177. httpPost.setEntity(builder.build());
  178. //请求执行,获取返回对象
  179. response = httpClient.execute(httpPost);
  180. log.info("postUploadFileStream response status:{}", response.getStatusLine());
  181. int statusCode = response.getStatusLine().getStatusCode();
  182. if (statusCode == HttpStatus.SC_OK || statusCode == HttpStatus.SC_NO_CONTENT) {
  183. result.setSuccess(true);
  184. }
  185. HttpEntity httpEntity = response.getEntity();
  186. if (Objects.nonNull(httpEntity)) {
  187. String content = EntityUtils.toString(httpEntity, "UTF-8");
  188. log.info("postUploadFileStream response body:{}", content);
  189. result.setMsg(content);
  190. }
  191. } catch (Exception e) {
  192. log.error(infoMessage + " failure", e);
  193. result.setMsg("请求异常");
  194. } finally {
  195. HttpClientUtils.closeQuietly(response);
  196. }
  197. return result;
  198. }
  199. /**
  200. * 从下载地址获取文件流(如果链接出现双斜杠,请用OKHttp)
  201. * @param url
  202. * @return
  203. */
  204. public static ByteArrayOutputStream getDownloadFileStream(String url) {
  205. String infoMessage = new StringBuilder().append("request getDownloadFileStream,url:").append(url).toString();
  206. log.info(infoMessage);
  207. ByteArrayOutputStream byteOutStream = null;
  208. CloseableHttpResponse response = null;
  209. try {
  210. response = httpClient.execute(new HttpGet(url));
  211. log.info("getDownloadFileStream response status:{}", response.getStatusLine());
  212. int statusCode = response.getStatusLine().getStatusCode();
  213. if (statusCode == HttpStatus.SC_OK) {
  214. //请求成功
  215. HttpEntity entity = response.getEntity();
  216. if (entity != null && entity.getContent() != null) {
  217. //复制输入流
  218. byteOutStream = cloneInputStream(entity.getContent());
  219. }
  220. }
  221. } catch (Exception e) {
  222. log.error(infoMessage + " failure", e);
  223. } finally {
  224. HttpClientUtils.closeQuietly(response);
  225. }
  226. return byteOutStream;
  227. }
  228. /**
  229. * 表单请求
  230. * @param url 地址
  231. * @param header 请求头部
  232. * @param param 请求表单
  233. * @return
  234. */
  235. public static RequestResult sendPostForm(String url, Map<String, String> header, Map<String, String> param) {
  236. String infoMessage = new StringBuilder().append("request postForm,url:").append(url).append(",header:").append(JacksonUtils.toJson(header)).append(",param:").append(JacksonUtils.toJson(param)).toString();
  237. if(log.isDebugEnabled()){
  238. log.debug(infoMessage);
  239. }
  240. RequestResult result = new RequestResult();
  241. CloseableHttpResponse response = null;
  242. try {
  243. HttpPost httpPost = new HttpPost(url);
  244. httpPost.addHeader("Content-type", "application/x-www-form-urlencoded");
  245. if (Objects.nonNull(header) && !header.isEmpty()) {
  246. for (Map.Entry<String, String> entry : header.entrySet()) {
  247. httpPost.setHeader(entry.getKey(), entry.getValue());
  248. if(log.isDebugEnabled()){
  249. log.debug(entry.getKey() + ":" + entry.getValue());
  250. }
  251. }
  252. }
  253. List<NameValuePair> nameValuePairs = new ArrayList<>();
  254. if (Objects.nonNull(param) && !param.isEmpty()) {
  255. for (Map.Entry<String, String> entry : param.entrySet()) {
  256. if (log.isDebugEnabled()) {
  257. log.debug(entry.getKey() + ":" + entry.getValue());
  258. }
  259. nameValuePairs.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
  260. }
  261. }
  262. httpPost.setEntity(new UrlEncodedFormEntity(nameValuePairs, Charset.forName("UTF-8")));
  263. //请求执行,获取返回对象
  264. response = httpClient.execute(httpPost);
  265. if(log.isDebugEnabled()){
  266. log.debug("postForm response status:{}", response.getStatusLine());
  267. }
  268. int statusCode = response.getStatusLine().getStatusCode();
  269. if (statusCode == HttpStatus.SC_OK || statusCode == HttpStatus.SC_NO_CONTENT) {
  270. result.setSuccess(true);
  271. }
  272. HttpEntity httpEntity = response.getEntity();
  273. if (Objects.nonNull(httpEntity)) {
  274. String content = EntityUtils.toString(httpEntity, "UTF-8");
  275. if(log.isDebugEnabled()){
  276. log.debug("postForm response body:{}", content);
  277. }
  278. result.setMsg(content);
  279. }
  280. } catch (Exception e) {
  281. log.error(infoMessage + " failure", e);
  282. result.setMsg("请求异常");
  283. } finally {
  284. HttpClientUtils.closeQuietly(response);
  285. }
  286. return result;
  287. }
  288. /**
  289. * 发送http请求(通用方法)
  290. * @param httpMethod 请求方式(GET、POST、PUT、DELETE)
  291. * @param url 请求路径
  292. * @param header 请求头
  293. * @param params 请求body(json数据)
  294. * @return 响应文本
  295. */
  296. public static String sendHttp(HttpMethod httpMethod, String url, Map<String, String> header, String params) {
  297. String infoMessage = new StringBuilder().append("request sendHttp,url:").append(url).append(",method:").append(httpMethod.name()).append(",header:").append(JSONObject.toJSONString(header)).append(",param:").append(params).toString();
  298. log.info(infoMessage);
  299. //返回结果
  300. String result = null;
  301. CloseableHttpResponse response = null;
  302. long beginTime = System.currentTimeMillis();
  303. try {
  304. ContentType contentType = ContentType.APPLICATION_JSON.withCharset("UTF-8");
  305. HttpRequestBase request = buildHttpMethod(httpMethod, url);
  306. if (Objects.nonNull(header) && !header.isEmpty()) {
  307. for (Map.Entry<String, String> entry : header.entrySet()) {
  308. //打印头部信息
  309. if(log.isDebugEnabled()){
  310. log.debug(entry.getKey() + ":" + entry.getValue());
  311. }
  312. request.setHeader(entry.getKey(), entry.getValue());
  313. }
  314. }
  315. if (StringUtils.isNotEmpty(params)) {
  316. if(HttpMethod.POST.equals(httpMethod) || HttpMethod.PUT.equals(httpMethod)){
  317. ((HttpEntityEnclosingRequest) request).setEntity(new StringEntity(params, contentType));
  318. }
  319. }
  320. response = httpClient.execute(request);
  321. HttpEntity httpEntity = response.getEntity();
  322. log.info("sendHttp response status:{}", response.getStatusLine());
  323. if (Objects.nonNull(httpEntity)) {
  324. result = EntityUtils.toString(httpEntity, "UTF-8");
  325. log.info("sendHttp response body:{}", result);
  326. }
  327. } catch (Exception e) {
  328. log.error(infoMessage + " failure", e);
  329. } finally {
  330. HttpClientUtils.closeQuietly(response);//关闭返回对象
  331. }
  332. long endTime = System.currentTimeMillis();
  333. log.info("request sendHttp response time cost:" + (endTime - beginTime) + " ms");
  334. return result;
  335. }
  336. /**
  337. * 请求方法(全大些)
  338. */
  339. public enum HttpMethod {
  340. GET, POST, PUT, DELETE
  341. }
  342. /**
  343. * 上传返回结果封装
  344. */
  345. public static class RequestResult{
  346. private boolean isSuccess;//是否成功
  347. private String msg;//消息
  348. public boolean isSuccess() {
  349. return isSuccess;
  350. }
  351. public RequestResult setSuccess(boolean success) {
  352. isSuccess = success;
  353. return this;
  354. }
  355. public String getMsg() {
  356. return msg;
  357. }
  358. public RequestResult setMsg(String msg) {
  359. this.msg = msg;
  360. return this;
  361. }
  362. public RequestResult() {
  363. this.isSuccess = false;
  364. }
  365. }
  366. /**
  367. * 构建请求方法
  368. * @param method
  369. * @param url
  370. * @return
  371. */
  372. private static HttpRequestBase buildHttpMethod(HttpMethod method, String url) {
  373. if (HttpMethod.GET.equals(method)) {
  374. return new HttpGet(url);
  375. } else if (HttpMethod.POST.equals(method)) {
  376. return new HttpPost(url);
  377. } else if (HttpMethod.PUT.equals(method)) {
  378. return new HttpPut(url);
  379. } else if (HttpMethod.DELETE.equals(method)) {
  380. return new HttpDelete(url);
  381. } else {
  382. return null;
  383. }
  384. }
  385. /**
  386. * 配置证书
  387. * @return
  388. */
  389. private static SSLContext createSSLContext(){
  390. try {
  391. //信任所有,支持导入ssl证书
  392. TrustStrategy acceptingTrustStrategy = (cert, authType) -> true;
  393. SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(null, acceptingTrustStrategy).build();
  394. return sslContext;
  395. } catch (Exception e) {
  396. log.error("初始化ssl配置失败", e);
  397. throw new RuntimeException("初始化ssl配置失败");
  398. }
  399. }
  400. /**
  401. * 复制文件流
  402. * @param input
  403. * @return
  404. */
  405. private static ByteArrayOutputStream cloneInputStream(InputStream input) {
  406. try {
  407. ByteArrayOutputStream byteOutStream = new ByteArrayOutputStream();
  408. byte[] buffer = new byte[1024];
  409. int len;
  410. while ((len = input.read(buffer)) > -1) {
  411. byteOutStream.write(buffer, 0, len);
  412. }
  413. byteOutStream.flush();
  414. return byteOutStream;
  415. } catch (IOException e) {
  416. log.warn("copy InputStream error,{}", ExceptionUtils.getStackTrace(e));
  417. }
  418. return null;
  419. }
  420. /**
  421. * 输入流转字节流
  422. * @param in
  423. * @return
  424. */
  425. private static byte[] inputStream2byte(InputStream in) {
  426. ByteArrayOutputStream bos = null;
  427. try {
  428. bos = new ByteArrayOutputStream();
  429. byte[] b = new byte[1024];
  430. int n;
  431. while ((n = in.read(b)) != -1) {
  432. bos.write(b, 0, n);
  433. }
  434. in.close();
  435. bos.close();
  436. byte[] buffer = bos.toByteArray();
  437. return buffer;
  438. } catch (IOException e) {
  439. log.warn("inputStream transfer byte error,{}", ExceptionUtils.getStackTrace(e));
  440. } finally {
  441. try {
  442. if (in != null) {
  443. in.close();
  444. }
  445. } catch (IOException e) {
  446. log.error("clone inputStream error", e);
  447. }
  448. try {
  449. if (bos != null) {
  450. bos.close();
  451. }
  452. } catch (IOException e) {
  453. log.error("clone outputStream error", e);
  454. }
  455. }
  456. return null;
  457. }
  458. public static void main(String[] args) {
  459. String url = "https://101.231.204.80:5000/gateway/api/queryTrans.do";
  460. String result = postJson(url);
  461. System.out.println(result);
  462. }
  463. }

除了上传、下载请求之外,默认封装的请求参数格式都是application/json,如果不够,可以根据自己的业务场景进行封装处理!
其中sendHttp是一个支持GET、POST、PUT、DELETE请求的通用方法,上面介绍的getUrlpostJosn等方法,最终都会调用到这个方法!

2.3、接口请求示例

工具包封装完成之后,在代码中使用起来也非常简单,直接采用工具类方法就可以直接使用,例如下面以post方式请求某个接口!

  1. public static void main(String[] args) throws Exception {
  2. String url = "https://101.231.204.80:5000/gateway/api/queryTrans.do";
  3. String result= HttpUtils.postJson(url);
  4. System.out.println(result);
  5. }

三、小结

在编写工具类的时候,需要注意的地方是,尽可能保证httpClient客户端全局唯一,也就是采用单利模式,如果每次请求都初始化一个客户端,结束之后又将其关闭,在高并发的接口请求场景下,性能效率急剧下降!
HttpClients客户端的初始化参数配置非常丰富,这里默认初始化的线程池为300,在实际的业务开发中,大家还可以结合自己的业务场景进行调优,具体的配置可以参考官网文档,地址:Apache HttpComponents