Gitee仓库
https://gitee.com/Lin_DH/system
介绍
Http 协议是 Internet 上使用的最多、最重要的协议之一,越来越多的 Java 应用程序需要直接通过 Http 协议来访问网络资源。虽然在 JDK 的 java net 包中已经提供了访问 Http 协议的基本功能,但是对于大部分应用程序来说,JDK 库本身提供的功能还不够丰富和灵活。HttpClient 是 Apache Jakarta Common 下的子项目,用来提供高效的、最新的、功能丰富的支持 Http 协议的客户端编程工具包,并且其支持 Http 协议最新的版本和建议。HttpClient 已经应用在很多的项目当中,比如 Apache Jakarta 上很著名的另外两个开源项目 Cactus 和 HTML Unit 都使用了 HttpClient。Commons HttpClient 项目现已终止,不在开发。其已被 Apache HttpComponents 项目里的 HttpClient 和 HttpCore 模块取代,它们都提供了更好的灵活性。
功能
1)实现了所有 Http 的方法(GET,POST,PUT,HEAD 等)
2)支持自动转向
3)支持 HTTPS 协议
4)支持代理服务器等
核心API
HttpClient:Http 客户端对象,使用该类型对象可发起 Http 请求
HttpClients:构建器,用于获取 HttpClient 对象
CloseableHttpClient:具体实现类,实现了 HttpClient 接口
HttpGet:Get 方式请求类型
HttpPost:Post方式请求类型
功能实现(传统HttpClient)
注:这是 HttpClient 传统的写法,封装好的 HttpUtil 在后文中。
依赖
pom.xml
<dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpclient</artifactId><version>4.5.10</version></dependency>
数据接口
HelloController.java
package com.lm.system.controller;import io.swagger.annotations.Api;
import org.springframework.web.bind.annotation.*;import java.util.Map;/*** @Author: DuHaoLin* @Date: 2024/7/26*/@RestController
public class HelloController {@GetMapping("hello")public String hello(@RequestParam("name") String name) {return "Hello " + name;}@PostMapping("postHello")public String postHello(@RequestBody Map<String, String> params) {return "postHello " + params;}}
GET
实现步骤
使用 HttpClient 的 GET 方法需要以下六个步骤:
1)创建 HttpClient 实例
2)创建连接方法的实例,在 GetMethod 的构造函数中传入连接的地址
3)调用实例的 execute 方法来执行 GetMethod 实例
4)读取 response
5)释放连接,无论成功与否都需要释放连接
6)对得到后的内容进行处理
代码实现
HttpClientTest.java
package com.lm.system;import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.junit.Test;import java.io.IOException;/*** @author DUHAOLIN* @date 2024/12/2*/
public class HttpClientTest {private static final String GET_URL = "http://localhost:8888/hello";@Testpublic void getTest() {System.out.println("Get Method Result:" + doGet());}public String doGet() {//1.创建一个默认的实例CloseableHttpClient client = HttpClients.createDefault();String result = null;try {//2.创建一个HttpGet对象HttpGet get = new HttpGet(GET_URL + "?name=Joe");//3.执行GET请求并获取响应对象CloseableHttpResponse response = client.execute(get);try {//4.获取响应体HttpEntity entity = response.getEntity();//5.打印响应状态System.out.println("status code:" + response.getStatusLine());//6.打印响应长度和响应内容if (null != entity) {result = EntityUtils.toString(entity);}} finally {//7.无论请求成功与否都要关闭respresponse.close();}} catch (IOException e) {e.printStackTrace();} finally {//8.最终要关闭连接,释放资源try {client.close();} catch (Exception e) {e.printStackTrace();}}return result;}}
效果图
POST
实现步骤
使用 HttpClient 的 POST 方法需要以下六个步骤:
1)创建 HttpClient 实例
2)创建连接方法的实例,在 PostMethod 的构造函数中传入连接的地址和构建好的请求参数
3)调用实例的 execute 方法来执行 PostMethod 实例
4)读取 response
5)释放连接,无论成功与否都需要释放连接
6)对得到后的内容进行处理
代码实现
HttpClientTest.java
package com.lm.system;import com.alibaba.fastjson.JSONObject;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.junit.Test;import java.io.IOException;/*** @author DUHAOLIN* @date 2024/12/2*/
public class HttpClientTest {private static final String POST_URL = "http://localhost:8888/postHello";@Testpublic void postTest() {System.out.println("Post Method Result:" + doPost());}public String doPost() {//1.创建一个默认的实例CloseableHttpClient httpClient = HttpClients.createDefault();String result = null;try {//2.创建一个HttpPost对象HttpPost post = new HttpPost(POST_URL);JSONObject jsonObject = new JSONObject();jsonObject.put("name", "Tom");StringEntity entity = new StringEntity(jsonObject.toString());//3.设置编码、数据格式、请求参数entity.setContentEncoding("utf-8");entity.setContentType("application/json");post.setEntity(entity);//4.发送请求CloseableHttpResponse response = httpClient.execute(post);try {//5.解析处理结果System.out.println("status code:" + response.getStatusLine().getStatusCode());result = EntityUtils.toString(response.getEntity());} finally {//6.关闭连接,释放资源response.close();}} catch (IOException e) {e.printStackTrace();} finally {try {httpClient.close();} catch (Exception e) {e.printStackTrace();}}return result;}}
效果图
问题
HttpClient 已经可以使用了,为什么还要封装成工具类呢?
Java HttpClient 封装成工具类的主要原因是为了提高代码的复用性,减少代码冗余。
在 Java 开发中,HttpClient 是一个常用的工具类库,用于发送 HTTP 请求。虽然 Java 原生提供了 java.net 包来处理网络请求,但其功能相对有限,不够丰富和灵活。因此, HttpClient 库(如 Apache HttpClient)被广泛使用,其提供了更加丰富的功能和更好的性能。然而,直接使用 HttpClient 库可能会在项目中重复编写大量相似的代码,导致代码冗余和维护困难。
封装成工具类的优点
1)提高代码复用性:通过封装可以将 HttpClient 封装成一个工具类,在项目中可以直接调用该工具类中封装好的方法,而不需要重复编写相同的代码。
2)减少代码冗余:封装后的工具类可以统一管理 HttpClient 的使用,避免在不同地方重复编写相似代码,减少代码冗余。
3)便于维护和扩展:封装后的工具类使得代码更加模块化,便于后续的维护和功能扩展。如需要修改 HttpClient 的使用方式或添加性能,只需要修改工具类即可。
- 支持多种请求方式:封装后的工具类可以自定义提供 GET、POST、PUT、DELETE 等多种 HTTP 请求方式。
- 添加自定义头信息:可以在发送请求时添加自定义的头信息,满足不同的业务需求。
- 处理响应内容:可以在处理响应内容,如解析 JSON、处理 Cookie 等。
HTTP工具类
代码实现
HttpClient.java
package com.lm.system.util;import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpRequest;
import org.apache.http.NoHttpResponseException;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.config.SocketConfig;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;import javax.net.ssl.*;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.TimerTask;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;/*** @author DUHAOLIN* @date 2024/12/2*/
@Slf4j
public class HttpClient {//客户端从服务端读取数据的超时时间private static final int HTTP_TIMEOUT = 5000;//空闲的连接超时时间private static final int IDLE_TIMEOUT = 5000;//整个连接池连接的最大值private static final int HTTP_MAX_TOTAL = 10000;//客户端与服务器建立连接的超时时间private static final int HTTP_CON_TIMEOUT = 2000;//路由的默认最大连接private static final int HTTP_MAX_PERROUTE = 5000;//任务前一次执行结束到下一次执行开始的间隔时间(间隔执行延迟时间)private static final int TASK_DELAY = 5000;//任务初始化延时private static final int TASK_INITIAL_DELAY = 5000;//客户端从连接池中获取连接的超时时间private static final int HTTP_CON_REQ_TIMEOUT = 1000;private static RequestConfig defaultRequestConfig = null;private static HttpRequestRetryHandler retryHandler = null;private static CloseableHttpClient defaultHttpClient = null;private static ScheduledExecutorService monitorExecutor = null;private static PoolingHttpClientConnectionManager connManager = null;private static final HttpClient httpClient = new HttpClient();public static HttpClient getInstance() {return httpClient;}private HttpClient() {//创建SSLConnectionSocketFactorySSLConnectionSocketFactory factory = getSSLConnectionSocketFactory();//创建连接池管理器connManager = createPoolConnectManager(factory);//设置Socket配置setSocketConfig();//设置获取连接超时时间,建立连接超时时间,从服务端读取数据的超时时间defaultRequestConfig = getRequestConfig();//请求失败时,进行请求重试retryHandler = retryHandler();//创建HttpClient实例defaultHttpClient = createHttpClient(factory);//开启线程监控,对异常和空闲线程进行关闭monitorExecutor = startUpThreadMonitor();}public CloseableHttpClient getHttpClient() {return defaultHttpClient;}/*** 关闭连接池*/public static void closeConnPool(){try {defaultHttpClient.close();connManager.close();monitorExecutor.shutdown();log.info("Close the thread pool");} catch (IOException e) {e.printStackTrace();log.error("Closing the thread pool failed", e);}}public RequestConfig getDefaultRequestConfig() {return defaultRequestConfig;}private SSLConnectionSocketFactory getSSLConnectionSocketFactory() {X509TrustManager manager = new X509TrustManager() {@Overridepublic void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {}@Overridepublic void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {}@Overridepublic X509Certificate[] getAcceptedIssuers() {return null;}};SSLContext context = null;try {context = SSLContext.getInstance("TLS");//初始化上下文context.init(null, new TrustManager[] { manager }, null);} catch (NoSuchAlgorithmException | KeyManagementException e) {e.printStackTrace();}assert context != null;return new SSLConnectionSocketFactory(context, NoopHostnameVerifier.INSTANCE);}private PoolingHttpClientConnectionManager createPoolConnectManager(SSLConnectionSocketFactory factory) {RegistryBuilder<ConnectionSocketFactory> registryBuilder = RegistryBuilder.create();Registry<ConnectionSocketFactory> registry = registryBuilder.register("http", PlainConnectionSocketFactory.getSocketFactory()).register("https", factory).build();return new PoolingHttpClientConnectionManager(registry);}private void setSocketConfig() {SocketConfig socketConfig = SocketConfig.custom().setTcpNoDelay(true).build();connManager.setDefaultSocketConfig(socketConfig);connManager.setMaxTotal(HTTP_MAX_TOTAL);connManager.setDefaultMaxPerRoute(HTTP_MAX_PERROUTE);}private RequestConfig getRequestConfig () {return RequestConfig.custom().setSocketTimeout(HTTP_TIMEOUT).setConnectTimeout(HTTP_CON_TIMEOUT).setConnectionRequestTimeout(HTTP_CON_REQ_TIMEOUT).build();}private HttpRequestRetryHandler retryHandler() {return (e, executionCount, httpContext) -> {//重试超过3次,放弃请求if (executionCount > 3) {log.error("retry has more than 3 time, give up request");return false;}//服务器没有响应,可能是服务器断开了连接,应该重试if (e instanceof NoHttpResponseException) {log.error("receive no response from server, retry");return true;}// SSL握手异常if (e instanceof SSLHandshakeException){log.error("SSL hand shake exception");return false;}//超时if (e instanceof InterruptedIOException){log.error("InterruptedIOException");return false;}// 服务器不可达if (e instanceof UnknownHostException){log.error("server host unknown");return false;}if (e instanceof SSLException){log.error("SSLException");return false;}HttpClientContext context = HttpClientContext.adapt(httpContext);HttpRequest request = context.getRequest();//如果请求不是关闭连接的请求return !(request instanceof HttpEntityEnclosingRequest);};}private CloseableHttpClient createHttpClient(SSLConnectionSocketFactory factory) {CloseableHttpClient httpClient = HttpClients.custom()
// .setRetryHandler(retryHandler).setConnectionManager(connManager).setDefaultRequestConfig(defaultRequestConfig)
// .setSSLSocketFactory(factory).build();log.info("HttpClient Build");return httpClient;}private ScheduledExecutorService startUpThreadMonitor() {ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);executor.scheduleAtFixedRate(new TimerTask() {@Overridepublic void run() {try {//关闭异常连接connManager.closeExpiredConnections();//关闭5s空闲的连接connManager.closeIdleConnections(IDLE_TIMEOUT, TimeUnit.MILLISECONDS);log.debug("close expired and idle for over IDLE_TIMEOUT connection");} catch (Exception e) {log.error("close expired or idle for over IDLE_TIMEOUT connection fail", e);}}}, TASK_INITIAL_DELAY, TASK_DELAY, TimeUnit.MICROSECONDS);return executor;}}
HttpUtil.java
package com.lm.system.util;import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;
import org.springframework.util.StringUtils;import javax.net.ssl.SSLContext;
import java.io.FileInputStream;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;/*** @author DUHAOLIN* @date 2024/12/2*/
public class HttpUtil {private static final String JSON_FORMAT = "application/json";private static final String UTF8_CHARSET = "utf-8";private static final RequestConfig DEFAULT_REQUEST_CONFIG = HttpClient.getInstance().getDefaultRequestConfig();public static HttpEntity doGet(String url) {return send(url, HttpClient.getInstance());}private static HttpEntity send(String url, HttpClient httpClient) {HttpGet get = new HttpGet(url);try {get.setConfig(DEFAULT_REQUEST_CONFIG);HttpResponse response = httpClient.getHttpClient().execute(get);return response.getEntity();} catch (Exception e) {throw new RuntimeException(e);}}public static String doHttpPost(String url) {return sendData(url, createSSLInsecureClient(), UTF8_CHARSET);}public static String doHttpPost(String url, String data) {return sendData(url, data, createSSLInsecureClient(), UTF8_CHARSET);}public static String doHttpPost(String url, String data, String encoding) {return sendData(url, data, createSSLInsecureClient(), encoding);}public static String doHttpPost(String url, String data, String encoding, String contentType) {return sendData(url, data, createSSLInsecureClient(), encoding, contentType);}public static String doHttpsPost(String data, String url, String certAddress, String mchId, String TLSVersion, String encoding) {return sendData(url, data, getCAHttpClient(mchId, certAddress, TLSVersion), encoding);}private static String sendData(String url, String data, CloseableHttpClient httpClient, String encoding) {return sendData(url, data, httpClient, encoding, JSON_FORMAT);}private static CloseableHttpClient getCAHttpClient(String mchId, String certAddress, String TLSVersion) {if (!StringUtils.hasText(TLSVersion)) {TLSVersion = "TLSv1";}CloseableHttpClient httpClient = null;try {KeyStore keyStore = KeyStore.getInstance("PKCS12");try (FileInputStream inputStream = new FileInputStream(certAddress)) {keyStore.load(inputStream, mchId.toCharArray());}SSLContext context = SSLContexts.custom().loadKeyMaterial(keyStore, mchId.toCharArray()).build();SSLConnectionSocketFactory factory = new SSLConnectionSocketFactory(context,new String[] { TLSVersion },null,SSLConnectionSocketFactory.getDefaultHostnameVerifier());httpClient = HttpClients.custom().setSSLSocketFactory(factory).build();} catch (Exception e) {e.printStackTrace();}return httpClient;}private static String sendData(String url, String data, CloseableHttpClient httpClient, String encoding, String contentType) {HttpPost post = new HttpPost(url);String result = null;try {post.setConfig(DEFAULT_REQUEST_CONFIG);StringEntity entity = new StringEntity(data, UTF8_CHARSET);entity.setContentEncoding(UTF8_CHARSET);entity.setContentType(contentType);post.setEntity(entity);HttpResponse response = httpClient.execute(post);HttpEntity returnEntity = response.getEntity();result = EntityUtils.toString(returnEntity, encoding);} catch (Exception e) {e.printStackTrace();}return result;}private static CloseableHttpClient createSSLInsecureClient() {try {SSLContext context = new SSLContextBuilder().loadTrustMaterial(null, (x509Certificates, s) -> true).build();SSLConnectionSocketFactory factory = new SSLConnectionSocketFactory(context, new NoopHostnameVerifier());return HttpClients.custom().setSSLSocketFactory(factory).build();} catch (NoSuchAlgorithmException | KeyStoreException | KeyManagementException e) {e.printStackTrace();}return HttpClients.createDefault();}private static String sendData(String url, CloseableHttpClient httpClient, String encoding) {HttpPost post = new HttpPost(url);String result;try {post.setConfig(DEFAULT_REQUEST_CONFIG);HttpResponse response = httpClient.execute(post);HttpEntity entity = response.getEntity();result = EntityUtils.toString(entity, encoding);} catch (Exception e) {e.printStackTrace();throw new RuntimeException("请求异常", e);}return result;}}
HttpTest.java
package com.lm.system;import com.alibaba.fastjson.JSONObject;
import com.lm.system.util.HttpUtil;
import org.apache.http.HttpEntity;
import org.junit.Test;import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.stream.Collectors;/*** @author DUHAOLIN* @date 2024/12/2*/
public class HttpTest {@Testpublic void test01() throws Exception {String url = "http://localhost:8888/hello";String params = "?name=Tom";HttpEntity entity = HttpUtil.doGet(url + params);String result = new BufferedReader(new InputStreamReader(entity.getContent())).lines().collect(Collectors.joining("\n"));System.out.println("result:" + result);}@Testpublic void test02() {String url = "http://localhost:8888/postHello";JSONObject json = new JSONObject();json.put("name", "Alice");String data = HttpUtil.doHttpPost(url, json.toString());System.out.println("data:" + data);}}