HttpClient 概述
超文本传输协议 (HTTP) 可能是当今 Internet 上使用的最重要的协议。Web服务、支持网络的设备以及网络计算的增长继续扩大了 HTTP 的作用 协议,同时增加了需要 HTTP 支持的应用程序数量。
尽管 java.net 包提供了通过 HTTP 访问资源的基本功能,但它并未提供完整的 许多应用程序所需的灵活性或功能。HttpClient 试图通过提供高效的 最新且功能丰富的包,用于实现最新 HTTP 标准和建议的客户端。
HttpClient HttpClient专为扩展而设计,同时为基础HTTP协议提供强大的支持,任何构建HTTP感知客户端应用程序的人都可能感兴趣,例如web浏览器、web服务客户端或利用或扩展HTTP协议进行分布式通信的系统。
特征
- 基于标准的纯 Java,HTTP 版本 1.0、1.1、2.0 的实现(仅限异步 API)
- 支持使用 HTTPS (HTTP over SSL) 协议进行加密。
- 可插拔套接字工厂和 TLS 策略。
- 通过 HTTP/1.1 和 HTTP/1.0 代理进行透明消息交换。
- 通过 CONNECT 方法通过 HTTP/1.1 和 HTTP/1.0 代理建立隧道 HTTPS 连接。
- Basic、Digest、Bearer 身份验证方案。
- HTTP 状态管理和 Cookie 支持。
- 灵活的连接管理和池化。
- 支持 HTTP 响应缓存。
- 源代码在 Apache 许可证下免费提供。
标准合规性
HttpClient 努力遵守 Internet 工程任务组 (IETF) 认可的以下规范,以及 整个互联网:
- RFC 9110 - HTTP 语义
- RFC 9111 - HTTP 缓存
- RFC 9112 - 超文本传输协议版本 1.1 (HTTP/1.1)
- RFC 7540 - 超文本传输协议版本 2 (HTTP/2)
- RFC 7541 - HPACK:HTTP/2 的标头压缩
- RFC 1945 - 超文本传输协议 -- HTTP/1.0
- RFC 2396 - 统一资源标识符 (URI):泛型语法
- RFC 6265 - HTTP 状态管理机制 (Cookie)
- RFC 7616 - HTTP 摘要访问身份验证
- RFC 7617 - HTTP"基本"身份验证方案
- RFC 5861 - 过时内容的 HTTP 缓存控制扩展
HttpClient5相对早期版本的优势
早期版本劣势
1. 同步阻塞,效率低下
早期的HttpClient(比如Apache HttpClient 4.x)主要是同步的,这意味着每次发起请求时,线程都会被挂起,直到服务器响应。在高并发场景下,这种阻塞式调用会严重拖慢应用的性能,导致资源利用率低下。
2. 配置复杂,易出错
配置HttpClient可不是件简单事儿。连接超时、请求超时、套接字超时,还有各种各样的HTTP头设置,稍不注意就可能踩坑。更别提SSL/TLS配置了,简直是新手程序员的噩梦。
3. API过时,维护成本高
随着Java版本的迭代,一些老的HttpClient API显得越来越过时。它们可能不支持最新的Java特性,比如Lambda表达式、Stream API等,这使得代码维护起来异常艰难。而且,随着新特性的加入,老版本的HttpClient往往需要打补丁,增加了维护成本。
4. 安全性隐患
网络安全日益重要,而老版本的HttpClient在安全性方面可能存在漏洞。比如,对SSL/TLS协议的支持可能不够全面,容易受到中间人攻击或数据泄露的风险。
新版本优势
1、异步支持
HttpClient 5原生支持异步和响应式编程,这意味着你可以在不阻塞线程的情况下发起HTTP请求,大大提高了应用的并发性能。
2、简化配置
相比老版本,HttpClient 5的配置更加直观和灵活。你可以通过构建器模式轻松设置各种参数,减少了配置错误的可能性。
3、增强安全性
HttpClient 5对SSL/TLS协议的支持更加全面,默认启用了更安全的加密套件和协议版本,提升了数据传输的安全性。
HttpClient5 快速入门
-
下载最新 HttpClient 5.4 版本的"二进制"包,或使用您选择的依赖项管理器配置依赖项.
-
HttpClient 5.4 需要 Java 1.8 或更高版本。
Maven引入依赖
XML
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
<version>5.4.1</version>
</dependency>
使用示例
- 以下代码片段说明了如何使用 HttpClient 本机 API 执行 HTTP GET 和 POST 请求。
java
try (CloseableHttpClient httpclient = HttpClients.createDefault()) {
ClassicHttpRequest httpGet = ClassicRequestBuilder.get("http://httpbin.org/get")
.build();
// The underlying HTTP connection is still held by the response object
// to allow the response content to be streamed directly from the network socket.
// In order to ensure correct deallocation of system resources
// the user MUST call CloseableHttpResponse#close() from a finally clause.
// Please note that if response content is not fully consumed the underlying
// connection cannot be safely re-used and will be shut down and discarded
// by the connection manager.
httpclient.execute(httpGet, response -> {
System.out.println(response.getCode() + " " + response.getReasonPhrase());
final HttpEntity entity1 = response.getEntity();
// do something useful with the response body
// and ensure it is fully consumed
EntityUtils.consume(entity1);
return null;
});
ClassicHttpRequest httpPost = ClassicRequestBuilder.post("http://httpbin.org/post")
.setEntity(new UrlEncodedFormEntity(Arrays.asList(
new BasicNameValuePair("username", "vip"),
new BasicNameValuePair("password", "secret"))))
.build();
httpclient.execute(httpPost, response -> {
System.out.println(response.getCode() + " " + response.getReasonPhrase());
final HttpEntity entity2 = response.getEntity();
// do something useful with the response body
// and ensure it is fully consumed
EntityUtils.consume(entity2);
return null;
});
}
- 可以使用更简单但不太灵活、流畅的 API 来执行相同的请求。
java
// The fluent API relieves the user from having to deal with manual deallocation of system
// resources at the cost of having to buffer response content in memory in some cases.
Request.Get("http://targethost/homepage")
.execute().returnContent();
Request.Post("http://targethost/login")
.bodyForm(Form.form().add("username", "vip").add("password", "secret").build())
.execute().returnContent();
- 以下代码片段说明了如何使用 HttpClient 异步 API 执行 HTTP 请求。
java
try (CloseableHttpAsyncClient httpclient = HttpAsyncClients.createDefault()) {
// Start the client
httpclient.start();
// Execute request
SimpleHttpRequest request1 = SimpleRequestBuilder.get("http://httpbin.org/get").build();
Future<SimpleHttpResponse> future = httpclient.execute(request1, null);
// and wait until response is received
SimpleHttpResponse response1 = future.get();
System.out.println(request1.getRequestUri() + "->" + response1.getCode());
// One most likely would want to use a callback for operation result
CountDownLatch latch1 = new CountDownLatch(1);
SimpleHttpRequest request2 = SimpleRequestBuilder.get("http://httpbin.org/get").build();
httpclient.execute(request2, new FutureCallback<SimpleHttpResponse>() {
@Override
public void completed(SimpleHttpResponse response2) {
latch1.countDown();
System.out.println(request2.getRequestUri() + "->" + response2.getCode());
}
@Override
public void failed(Exception ex) {
latch1.countDown();
System.out.println(request2.getRequestUri() + "->" + ex);
}
@Override
public void cancelled() {
latch1.countDown();
System.out.println(request2.getRequestUri() + " cancelled");
}
});
latch1.await();
// In real world one most likely would want also want to stream
// request and response body content
CountDownLatch latch2 = new CountDownLatch(1);
AsyncRequestProducer producer3 = AsyncRequestBuilder.get("http://httpbin.org/get").build();
AbstractCharResponseConsumer<HttpResponse> consumer3 = new AbstractCharResponseConsumer<HttpResponse>() {
HttpResponse response;
@Override
protected void start(HttpResponse response, ContentType contentType) throws HttpException, IOException {
this.response = response;
}
@Override
protected int capacityIncrement() {
return Integer.MAX_VALUE;
}
@Override
protected void data(CharBuffer data, boolean endOfStream) throws IOException {
// Do something useful
}
@Override
protected HttpResponse buildResult() throws IOException {
return response;
}
@Override
public void releaseResources() {
}
};
httpclient.execute(producer3, consumer3, new FutureCallback<HttpResponse>() {
@Override
public void completed(HttpResponse response3) {
latch2.countDown();
System.out.println(request2.getRequestUri() + "->" + response3.getCode());
}
@Override
public void failed(Exception ex) {
latch2.countDown();
System.out.println(request2.getRequestUri() + "->" + ex);
}
@Override
public void cancelled() {
latch2.countDown();
System.out.println(request2.getRequestUri() + " cancelled");
}
});
latch2.await();
}