【SpringBoot】29 基于HttpClient的Http工具类
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";
@Test
public 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.无论请求成功与否都要关闭resp
response.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";
@Test
public 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() {
//创建SSLConnectionSocketFactory
SSLConnectionSocketFactory 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() {
@Override
public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {}
@Override
public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {}
@Override
public 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() {
@Override
public 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 {
@Test
public 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);
}
@Test
public 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);
}
}