Java 26 的 HTTP Client API 新增 HTTP/3 支持
[LOADING...]
1. 概述
现代应用需要更快、更可靠且更安全的网络通信,而 HTTP/3 正是为了满足这些需求。
Java 在 Java 11 中引入了现代的 HttpClient API,以替代旧的 HttpURLConnection API,并内置支持 HTTP/2、异步通信以及 WebSocket 集成。在 Java 26 中,HttpClient API 继续演进,新增了对 HTTP/3 的支持。
在本教程中,我们将探讨 Java 26 如何通过 HTTP/3 支持扩展 HttpClient API,并使用它构建一个 Java 应用程序。
2. 什么是 HTTP/3?
顾名思义,HTTP 版本 3,或简称 HTTP/3,是超文本传输协议的较新版本。
HTTP/3 通过将 TCP 替换为 Quick UDP Internet Connections(QUIC)作为传输层,在性能、可靠性和安全性方面带来了显著改进。 QUIC 将传输和 TLS 1.3 握手合并为单次往返,从而相比 TCP 缩短了连接建立时间。它尤其适用于高延迟和移动网络。
QUIC 流是相互独立的,因此丢包只影响该流,而其他流可以继续不受中断地传输。大多数现代浏览器现在都支持 HTTP/3,并在服务器通告它时自动使用。
3. 在 Java 26 中使用 HTTP/3
我们可以继续使用相同的高层 HTTP Client API,而由运行时在内部处理协议协商。
3.1. 声明 HTTP/3
在发送 HTTP 请求之前,我们首先创建一个 HttpClient 实例。可以通过其 builder 配置并创建 HttpClient 实例:
当我们调用 .version(HttpClient.Version.HTTP_3) 时,我们指示客户端在发出请求时优先使用 HTTP/3。默认情况下,Java 26 使用 HTTP/2,因此如果不需要 HTTP/3,现有应用程序无需更改。
3.2. 协议发现
如果将客户端设置为使用 HTTP/3,客户端需要发现服务器是否支持它。 由于 HTTP/3 运行在基于 UDP 的传输协议 QUIC 之上,而 HTTP/1.1 和 HTTP/2 运行在 TCP 之上,客户端无法像在较旧版本之间切换那样,将现有连接升级到 HTTP/3。
我们通过设置 HttpOption.H3_DISCOVERY 来选择客户端针对每个请求发现 HTTP/3 的方式。支持三种模式:
- ANY(默认):客户端使用自己的算法建立连接。它可能尝试通过 QUIC 使用 HTTP/3,并通过 TLS/TCP 使用 HTTP,并采用最先成功的那个。
- HTTP_3_URI_ONLY:客户端直接尝试使用请求 URI 中的主机和端口进行 HTTP/3,而不使用 Alternative Services。仅当服务器已经在该端口上监听 HTTP/3 时,此方式才会成功。
- ALT_SVC:客户端仅依赖 HTTP Alternative Services 来发现 HTTP/3。服务器通过 RFC 7838 中定义的 HTTP Alternative Services 来通告 HTTP/3。服务器可以通过 HTTP/1.1 或 HTTP/2 响应第一个请求,并包含一个 Alt-Svc 头帧,其中指定了 h3 端点。 该头告诉客户端,同一资源可通过 HTTP/3 在给定的主机和端口上访问。然后,客户端可以通过 HTTP/3 发送后续请求。 或者,服务器也可以通过 HTTP/2 ALTSVC 帧通告 HTTP/3。
如果我们不设置 H3_DISCOVERY,客户端默认使用 ANY。此选项仅在 HTTP/3 是首选版本(在客户端或请求上设置)时有效。
3.3. 发送请求
让我们看看实际效果。我们的 fetch() 方法构建并发送一个 HttpRequest:
这里,我们使用 setOption() 设置发现模式。我们使用 HTTP_3_URI_ONLY 直接尝试连接服务器主机和端口上的 HTTP/3,而不等待服务器通过 Alt-Svc 通告它。
请注意,在我们的示例中,HttpOption.H3_DISCOVERY 会生效,是因为我们将 HTTP/3 设置为客户端的首选版本。如果客户端和请求都没有优先选择 HTTP/3,客户端将忽略 H3_DISCOVERY。
BodyHandlers.ofString() 指示客户端将响应体作为字符串读取。客户端使用 Content-Type 头中指定的字符集解码响应;如果未提供有效字符集,则回退到 UTF-8。
我们继续处理与使用 HttpClient 时相同的受检异常:网络或协议故障时抛出 IOException,以及 InterruptedException(因为 send() 方法会阻塞调用线程)。
4. 协议选择与错误处理
设置 HTTP/3 偏好并不能保证客户端一定会使用它。让我们讨论客户端如何选择协议版本,以及如何告知我们它无法使用 HTTP/3。
4.1. 客户端如何选择协议版本
与 HTTP/2 一样,有多个因素决定每个请求实际使用的协议版本。
HTTP/3 默认处于关闭状态。我们在构建客户端或单个请求时,将首选版本设置为 HTTP/3 来启用它。一旦启用 HTTP/3,发现模式会指导客户端如何建立交换。如果我们不设置模式,客户端将使用默认模式。
但是,有一些例外情况会覆盖我们的偏好:
- 除非 URI 使用 https 方案,否则客户端绝不会通过 HTTP/3 发送请求。
- 使用代理时,它也会跳过 HTTP/3。
4.2. 处理 UnsupportedProtocolVersionException
有时,客户端不支持 HTTP/3。这样的客户端可能抛出 UnsupportedProtocolVersionException:
- 在构建客户端并将 HTTP/3 作为其首选版本时
- 在发送启用了 HTTP/3 但发现模式设置为 HTTP_3_URI_ONLY 的请求时
由于我们的示例使用 HTTP_3_URI_ONLY,生产代码应在 send() 调用周围处理此异常,同时还要处理我们已经处理的 IOException 和 InterruptedException。
5. 使用单元测试验证 HTTP/3 支持
让我们验证我们的 HTTP/3 客户端能够处理受支持和不受支持的场景。
5.1. 验证有效的 HTTP/3 端点
让我们向 https://cloudflare-quic.com/ 发送请求,这是 Cloudflare 为 HTTP/3 实验提供的公共端点:
我们使用 Http3Demo.fetch() 调用该端点,并期望获得成功的 200 OK 响应。然后,我们使用 response.version() 验证客户端是否成功协商了 HTTP/3。
5.2. 拒绝纯 HTTP 端点
在此测试中,我们使用纯 HTTP 启动本地服务器,并尝试通过 HTTP/3 客户端获取它。我们期望该请求抛出 UnsupportedProtocolVersionException:
发生这种情况是因为 HTTP/3 需要安全的 HTTPS 连接,并使用 QUIC 作为其传输协议,而我们的本地服务器仅支持 HTTP。
6. 结论
在本文中,我们了解了 Java 26 的 HttpClient 中对 HTTP/3 的支持;我们通过在 builder 上将版本设置为 HTTP/3 来启用它。
HttpClient API 的其余部分保持不变。HTTP/2 仍是默认值,因此我们现有的代码无需修改即可继续工作。当服务器不支持 HTTP/3 时,客户端在某些情况下可以改用 HTTP/2 或 HTTP/1.1。
该 API 还支持多种发现模式,以便更好地控制协议选择。
一如既往,本文使用的代码可在 GitHub 上获取。
文章 Java 26 中 HTTP Client API 的 HTTP/3 支持 首次出现在 Baeldung 上。