一、Apache HttpClient 5介绍
Apache HttpClient 5 是一个功能齐全且高度可定制的 HTTP 客户端库, 其专门用于发送 HTTP 请求、处理 HTTP 响应并支持各种 HTTP 协议特性。特别适合处理复杂的 HTTP 请求需求,如多协议支持、认证、连接池、代理等。它适合中大型项目或需要高级 HTTP 特性的应用开发。
(1)核心特性
- 功能强大:- 同步与异步支持:支持同步和异步的 HTTP 请求处理,异步请求在处理大量并发请求时能够显著提高效率。- 连接池管理:内置的连接池机制能提高并发处理性能,减少资源消耗。- 多种认证机制:支持多种认证方式,包括 Basic、Digest、NTLM、Kerberos 等,能够处理多种安全场景。- 支持Cookie管理:内置 Cookie 管理功能,能够自动处理服务端返回的 Cookie 并在后续请求中使用,模拟浏览器行为。
- 协议支持广泛:- HTTP/1.1 和 HTTP/2 支持:支持现代 HTTP 协议,包括 HTTP/2 的多路复用和流优先级等特性,提升了网络请求效率;特别是 HTTP/2 多路复用的优势。- SSL/TLS 支持:支持 HTTPS,提供自定义 SSL/TLS 配置,确保通信的安全性。HttpClient 5 可以方便地处理 HTTPS 请求,支持定制化的 SSL/TLS 配置。
- 灵活性和可扩展性:- 易于扩展和定制:允许开发者根据需要进行灵活的定制,HttpClient 5 的设计高度模块化,用户可以根据需要对连接管理、重定向策略、请求重试策略、代理设置等进行灵活定制。- 代理支持:可以轻松配置 HTTP 或 SOCKS 代理,用于跨网络访问和隐私保护。
- 健壮的错误处理机制:- 自动重试和重定向处理:内置的重试机制和自动重定向处理,可以减少由于网络问题导致的失败请求。开发者可以定制是否启用自动重定向。
(2)Apache HttpClient 5 的新特性
与之前的 4.x 版本相比,HttpClient 5 进行了大量的改进和优化,特别是在性能和安全性方面:
- HTTP/2 支持:提供了完整的 HTTP/2 支持,包括多路复用、流优先级等特性。
- 更好的异步支持:在处理并发请求时,通过异步模型极大提升了响应速度和吞吐量。
- 灵活的响应处理:通过改进的响应处理 API,可以更方便地处理大型响应体,避免内存溢出问题。
(3)在 Java 项目的主要使用场景及缺点
使用场景:
- RESTful API 客户端:与第三方 API 交互,发送各种 HTTP 请求。
- Web 爬虫:抓取网页内容,处理重定向、Cookie 等。
- 分布式系统通信:用于微服务间的 HTTP 通信。
- 测试与自动化:模拟 HTTP 请求,进行集成和自动化测试。
- 代理与网关:处理请求代理和认证机制。
- 文件上传下载:实现大文件的传输。
- 认证交互:处理 OAuth2、JWT 等认证协议。
- 安全通信:处理 HTTPS 请求,确保数据安全。
缺点:
- 学习曲线陡峭:高级特性(如自定义连接池、SSL 配置、异步请求等)较为复杂,新手需要时间学习和掌握。
- 体积较大:相比轻量级客户端,如 OkHttp,HttpClient 依赖库较多,可能不适合小型项目。
- 性能消耗高:默认配置下的内存和 CPU 占用较高,需调整才能达到最佳性能。
- 配置繁琐:高级定制(如连接管理、认证、代理)需要较多配置,增加开发复杂度。
- 异步编程复杂:异步请求的回调、错误处理等逻辑复杂,增加代码难度。
二、在实际项目中的应用
(1)引入maven配置
首先,你需要将 HttpClient 5 的依赖加入到项目中。
pom.xml
文件如下:
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
<version>5.1</version>
</dependency>
(2)自定义HttpUtils工具类---实现连接管理、重试策略等
import lombok.extern.slf4j.Slf4j;
import org.apache.hc.client5.http.DnsResolver;
import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
import org.apache.hc.core5.http.HttpEntity;
import org.apache.hc.core5.http.io.SocketConfig;
import org.apache.hc.core5.http.message.BasicHeader;
import org.apache.hc.core5.util.Timeout;
import org.springframework.stereotype.Component;
import java.io.Closeable;
import java.io.IOException;
import java.net.InetAddress;
import java.net.URI;
import java.net.UnknownHostException;
import java.util.Map;
/**
* @author 响叮当
* @since 2024/8/15 14:10
**/
@Slf4j
@Component
public class ApacheHttpClientUtil {
private CloseableHttpClient httpClient;
@PostConstruct
public void init() {
SocketConfig socketConfig = SocketConfig.custom()
.setSoTimeout(Timeout.ofMilliseconds(1000))
.build();
PoolingHttpClientConnectionManager connectionManager = PoolingHttpClientConnectionManagerBuilder
.create()
.setDefaultSocketConfig(socketConfig)
.setMaxConnTotal(1000)
.setMaxConnPerRoute(50)
.build();
RequestConfig requestConfig = RequestConfig.custom()
.setConnectionRequestTimeout(Timeout.ofMilliseconds(8000))
.setResponseTimeout(Timeout.ofMilliseconds(8000))
.setConnectTimeout(Timeout.ofMilliseconds(8000))
.build();
httpClient = HttpClients
.custom()
.disableContentCompression()
.setConnectionManager(connectionManager)
.setDefaultRequestConfig(requestConfig)
.build();
}
public CloseableHttpResponse getOrHead(String url, String method, Map<String, String> headers) throws IOException {
HttpUriRequestBase request = new HttpUriRequestBase(method, URI.create(url));
BasicHeader[] head = mapToHeaders(headers);
request.setHeaders(head);
return httpClient.execute(request);
}
public CloseableHttpResponse post(String url, Map<String, String> headers, HttpEntity httpEntity) throws IOException {
HttpPost request = new HttpPost(url);
BasicHeader[] head = mapToHeaders(headers);
request.setHeaders(head);
request.setEntity(httpEntity);
return httpClient.execute(request);
}
public static BasicHeader[] mapToHeaders(Map<String, String> map) {
BasicHeader[] headers = new BasicHeader[map.size()];
int i = 0;
for (Map.Entry<String, String> entry : map.entrySet()) {
headers[i++] = new BasicHeader(entry.getKey(), entry.getValue());
}
return headers;
}
public static void closeQuietly(Closeable is) {
if (is != null) {
try {
is.close();
} catch (Exception ex) {
log.error("Resources encounter an exception when closing,ex:{}", ex.getMessage());
}
}
}
}
功能概述
- 初始化 HTTP 客户端:
init()
方法初始化CloseableHttpClient
,配置连接池、请求超时和套接字超时。 - 连接池配置:通过PoolingHttpClientConnectionManagerBuilder
设置最大连接数(1000)和每路由的最大连接数(50),用于高并发场景。- 请求配置:包括连接超时、请求超时和响应超时,确保在合理的时间内处理请求。 - HTTP 请求处理: - GET/HEAD 请求:
getOrHead()
方法可发送 GET 或 HEAD 请求,并接受自定义请求头。- POST 请求:post()
方法可发送 POST 请求,支持自定义请求头和实体。 - 资源管理:
closeQuietly()
方法用于关闭资源,避免抛出异常。
(3)代码中的具体实现
1、Get请求
@GetMapping("/get")
public void testGet() {
String url = "http://xxx.com.cn";
try {
// 1、构建入参、添加 headers
Map<String, String> headers = new HashMap<>();
// 2、发起http请求
CloseableHttpResponse httpResponse = apacheHttpClientUtil.getOrHead(url, "GET", headers);
// 3、返回结果,异常处理
if (httpResponse.getCode() == 200) {
String resStr = EntityUtils.toString(httpResponse.getEntity());
log.info("request_success, response:{}, httpResponse_Code:{}, reasonPhrase:{}", resStr, httpResponse.getCode(), httpResponse.getReasonPhrase());
} else {
log.error("request_fail, httpResponse_Code:{}, reasonPhrase:{}", httpResponse.getCode(), httpResponse.getReasonPhrase());
}
} catch (Exception e) {
log.error("request_udmp_fail, ex:{}", e);
}
}
2、POST请求-常规
@PostMapping("/post")
public void testPost(@RequestBody PostReq req) {
String url = "http://xxx.com.cn";
try {
// 1、构建入参、添加 headers
StringEntity stringEntity = new StringEntity(JSONUtil.toJsonStr(req), ContentType.APPLICATION_JSON);
Map<String, String> headers = new HashMap<>();
// 2、发起http请求
CloseableHttpResponse httpResponse = apacheHttpClientUtil.post(url, headers, stringEntity);
// 3、返回结果,异常处理
if (httpResponse.getCode() == 200) {
String resStr = EntityUtils.toString(httpResponse.getEntity());
log.info("request_success, response:{}, httpResponse_Code:{}, reasonPhrase:{}", resStr, httpResponse.getCode(), httpResponse.getReasonPhrase());
} else {
log.error("request_fail, httpResponse_Code:{}, reasonPhrase:{}", httpResponse.getCode(), httpResponse.getReasonPhrase());
}
} catch (Exception e) {
log.error("request_udmp_fail, ex:{}", e);
}
}
3、POST请求-上传文件
/**
* 上传文件
* @param multipartFile
* @param token
* @param key
* @return
*/
@PostMapping("/uploadFile")
public UploadRes testPostFile(
@RequestParam("file") MultipartFile multipartFile,
@RequestParam("token") String token,
@RequestParam("key") String key
) {
UploadRes res = null;
String url = "http://xxx.com.cn";
try {
// 1、构建File参数
File file = new File(multipartFile.getOriginalFilename());
try (FileOutputStream fos = new FileOutputStream(file)) {
fos.write(multipartFile.getBytes());
}
MultipartEntityBuilder builder = MultipartEntityBuilder.create();
builder.addBinaryBody("file", file, ContentType.MULTIPART_FORM_DATA, "ex.xlsx");
// 2、构建其他参数
builder.addTextBody("token", token, ContentType.TEXT_PLAIN);
builder.addTextBody("key", key, ContentType.TEXT_PLAIN);
HttpEntity multipartEntity = builder.build();
// 3、需要加 header,在这里加
Map<String, String> headers = new HashMap<>();
// 4、发起http请求
CloseableHttpResponse httpResponse = apacheHttpClientUtil.post(url, headers, multipartEntity);
// 5、返回结果,异常处理
if (httpResponse.getCode() == 200) {
String resStr = EntityUtils.toString(httpResponse.getEntity());
res = JSONUtil.toBean(resStr, UploadRes.class);
log.info("request_success, response:{}, httpResponse_Code:{}, reasonPhrase:{}", resStr, httpResponse.getCode(), httpResponse.getReasonPhrase());
} else {
log.error("request_fail, httpResponse_Code:{}, reasonPhrase:{}", httpResponse.getCode(), httpResponse.getReasonPhrase());
}
} catch (Exception e) {
log.error("request_udmp_fail, ex:{}", e);
}
return res;
}
(4)高级用法
1、处理重定向
HttpClient 5 默认会处理 3XX 重定向,但你也可以自定义行为。
CloseableHttpClient httpClient = HttpClients.custom()
.disableRedirectHandling() // 禁用自动重定向
.build();
2、SSL/TLS 支持
使用 HttpClient 5 可以轻松处理 HTTPS 请求,下面展示如何自定义 SSL 配置。
import org.apache.hc.core5.ssl.SSLContextBuilder;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import javax.net.ssl.SSLContext;
SSLContext sslContext = SSLContextBuilder.create()
.loadTrustMaterial((chain, authType) -> true) // 信任所有证书
.build();
CloseableHttpClient httpClient = HttpClients.custom()
.setSSLContext(sslContext)
.build();
3、处理异步请求
如果你需要发送异步 HTTP 请求,可以使用
HttpAsyncClient
。
import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
import org.apache.hc.client5.http.impl.async.HttpAsyncClients;
import org.apache.hc.core5.concurrent.FutureCallback;
import org.apache.hc.client5.http.classic.methods.HttpGet;
CloseableHttpAsyncClient asyncClient = HttpAsyncClients.createDefault();
asyncClient.start();
HttpGet request = new HttpGet("https://jsonplaceholder.typicode.com/posts/1");
asyncClient.execute(request, new FutureCallback<>() {
@Override
public void completed(CloseableHttpResponse response) {
System.out.println("Response received: " + response.getCode());
}
@Override
public void failed(Exception ex) {
System.out.println("Request failed: " + ex.getMessage());
}
@Override
public void cancelled() {
System.out.println("Request cancelled");
}
});
4、处理 Cookie
import org.apache.hc.client5.http.cookie.BasicCookieStore;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.cookie.CookieStore;
CookieStore cookieStore = new BasicCookieStore();
CloseableHttpClient httpClient = HttpClients.custom()
.setDefaultCookieStore(cookieStore)
.build();
5、HTTPDNS支持
// 当域名为www.baidu.com结尾时候,转到local的机器上
String Target_IP= "127.0.0.1";
String[] domains = new String[]{"www.baidu.com"};
DnsResolver dnsResolver = new DnsResolver() {
@Override
public InetAddress[] resolve(String host) throws UnknownHostException {
if (host.endsWith(domains[0]))) {
return new InetAddress[]{InetAddress.getByName(Target_IP)};
}
return new InetAddress[0];
}
@Override
public String resolveCanonicalHostname(String s) {
return null;
}
};
// @PostConstruct
public void init() {
SocketConfig socketConfig = SocketConfig.custom()
.setSoTimeout(Timeout.ofMilliseconds(1000))
.build();
PoolingHttpClientConnectionManager connectionManager = PoolingHttpClientConnectionManagerBuilder
.create()
.setDnsResolver(dnsResolver) // 这里是HttpDns配置
.setDefaultSocketConfig(socketConfig)
.setMaxConnTotal(1000)
.setMaxConnPerRoute(50)
.build();
版权归原作者 响叮当! 所有, 如有侵权,请联系我们删除。