Java 9 提供了新的HTTP 客户端(HttpClient
)来处理HTTP调用,但是那时还处于孵化器阶段,经历了 Java 9 、Java 10 ,终于在Java11版本正式“转正”。
为什么要提供全新的 HTTP 客户端
Java 11 引入新的 HTTP 客户端的原因是为了解决旧的HttpURLConnection
类存在的多个问题和局限性。HttpURLConnection
存在如下几个局限性:
- 不直观的API设计:
HttpURLConnection
的 API 设计过于复杂,对于新手比较难上手,例如我们必须手动处理输入和输出流,以及错误处理。 - 不支持 HTTP/2:
HttpURLConnection
仅支持 HTTP/1.1,不支持 HTTP/2。HTTP/2支持多路复用、服务器推送和头部压缩等多项特性,这些都能提高网络通信的效率。 - 性能问题:
HttpURLConnection
在高并发场景下性能表现不是很好,尤其是同步阻塞的特性更是性能的瓶颈。 - 缺乏现代Web特性的支持:随着Web技术的发展,如 WebSocket 等技术变得越来越重要,而
HttpURLConnection
并不支持这些。 - 缺乏异步处理能力:在处理HTTP请求时,
HttpURLConnection
不支持异步模式。 - 错误处理繁琐:
HttpURLConnection
的错误处理比较麻烦。我们需要检查特定的HTTP错误代码,并对不同的响应代码进行不同的处理。 - 连接管理不足:
HttpURLConnection
不支持连接池、不支持自动重试。 - 不可变对象和线程安全:
HttpURLConnection
不是不可变的,也不是线程安全的。
基于上述几点,Java 11 引入 HttpClient
来替代HttpURLConnection
。相比HttpURLConnection
,HttpClient
具有如下优势:
- 支持 HTTP/2:
HttpClient
支持 HTTP/2 协议的所有特性,包括同步和异步编程模型。 - 支持** WebSocket**:支持WebSocket,允许建立持久的连接,并进行全双工通信。
- 更简洁的API:
HttpClient
提供了更简洁、更易于使用的 API,我们能够非常方便地发送 HTTP 请求。 - 支持同步和异步:提供了同步和异步的两种模式。这意味着我们既可以等待HTTP响应,也可以使用回调机制处理HTTP响应。
- 链式调用:新的
HttpClient
允许链式调用,使得构建和发送请求变得更简单。 - 更好的错误处理机制:新的
HttpClient
提供了更好的错误处理机制,当HTTP请求失败时,可以通过异常机制更清晰地了解到发生了什么。
核心类介绍
HttpClient的主要类有下面三个:
java.net.http.HttpClient
java.net.http.HttpRequest
java.net.http.HttpResponse
HttpClient
HttpClient
是用于发送请求和获取响应的客户端。它可以发送同步和异步的HTTP请求,并支持HTTP/1.1、HTTP/2 以及WebSocket。
由于HttpClient
是不可变的,所以它是线程安全的,可以重用以发送多个请求。
利用HttpClient.newBuilder()
可以创建HttpClient
的实例,并可以配置各种属性,如:
priority | 配置属性 |
---|---|
version() |
指定HTTP协议的版本(HTTP/1.1或HTTP/2) |
followRedirects() |
设置重定向策略 |
proxy() |
设置代理服务器 |
authenticator() |
设置HTTP身份验证 |
connectTimeout() |
置建立HTTP连接的超时时间 |
executor() |
设置自定义的Executor,该Executor用于异步任务 |
cookieHandler() |
设置Cookie处理器 |
sslParameters() |
设置SSL/TLS的配置 |
priority() |
设置请求的优先级 |
这些方法可以使用链式的方式配置在一起,一旦配置完成,调用build()
方法就会创建一个新的HttpClient
实例,该实例包含所有配置的设置。例如:
HttpClient client = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_2)
.followRedirects(HttpClient.Redirect.NEVER)
.proxy(ProxySelector.of(new InetSocketAddress("127.0.0.1", 8808)))
.connectTimeout(Duration.ofMinutes(1))
.build();
HttpClient
HttpRequest
HttpRequest
表示一个HTTP请求,它也是不可变的,所以一旦创建,就不能更改它的配置和数据。使用 HttpRequest.newBuilder()
来创建 HttpRequest
实例,然后链式调用设置方法来配置请求,配置项如下:
方法名 | 配置项 |
---|---|
uri() |
设置请求的统一资源标识符 (URI) |
timeout() |
设置请求的超时时间 |
header() |
添加单个请求头 |
headers() |
批量添加请求头 |
version() |
指定 HTTP 协议的版本 |
expectContinue() |
设置 Expect: 100-Continue 请求头 |
setHeader() |
设置特定的请求头,如认证头 |
HttpRequest
支持 GET、POST、DELETE 等方法,对应的方法如下:
方法名 | 描述 |
---|---|
GET() |
创建一个 GET 请求 |
POST(HttpRequest.BodyPublisher body) |
创建一个带有请求体的 POST 请求 |
PUT(HttpRequest.BodyPublisher body) |
创建一个带有请求体的 PUT 请求 |
DELETE() |
创建一个 DELETE 请求 |
method(String method, HttpRequest.BodyPublisher body) |
创建一个自定义方法的请求 |
下面代码是创建一个 GET 请求:
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://www.skjava.com"))
.version(HttpClient.Version.HTTP_2)
.timeout(Duration.ofSeconds(10))
.GET()
.build();
对于需要请求体的请求,例如 POST、PUT,我们可以使用HttpRequest.BodyPublishers
来提供请求体的内容。
HttpResponse
HttpResponse
表示一个 HTTP 响应,它包含HTTP响应的所有信息,包括状态码、响应头和响应体。与HttpClient
和HttpRequest
一样,HttpResponse
也是不可变的。
当我们利用 HttpClient
发送 HTTP 请求时,需要指定一个HttpResponse.BodyHandler
来处理响应体。这个处理器决定了如何处理响应数据,比如将其作为String,如下:
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
HttpResponse
方法如下:
方法名 | 描述 |
---|---|
statusCode() |
获取HTTP响应的状态码 |
body() |
获取HTTP响应体 |
headers() |
获取HTTP响应头 |
uri() |
获取请求的URI |
version() |
获取响应的HTTP协议版本 |
request() |
获取生成此响应的HttpRequest 对象 |
previousResponse() |
获取重定向之前的响应,如果有的话 |
sslSession() |
获取SSL会话信息,如果在SSL连接上获得响应,则为Optional<SSLSession> |
trailers() |
异步获取HTTP尾部头,返回CompletableFuture<HttpHeaders> |
示例
HttpClient
可以使用同步发送和异步发送。
同步发送
HttpClient
提供了 send()
方法,发送请求后会一直阻塞到收到response为止。
@Test
public void sendTest() {
// 构建 HttpClient 对象
HttpClient httpClient = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_2) //HTTP版本为HTTP/2
.connectTimeout(Duration.ofSeconds(10)) // 连接超时为10秒
.build();
// 构建 HttpRequest 对象
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://skjava.com/article/list?page=3")) // uri
.timeout(Duration.ofSeconds(5)) // 超时时间为 5 秒
.GET().build();
HttpResponse<String> response = null;
try {
response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == 200) {
// 请求成功
System.out.println("Response Body: " + response.body());
} else {
// 请求不成功
System.err.println("Response Status Code: " + response.statusCode());
System.err.println("Response Body: " + response.body());
}
} catch (IOException e) {
// 捕获并处理网络I/O错误
System.err.println("IOException occurred: " + e.getMessage());
} catch (InterruptedException e) {
// 捕获并处理请求过程中的中断异常
System.err.println("InterruptedException occurred: " + e.getMessage());
// 中断当前线程
Thread.currentThread().interrupt();
}
}
首先构建 HttpClient
和 HttpRequest
实例对象,调用 HttpClient 的 send()
方法发送 HTTP 同步请求,结果如下:
发送同步请求处理比较简单,我们只需要注意处理 IOException
和 InterruptedException
两个异常。
异步发送
HttpClient
提供了 sendAsync()
方法用于发送异步请求,发送请求后会立刻返回 CompletableFuture
,然后我们可以使用CompletableFuture
中的方法来设置异步处理器。
@Test
public void sendAsyncTest() {
// 构建 HttpClient 对象
HttpClient httpClient = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_2) //HTTP版本为HTTP/2
.connectTimeout(Duration.ofSeconds(10)) // 连接超时为10秒
.build();
// 构建 HttpRequest 对象
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://skjava.com/article/list?page=1")) // uri
.timeout(Duration.ofSeconds(5)) // 超时时间为 5 秒
.GET().build();
// 发送异步请求
CompletableFuture<HttpResponse<String>> futureResponse = httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString());
// 处理响应
futureResponse.thenApply(HttpResponse::body) // 获取响应体
.thenAccept(System.out::println) // 打印响应体
.join(); // 等待所有的操作完成
}
注意,异步请求 sendAsync()
返回的是一个 CompletableFuture
,关于 可以看这篇文章:Java 8 新特性—深入理解 CompletableFuture