长洪斗落生跳波,轻舟南下如投梭。水师绝叫凫雁起,乱石一线争磋磨。
—— 苏轼

做技术不能闭门造车,要睁眼看看技术世界最流行的技术是怎么设计演化的 —— 跟着高手才有成长,做到吸收技巧、学习规律、跟上思路,日积月累涓滴之功。

下面就看一个 JEP(JDK Enhancement Proposal)517,初步了解 Java JDK 是怎么做设计演化的 —— 不要把它看做阳春白雪,可以尝试着将这些设计思路应用到日常工作中。

原文: JEP 517: HTTP/3 for the HTTP Client API

摘要

更新 HTTP Client API 以支持 HTTP/3,让库和应用通过最小的代码改动就可以和使用 HTTP/3 的服务器交互。

目标

  • 更新 HTTP Client API 以支持收发 HTTP/3 请求和响应。
  • 只对 HTTP Client API 和应用代码引入最小改动。
  • 不把默认协议版本从 HTTP/2 改为 HTTP/3,而是让开发者可以选择使用 HTTP/3。

非目标

  • 不在这里为 QUIC 协议提供 API —— HTTP/3 底层基于 QUIC。
  • 不在这里为第三方的安全套接字 providers 提供支持。
  • 不在这里提供 HTTP/3 的服务端实现。
  • 不在这里更新遗留 java.net.URL 中使用的 protocol handler。

动机

JEP 321(JDK 11)为 Java 平台添加了现代的 HTTP Client API。这套 API 支持 HTTP/1.1 和 HTTP/2,并设计用最小的改动来支持未来的 HTTP 版本。这套 API 默认选择 HTTP/2 协议,但是当服务端不支持时,将透明降级为 HTTP/1.1。

HTTP Client API 使得编写代码和 HTTP 服务器交互变得简单。比如,发送一个 GET 请求到 https://openjdk.org/ 并将响应转为一个 string:

import java.net.http.*;
...
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create("https://openjdk.org/"))
                         .GET().build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
assert response.statusCode() == 200;

这里使用 API 时不需要显式指明依赖的 HTTP 协议版本。应用代码对协议无感。

不幸的是,HTTP Client API 不支持最新的 HTTP/3 协议 —— 2022 年被 IETF 标准化为继 HTTP/2 后的下一代协议。HTTP/3 基于 QUIC 可靠传输协议而不是 TCP。TLS 1.3 支持 QUIC 协议。

支持 HTTP/3 将使得使用 HTTP Client API 的应用从 HTTP/3 提供的改进中受益

  • 潜在更快的握手
  • 避免网络拥塞问题,比如队头阻塞
  • 更可靠的传输,特别是在高丢包率的环境

HTTP/3 已经被绝大部分浏览器支持,同时部署在了大约 1/3 的网站上。Java 平台应该在客户端使用上提供支持。

描述

使用 HTTP/3 发送请求,需要手工选择使用它。

可以通过设置 HttpClient 对象的协议版本为 HTTP/3 来启用它,client 发送的所有请求都将默认首选 HTTP/3:

var client = HttpClient.newBuilder()
                       .version(HttpClient.Version.HTTP_3)
                       .build();

也可以针对单个 HttpRequest 对象设置优先选择 HTTP/3:

var request = HttpRequest.newBuilder(URI.create("https://openjdk.org/"))
                         .version(HttpClient.Version.HTTP_3)
                         .GET().build();

不需要再做别的改动。选择 HTTP/3 作为默认版本后,不管是在 request 对象还是 client 对象里,发送 request 都和之前一样。如果目标服务器尚未支持 HTTP/3,则请求将默认透明降级为 HTTP/2 或者 HTTP/1.1。

协商 HTTP 版本

不可能提前知道目标服务器是否支持 HTTP/3。也不可能将已经建立的 HTTP/1.1 或 HTTP/2 连接升级为 HTTP/3 连接,因为 HTTP/1.1 和 HTTP/2 是基于 TCP 流,而 HTTP/3 的 QUIC 是基于 UDP 报文。

