1. 概述
本文将深入探讨 Java 11 中标准化的 HTTP Client API,该 API 实现了 HTTP/2 和 WebSocket 协议。
它的目标是取代 JDK 中自 Java 早期版本就存在的老旧 HttpUrlConnection 类。
直到最近,Java 仅提供 HttpURLConnection API,这个 API 层级较低,且以功能贫瘠、用户体验差而闻名。
因此,开发者普遍依赖第三方库,例如:
- Apache HttpClient
- Jetty
- Spring 的 RestTemplate
2. 背景
该变更作为 JEP 321 的一部分实现。
2.1. JEP 321 的主要变更
- Java 9 中孵化的 HTTP API 现已正式纳入 Java SE API。新的 HTTP APIs 位于 java.net.http.* 包下
- 新版 HTTP 协议通过引入流多路复用、头部压缩和服务器推送等特性,显著提升了客户端请求与服务器响应的整体性能
- 自 Java 11 起,API 已完全异步化(之前的 HTTP/1.1 实现是阻塞式的)。异步调用通过 CompletableFuture 实现,其机制确保每个阶段在前一阶段完成后自动执行,整个流程完全异步
- 新 HTTP Client API 提供了执行 HTTP 网络操作的标准方式,原生支持 HTTP/2 等现代 Web 特性,无需第三方依赖
- 新 API 原生支持 HTTP 1.1/2 和 WebSocket。核心功能类包括:
- HttpClient 类:
java.net.http.HttpClient
- HttpRequest 类:
java.net.http.HttpRequest
- HttpResponse
接口: java.net.http.HttpResponse
- WebSocket 接口:
java.net.http.WebSocket
- HttpClient 类:
2.2. Java 11 之前 HTTP Client 的问题
现有的 HttpURLConnection API 及其实现存在诸多缺陷:
- URLConnection API 设计时考虑了多种现已失效的协议(FTP、gopher 等)
- API 早于 HTTP/1.1 诞生,设计过于抽象
- 仅支持阻塞模式(即每个请求/响应占用一个线程)
- 维护成本极高
3. HTTP Client API 概览
与 HttpURLConnection 不同,HTTP Client 提供了同步和异步两种请求机制。
API 由三个核心类组成:
- HttpRequest:表示要通过 HttpClient 发送的请求
- HttpClient:作为多个请求共享的配置信息容器
- HttpResponse:表示 HttpRequest 调用的结果
后续章节将详细解析这些组件。首先聚焦请求对象。
4. HttpRequest
HttpRequest 表示要发送的请求对象。新实例可通过 HttpRequest.Builder 创建。
调用 HttpRequest.newBuilder()
获取构建器。Builder 类提供丰富方法用于配置请求。
下面介绍关键配置项。
⚠️ JDK 16 新增了
newBuilder(HttpRequest request, BiPredicate<String,String> filter)
方法,可基于现有请求创建构建器并过滤头部:HttpRequest.newBuilder(request, (name, value) -> !name.equalsIgnoreCase("Foo-Bar"))
4.1. 设置 URI
创建请求的第一步是提供 URL。有两种方式:
- 使用带 URI 参数的 Builder 构造函数
- 在 Builder 实例上调用
uri(URI)
方法:
HttpRequest.newBuilder(new URI("https://postman-echo.com/get"))
HttpRequest.newBuilder()
.uri(new URI("https://postman-echo.com/get"))
4.2. 指定 HTTP 方法
通过调用 Builder 的方法定义 HTTP 方法:
GET()
POST(BodyPublisher body)
PUT(BodyPublisher body)
DELETE()
BodyPublisher 将在后续详细说明
简单 GET 请求示例:
HttpRequest request = HttpRequest.newBuilder()
.uri(new URI("https://postman-echo.com/get"))
.GET()
.build();
此请求包含 HttpClient 所需的所有基本参数。但通常需要添加额外配置:
- HTTP 协议版本
- 请求头
- 超时设置
4.3. 设置 HTTP 协议版本
API 默认使用 HTTP/2 协议,但可显式指定版本:
HttpRequest request = HttpRequest.newBuilder()
.uri(new URI("https://postman-echo.com/get"))
.version(HttpClient.Version.HTTP_2)
.GET()
.build();
✅ 重要提示:若服务器不支持 HTTP/2,客户端会自动降级到 HTTP/1.1
4.4. 设置请求头
通过构建器方法添加请求头:
- 使用
headers()
方法批量添加键值对 - 使用
header()
方法逐个添加:
HttpRequest request = HttpRequest.newBuilder()
.uri(new URI("https://postman-echo.com/get"))
.headers("key1", "value1", "key2", "value2")
.GET()
.build();
HttpRequest request2 = HttpRequest.newBuilder()
.uri(new URI("https://postman-echo.com/get"))
.header("key1", "value1")
.header("key2", "value2")
.GET()
.build();
4.5. 设置超时
通过 timeout()
方法定义等待响应的时间。超时将抛出 HttpTimeoutException,默认超时为无限:
HttpRequest request = HttpRequest.newBuilder()
.uri(new URI("https://postman-echo.com/get"))
.timeout(Duration.of(10, SECONDS))
.GET()
.build();
5. 设置请求体
通过请求构建器方法添加请求体:
POST(BodyPublisher body)
PUT(BodyPublisher body)
DELETE()
新 API 提供多种开箱即用的 BodyPublisher 实现:
- StringProcessor:从字符串读取体,通过
HttpRequest.BodyPublishers.ofString
创建 - InputStreamProcessor:从输入流读取体,通过
HttpRequest.BodyPublishers.ofInputStream
创建 - ByteArrayProcessor:从字节数组读取体,通过
HttpRequest.BodyPublishers.ofByteArray
创建 - FileProcessor:从文件读取体,通过
HttpRequest.BodyPublishers.ofFile
创建
无需请求体时使用 HttpRequest.BodyPublishers.noBody()
:
HttpRequest request = HttpRequest.newBuilder()
.uri(new URI("https://postman-echo.com/post"))
.POST(HttpRequest.BodyPublishers.noBody())
.build();
⚠️ JDK 16 新增
BodyPublishers.concat(BodyPublisher…)
方法,可将多个发布者的请求体串联
5.1. StringBodyPublisher
使用 StringBodyPublishers
设置字符串请求体非常直观:
HttpRequest request = HttpRequest.newBuilder()
.uri(new URI("https://postman-echo.com/post"))
.headers("Content-Type", "text/plain;charset=UTF-8")
.POST(HttpRequest.BodyPublishers.ofString("Sample request body"))
.build();
5.2. InputStreamBodyPublisher
需将 InputStream 作为 Supplier 传递(实现延迟创建):
byte[] sampleData = "Sample request body".getBytes();
HttpRequest request = HttpRequest.newBuilder()
.uri(new URI("https://postman-echo.com/post"))
.headers("Content-Type", "text/plain;charset=UTF-8")
.POST(HttpRequest.BodyPublishers
.ofInputStream(() -> new ByteArrayInputStream(sampleData)))
.build();
此处使用 ByteArrayInputStream,实际可替换为任何 InputStream 实现
5.3. ByteArrayProcessor
直接传递字节数组:
byte[] sampleData = "Sample request body".getBytes();
HttpRequest request = HttpRequest.newBuilder()
.uri(new URI("https://postman-echo.com/post"))
.headers("Content-Type", "text/plain;charset=UTF-8")
.POST(HttpRequest.BodyPublishers.ofByteArray(sampleData))
.build();
5.4. FileProcessor
使用文件路径创建请求体:
HttpRequest request = HttpRequest.newBuilder()
.uri(new URI("https://postman-echo.com/post"))
.headers("Content-Type", "text/plain;charset=UTF-8")
.POST(HttpRequest.BodyPublishers.fromFile(
Paths.get("src/test/resources/sample.txt")))
.build();
6. HttpClient
所有请求通过 HttpClient 发送,可通过 HttpClient.newBuilder()
或 HttpClient.newHttpClient()
实例化。
6.1. 处理响应体
类似发布者的工厂方法,API 提供常用响应体类型的处理器:
BodyHandlers.ofByteArray
BodyHandlers.ofString
BodyHandlers.ofFile
BodyHandlers.discarding
BodyHandlers.replacing
BodyHandlers.ofLines
BodyHandlers.fromLineSubscriber
✅ 注意新的 BodyHandlers 工厂类简化了代码: Java 11 之前:
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandler.asString());
**Java 11+**:
HttpResponse<String> response = client.send(request, BodyHandlers.ofString());
6.2. 设置代理
通过 proxy()
方法配置代理:
HttpResponse<String> response = HttpClient
.newBuilder()
.proxy(ProxySelector.getDefault())
.build()
.send(request, BodyHandlers.ofString());
示例使用系统默认代理
6.3. 设置重定向策略
当页面迁移时,服务器返回 3xx 状态码和新 URI。HttpClient 可根据配置自动重定向:
HttpResponse<String> response = HttpClient.newBuilder()
.followRedirects(HttpClient.Redirect.ALWAYS)
.build()
.send(request, BodyHandlers.ofString());
所有策略定义在枚举 HttpClient.Redirect 中
6.4. 设置连接认证器
Authenticator 处理连接的凭据协商(HTTP 认证),支持多种认证方案(如 Basic/Digest 认证):
HttpResponse<String> response = HttpClient.newBuilder()
.authenticator(new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(
"admin",
"s3cr3t".toCharArray());
}
}).build()
.send(request, BodyHandlers.ofString());
⚠️ 生产环境中应避免硬编码凭据,Authenticator 提供的
getRequestingSite()
等方法可动态获取认证信息
6.5. 发送请求 – 同步 vs 异步
新 HttpClient 提供两种请求方式:
- send(…) – 同步(阻塞直到响应返回)
- sendAsync(…) – 异步(非阻塞,立即返回)
同步请求示例:
HttpResponse<String> response = HttpClient.newBuilder()
.build()
.send(request, BodyHandlers.ofString());
异步请求示例:
CompletableFuture<HttpResponse<String>> response = HttpClient.newBuilder()
.build()
.sendAsync(request, HttpResponse.BodyHandlers.ofString());
✅ 异步 API 支持多响应处理和流式传输:
List<URI> targets = Arrays.asList( new URI("https://postman-echo.com/get?foo1=bar1"), new URI("https://postman-echo.com/get?foo2=bar2")); HttpClient client = HttpClient.newHttpClient(); List<CompletableFuture<String>> futures = targets.stream() .map(target -> client .sendAsync( HttpRequest.newBuilder(target).GET().build(), HttpResponse.BodyHandlers.ofString()) .thenApply(response -> response.body())) .collect(Collectors.toList());
6.6. 为异步调用设置执行器
通过 executor()
方法自定义线程池,限制并发请求数:
ExecutorService executorService = Executors.newFixedThreadPool(2);
CompletableFuture<HttpResponse<String>> response1 = HttpClient.newBuilder()
.executor(executorService)
.build()
.sendAsync(request, HttpResponse.BodyHandlers.ofString());
CompletableFuture<HttpResponse<String>> response2 = HttpClient.newBuilder()
.executor(executorService)
.build()
.sendAsync(request, HttpResponse.BodyHandlers.ofString());
默认使用
java.util.concurrent.Executors.newCachedThreadPool()
6.7. 定义 CookieHandler
通过 cookieHandler()
方法配置 Cookie 策略:
HttpClient.newBuilder()
.cookieHandler(new CookieManager(null, CookiePolicy.ACCEPT_NONE))
.build();
允许存储 Cookie 时,可通过以下方式访问:
((CookieManager) httpClient.cookieHandler().get()).getCookieStore()
7. HttpResponse 对象
HttpResponse 表示服务器响应,关键方法包括:
statusCode()
:返回状态码(int 类型)body()
:返回响应体(类型取决于 BodyHandler)
其他实用方法:uri()
、headers()
、trailers()
和 version()
7.1. 响应对象的 URI
uri()
返回实际响应的 URI(可能因重定向与请求 URI 不同):
assertThat(request.uri()
.toString(), equalTo("http://stackoverflow.com"));
assertThat(response.uri()
.toString(), equalTo("https://stackoverflow.com/"));
7.2. 响应头部
通过 headers()
获取只读的 HttpHeaders 对象:
HttpResponse<String> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
HttpHeaders responseHeaders = response.headers();
7.3. 响应协议版本
version()
返回实际通信使用的 HTTP 版本(即使请求指定 HTTP/2,服务器可能用 HTTP/1.1 响应):
HttpRequest request = HttpRequest.newBuilder()
.uri(new URI("https://postman-echo.com/get"))
.version(HttpClient.Version.HTTP_2)
.GET()
.build();
HttpResponse<String> response = HttpClient.newHttpClient()
.send(request, BodyHandlers.ofString());
assertThat(response.version(), equalTo(HttpClient.Version.HTTP_1_1));
8. 处理 HTTP/2 的服务器推送
新 HttpClient 通过 PushPromiseHandler 接口支持服务器推送。
✅ 核心优势:服务器在响应主资源时主动推送额外资源,减少往返延迟,提升页面渲染性能
HttpClient 提供重载的 sendAsync 方法处理推送承诺:
创建 PushPromiseHandler:
private static PushPromiseHandler<String> pushPromiseHandler() {
return (HttpRequest initiatingRequest,
HttpRequest pushPromiseRequest,
Function<HttpResponse.BodyHandler<String>,
CompletableFuture<HttpResponse<String>>> acceptor) -> {
acceptor.apply(BodyHandlers.ofString())
.thenAccept(resp -> {
System.out.println(" Pushed response: " + resp.uri() + ", headers: " + resp.headers());
});
System.out.println("Promise request: " + pushPromiseRequest.uri());
System.out.println("Promise request: " + pushPromiseRequest.headers());
};
}
处理推送承诺:
httpClient.sendAsync(pageRequest, BodyHandlers.ofString(), pushPromiseHandler())
.thenAccept(pageResponse -> {
System.out.println("Page response status code: " + pageResponse.statusCode());
System.out.println("Page response headers: " + pageResponse.headers());
String responseBody = pageResponse.body();
System.out.println(responseBody);
})
.join();
9. 总结
本文深入解析了 Java 11 标准化的 HttpClient API,该 API 在 Java 9 孵化版本基础上进行了重大增强。
完整示例代码可在 GitHub 获取。
文中示例使用了 https://postman-echo.com 提供的测试接口。