1. 前言
在现代软件开发中,HTTP客户端库是与Web服务交互的关键组件。本文将介绍一个功能丰富的HttpClientUtils工具类,它不仅简化了发送HTTP请求的过程,还提供了绕过HTTPS证书验证的能力,这对于调试和测试环境尤其有用。此工具类基于Apache HttpClient库构建,结合Spring框架的便利性,以及Lombok简化代码的特点,旨在提高开发者的工作效率。
2. 工具类代码
注意:
1. 如果请求体为空,请不要传null,而是传入空字符串"";
功能支持:
1. 支持http和https协议;
2. 支持GET、POST、PUT、DELETE请求;
3. 支持图片下载到本地。
1. 所需依赖:
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.13</version>
</dependency>
2. 代码:
import lombok.extern.slf4j.Slf4j;
import org.apache.http.Header;
import org.apache.http.HeaderIterator;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.*;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
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.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.util.EntityUtils;
import org.springframework.http.HttpHeaders;
import javax.imageio.ImageIO;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.Map;
@Slf4j
public class HttpClientUtils {
private static final PoolingHttpClientConnectionManager connectionManager;
private static final CloseableHttpClient httpClient;
static {
SSLContext sslContext;
try {
// 创建一个信任所有证书的SSLContext
sslContext = createTrustAllSSLContext();
// 创建一个使用该SSLContext的SSLConnectionSocketFactory
SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);
// 构建一个包含SSL连接工厂的Registry
Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", PlainConnectionSocketFactory.getSocketFactory())
.register("https", sslSocketFactory)
.build();
// 创建一个支持HTTPS的PoolingHttpClientConnectionManager,使用上述Registry
connectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
httpClient = HttpClients.custom().setConnectionManager(connectionManager).build();
} catch (NoSuchAlgorithmException | KeyManagementException e) {
throw new RuntimeException("Failed to initialize SSL context", e);
}
}
/**
* 发送HTTP请求到指定URL,并绕过HTTPS证书验证。
*
* @param method HTTP方法(GET、POST、PUT等)
* @param url 请求URL
* @param requestBodyJson 请求体(JSON格式字符串,对于非POST/PUT请求可为空)
* @param headers 可选的额外请求头(键值对)
* @return 响应体(JSON格式字符串),如果请求失败则返回null
* @throws IOException 如果发生网络错误
*/
public static String sendRequest(HttpMethod method, String url, String requestBodyJson, Map<String, String> headers) throws IOException, NoSuchAlgorithmException, KeyManagementException {
try {
HttpUriRequest request = createHttpRequest(method, url, requestBodyJson, headers);
printHttpRequestDetails(request);
try (CloseableHttpResponse response = httpClient.execute(request)) {
return handleResponse(response);
}
} catch (IOException e) {
log.error("An exception occurred while executing the request: {}", e.getMessage());
return null;
}
}
/**
* 发送获取验证码的HTTP请求到指定URL,并绕过HTTPS证书验证。
*
* @param method HTTP方法(GET、POST、PUT等)
* @param url 请求URL
* @param requestBodyJson 请求体(JSON格式字符串,对于非POST/PUT请求可为空)
* @param headers 可选的额外请求头(键值对)
* @param filePath 图片保存路径
* @param fileType 文件类型,如png
* @throws IOException 如果发生网络错误
*/
public static void sendImageRequest(HttpMethod method, String url, String requestBodyJson, Map<String, String> headers, String filePath, String fileType) throws IOException {
try {
HttpUriRequest request = createHttpRequest(method, url, requestBodyJson, headers);
printHttpRequestDetails(request);
try (CloseableHttpResponse response = httpClient.execute(request)) {
downloadImages(response, filePath, fileType);
}
} catch (IOException e) {
log.error("An exception occurred while executing the request: {}", e.getMessage());
}
}
/**
* 处理HTTP响应
*
* @param response CloseableHttpResponse类型的响应对象,用于处理和读取HTTP响应信息
* @return String类型,返回处理后的HTTP响应内容,如果响应状态码不是200,则返回null
* @throws IOException 当读取响应内容时发生I/O错误,此异常会被抛出
*/
private static String handleResponse(CloseableHttpResponse response) throws IOException {
// 获取HTTP响应的状态码
int statusCode = response.getStatusLine().getStatusCode();
// 判断响应状态码是否为200(HTTP OK)
if (statusCode == 200) {
// 如果状态码为200,打印并返回HTTP响应的详细信息
return printHttpResponseDetails(response);
} else {
// 如果状态码不为200,记录错误日志,并返回null
log.error("Error: Request failed with status code " + statusCode);
return null;
}
}
/**
* 打印HTTP请求的详细信息
*
* @param request HTTP请求对象
*/
private static void printHttpRequestDetails(HttpUriRequest request) {
log.debug("\n----------------------------------------------- Request Details -------------------------------------------------------");
// 打印请求方法
log.debug("Method: " + request.getMethod());
// 打印请求URI
log.debug("URI: " + request.getURI());
// 打印请求头
log.debug("Headers:");
HeaderIterator headerIterator = request.headerIterator();
while (headerIterator.hasNext()) {
Header header = headerIterator.nextHeader();
log.debug(header.getName() + ": " + header.getValue());
}
// 如果是POST或PUT请求,打印请求体
if (request instanceof HttpEntityEnclosingRequestBase) {
HttpEntity entity = ((HttpEntityEnclosingRequestBase) request).getEntity();
if (entity != null) {
try {
String requestBody = EntityUtils.toString(entity, StandardCharsets.UTF_8);
log.debug("Request Body: " + requestBody);
} catch (IOException e) {
log.error("Failed to print request body:");
e.printStackTrace();
}
} else {
log.debug("Request Body: (empty)");
}
}
log.debug("\n----------------------------------------------------------------------------------------------------------------\n");
}
/**
* 打印HTTP响应的详细信息
*
* @return
*/
private static String printHttpResponseDetails(HttpResponse response) {
log.debug("\n----------------------------------------------- Response Details -------------------------------------------------------");
// 打印状态行
log.debug("Status Line: " + response.getStatusLine());
// 打印响应头
log.debug("Headers:");
HeaderIterator headerIterator = response.headerIterator();
while (headerIterator.hasNext()) {
Header header = headerIterator.nextHeader();
log.debug(header.getName() + ": " + header.getValue());
}
// 打印响应体
HttpEntity entity = response.getEntity();
if (entity != null) {
try {
String responseBody = EntityUtils.toString(entity, StandardCharsets.UTF_8);
log.debug("Response Body: " + responseBody);
log.debug("\n----------------------------------------------------------------------------------------------------------------\n");
return responseBody;
} catch (IOException e) {
log.error("Failed to print response body:");
log.debug("\n----------------------------------------------------------------------------------------------------------------\n");
e.printStackTrace();
}
} else {
log.debug("Response Body: (empty)");
}
return null;
}
/**
* 下载图片
*
* @param response HttpResponse
* @param filePath 下载的文件路径
* @param fileType 文件类型,如png
*/
private static void downloadImages(HttpResponse response, String filePath, String fileType) {
log.debug("\n----------------------------------------------- Response Details -------------------------------------------------------");
// 打印状态行
log.debug("Status Line: " + response.getStatusLine());
// 打印响应头
log.debug("Headers:");
HeaderIterator headerIterator = response.headerIterator();
while (headerIterator.hasNext()) {
Header header = headerIterator.nextHeader();
log.debug(header.getName() + ": " + header.getValue());
}
// 打印响应体
HttpEntity entity = response.getEntity();
if (entity != null) {
try {
// 获取响应实体内容
byte[] imageBytes = EntityUtils.toByteArray(response.getEntity());
// 创建输入流
InputStream inputStream = new ByteArrayInputStream(imageBytes);
// 将字节数组转换为BufferedImage
BufferedImage image = ImageIO.read(inputStream);
File outputFile = new File(filePath);
// 如果父路径没有则创建
if (!outputFile.getParentFile().exists()) {
outputFile.getParentFile().mkdirs();
}
ImageIO.write(image, fileType, outputFile);
log.debug("图片下载成功");
log.debug("\n----------------------------------------------------------------------------------------------------------------\n");
} catch (IOException e) {
log.error("图片下载失败");
log.debug("\n----------------------------------------------------------------------------------------------------------------\n");
e.printStackTrace();
}
} else {
log.debug("Response Body: (empty)");
}
}
/**
* 创建一个HTTP请求对象
*/
private static HttpUriRequest createHttpRequest(HttpMethod method, String url, String requestBodyJson, Map<String, String> headers) throws IOException {
HttpUriRequest request;
switch (method) {
case GET:
request = new HttpGet(url);
break;
case POST:
request = new HttpPost(url);
((HttpEntityEnclosingRequestBase) request).setEntity(new StringEntity(requestBodyJson, "UTF-8"));
break;
case PUT:
request = new HttpPut(url);
((HttpEntityEnclosingRequestBase) request).setEntity(new StringEntity(requestBodyJson, "UTF-8"));
break;
case DELETE:
request = new HttpDelete(url);
break;
default:
throw new IllegalArgumentException("Unsupported HTTP method: " + method);
}
// 设置默认请求头
request.setHeader(HttpHeaders.ACCEPT, "application/json");
request.setHeader(HttpHeaders.CONTENT_TYPE, "application/json");
// 添加额外请求头(如果有)
if (headers != null) {
for (Map.Entry<String, String> entry : headers.entrySet()) {
request.setHeader(entry.getKey(), entry.getValue());
}
}
return request;
}
/**
* 创建一个信任所有证书的SSLContext
*/
private static SSLContext createTrustAllSSLContext() throws NoSuchAlgorithmException, KeyManagementException {
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[]{new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] x509Certificates, String s) {
}
@Override
public void checkServerTrusted(X509Certificate[] x509Certificates, String s) {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
}}, new SecureRandom());
return sslContext;
}
/**
* HTTP方法枚举
*/
public enum HttpMethod {
GET, POST, PUT, DELETE
}
}
3. 核心功能
3.1 安全连接管理
- 信任所有证书:通过自定义SSLContext,实现了对所有SSL证书的信任,避免了因证书问题导致的请求失败,特别适合内部测试环境。
- 连接池管理:利用PoolingHttpClientConnectionManager来复用连接,提高性能,减少资源消耗。
3.2 发送HTTP请求
- 灵活的HTTP方法支持:支持GET、POST、PUT、DELETE四种基本HTTP方法。
- JSON请求体处理:自动处理JSON格式的请求体,适应RESTful API的普遍需求
- 自定义请求头:允许用户添加额外的HTTP头部信息,满足特定API调用需求。
- 响应内容处理:不仅能获取JSON格式的响应体,还能下载图片资源到指定路径。
3.3 日志记录
- 详尽的请求/响应日志:通过printHttpRequestDetails和printHttpResponseDetails方法,全面记录请求和响应的细节,便于调试和监控
3.4 异常处理
- 优雅的错误处理:对执行请求过程中可能发生的异常进行了捕获和日志记录,确保程序的健壮性。
4. 使用示例
4.1 发送普通JSON请求
String url = "https://api.example.com/data";
String jsonBody = "{\"key\":\"value\"}";
Map<String, String> headers = new HashMap<>();
headers.put("Authorization", "Bearer your_token_here");
try {
String response = HttpClientUtils.sendRequest(HttpClientUtils.HttpMethod.GET, url, jsonBody, headers);
System.out.println(response);
} catch (Exception e) {
e.printStackTrace();
}
4.2 下载图片
String imageUrl = "https://example.com/image.png";
String savePath = "/path/to/save/image.png";
try {
HttpClientUtils.sendImageRequest(HttpClientUtils.HttpMethod.GET, imageUrl, "", null, savePath,"png");
} catch (Exception e) {
e.printStackTrace();
}
5. 拓展HTTP方法(HTTP Methods)
1. HttpMethod枚举类中,新增新的HTTP方法(HTTP Methods)
2. createHttpRequest方法中,新增case方法。
6. 总结
HttpClientUtils工具类通过集成了Apache HttpClient的强大功能,结合了简化证书验证、请求响应日志记录、灵活的HTTP方法支持等特性,极大地提升了开发人员在进行HTTP通信时的效率和体验。无论是日常的API调用,还是复杂的数据交互场景,该工具类都能提供稳定可靠的解决方案。在实际应用中,根据具体需求,可以进一步扩展其功能,比如增加请求重试机制、超时配置等,以适应更多样化的应用场景。