但是,HTTP Client API 是否能判断对目标服务器使用 HTTP/3 ?有 4 种方案:

  • 使用 HTTP/3 发送第一个请求。如果无法在合理的时间内建立 HTTP/3 连接,则降级为 HTTP/2 或 HTTP/1.1。(当 HttpRequest 对象设置了优先使用 HTTP_3)
  • 发送第一个请求时,同时尝试建立 HTTP/3 连接和 HTTP/2(或 HTTP/1.1 连接)。使用第一个成功建立的连接。(当 HttpClient 设置了优先使用 HTTP_3 同时 HttpRequest 没有设置优先选择项)
  • 使用 HTTP/2 或 HTTP/1.1 发送第一个请求。如果服务端的响应提示 HTTP/3 属于一个可选服务,则后续所有请求都使用 HTTP/3。(当 H3_DISCOVERY 请求项的值为 Http3DiscoveryMode.ALT_SVC 且 HttpClient 和 HttpRequest 中至少一个设置了优先选择 HTTP_3)
  • 使用 HTTP/3 发送所有请求。如果服务端没有通过 HTTP/3 响应,则失败,且不降级为 HTTP/1.1 或 HTTP/2。(当 H3_DISCOVERY 请求项的值为 Http3DiscoveryMode.HTTP_3_URI_ONLY 且 HttpClient 和 HttpRequest 中至少一个设置了优先选择 HTTP_3)

这些方式有各自的权衡:方案 1 需要等待一个超时时间,方案 2 可能加载了一个 HTTP/3 实现并建立了一个 HTTP/3 连接但后续不再使用它,方案 3 初始需要使用 HTTP/2 或 HTTP/1.1,方案 4 只有在你提前知道目标服务器支持 HTTP/3 的情况下才奏效。

因为 HTTP/3 不像 HTTP/2 和 HTTP/1.1 那样被广泛部署,所以没有一种放之四海而皆准的方案。因此我们这次不会将 HTTP/3 作为默认配置,但是以后可能会。

未来的增强

  • 启用有限的配置和调优 HTTP/3 实现,例如通过 JDK 特定的系统属性或者小的新 API。
  • 为 HTTP/3 的特定异常场景定义新的 IOException、SSLException 的子类。
  • 增强 PushPromiseHandler 接口以支持 HTTP/3 的能力——在使用同一个连接的不同的请求/响应流之间共享 promise 响应。
  • 为 HttpClient.Builder 和 HttpRequest.Builder 增加配置选项,以控制目标服务器的发现。

测试

我们会做广泛的单元测试和集成测试。我们也会和各种服务端 HTTP/3 实现—— Netty、quic-go、quiche、Neqo、nghttp3,做互操作性测试。

风险和假设

第一版 HTTP/3 实现除了默认的安全套接字实现 SunJSSE 外,不会支持其他的安全套接字实现。要支持第三方安全套接字实现需要往实现的 SPI 中添加方法,这样这些实现的维护者就需要实现这些方法。我们可能在未来处理这个事情。

依赖

QUIC 协议定义:

  • RFC 8999:Version-Independent Properties of QUIC
  • RFC 9000:A UDP-Based Multiplexed and Secure Transport
  • RFC 9001:Using TLS to Secure QUIC
  • RFC 9002:QUIC Loss Detection and Congestion Control

HTTP/3 协议定义:

  • RFC 9114:HTTP/3
  • RFC 9204:QPACK:Field Compression for HTTP/3

下面的几个 RFC 关注于 QUIC 端点发现和测算网络 MTU。第一版实现支持 HTTP 可选服务发现但是不支持路径 MTU 发现。

  • RFC 7838:HTTP Alternative Services
  • RFC 8899:Packetization Layer Path MTU Discovery for Datagram Transports
  • RFC 4821:Packetization Layer Path MTU Discovery

第一版实现还支持了两个额外的 RFC:

  • RFC 9368:Compatible Version Negotiation for QUIC
  • RFC 9369:QUIC Version 2

分类: CODE

0 条评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注