HTTP-3

HTTP-3.0

HTTP/3 弃用 TCP 协议, 改为使用基于 UDP 协议的 QUIC 协议和类似于 HTTP/2 的内部成帧层为 HTTP 语义提供传输。

为什么弃用 TCP

HTTP/2 引入了二进制成帧和复用层,以在不修改传输层的情况下改善延迟。 然而,由于 HTTP/2 多路复用的并行特性对于 TCP 的丢失恢复机制来说是不可见的,因此丢失或重新排序的数据包会导致所有活动事务遇到停顿,无论该事务是否直接受到丢失数据包的影响。

什么是 QUIC 协议

QUIC 是基于 UDP 的多路复用安全传输

QUIC 传输协议结合了流复用和每流流量控制,类似于 HTTP/2 帧层提供的功能。通过提供流级别的可靠性和整个连接的拥塞控制,与 TCP 映射相比,QUIC 能够提高 HTTP 的性能。 QUIC 还在传输层整合了 TLS 1.3 的连接设置延迟。

流(Stream)

HTTP/3 基于 QUIC 定义了三种流类型:控制流,请求流,推送流。

QUIC 流提供可靠的按顺序传输字节,但不保证其他流上字节的传输顺序。

传输层缓冲并排序接收到的流数据,向应用程序公开可靠的字节流。 尽管 QUIC 允许流中的无序传送,但 HTTP/3 并未使用此功能。

当 HTTP 字段和数据通过 QUIC 发送时,QUIC 层处理大部分流管理。

双向流

所有客户端发起的双向流都用于 HTTP 请求和响应。 双向流确保响应可以轻松地与请求相关联。 这些流称为请求流(Request Stream)

这意味着客户端的第一个请求发生在 QUIC 流 0 上,后续请求发生在流 4、8 ...。

HTTP/3 服务器应该为允许的流数量和初始流流量控制窗口配置非零最小值。 为了避免不必要地限制并行性,一次至少应该允许 100 个请求流

HTTP/3 不使用服务器启动的双向流。 客户端将收到服务器发起的双向流视为类型为 H3_STREAM_CREATION_ERROR 的连接错误。

单向流

任一方向的单向流可用于多种目的。 可以通过 HTTP/3 的扩展来定义。

客户端和服务器发送的传输参数必须允许对方创建至少三个单向流:

  • 控制流

  • QPACK 编码器流

  • QPACK 解码器流

每个单向流提供至少 1024 字节的可用流量。

建关键单向流之前消耗了所有初始信用,端点应该创建 HTTP 控制流首先创建强制扩展所需的单向流。

除非另有说明,发送方可以关闭或重置单向流。 接收方必须允许单向流在接收到单向流标头之前被关闭或重置。

控制流(Control Streams)

控制流由流类型 0x00 指示。 该流上的数据由 HTTP/3 帧组成。

每一方必须在连接开始时启动单个控制流并发送其 SETTING 帧作为该流上的第一帧。 每个对等端点仅允许一个控制流。

控制流的内容用于管理其他流的行为,所以端点应该提供足够的流量以防止对等方的控制流被阻塞。

控制流使用一对单向流而不是单个双向流。这允许任一对等方尽快发送数据。

推送流(Push Streams)

服务器推送是 HTTP/2 中引入的一项可选功能,允许服务器在收到请求之前发起响应。

只有服务器可以推送,如果服务器接收到客户端发起的推送流,则必须将其视为类型为 H3_STREAM_CREATION_ERROR 的连接错误

该流上的其余数据由 HTTP/3 帧组成

Push Stream Header {
  Stream Type (i) = 0x01,
  Push ID (i),
}

每个 push_id 只能在推送流头中使用一次。 如果客户端检测到推流头中包含推送 ID 在另一个推送流标头中使用的,客户端必须将其视为类型为 H3_ID_ERROR 的连接错误。

客户端不应在读取推送流标头之前中止对推送流的读取,因为这可能会导致客户端和已使用 push_id 的服务器之间出现分歧。

对比 HTTP/2

与 HTTP/2 相比,HTTP/3 允许使用更多数量的流 (2 62 -1)。 HTTP/3 中的流并发由 QUIC 管理。

在 HTTP/2 中,只有请求和响应主体受到流量控制。 所有 HTTP/3 帧都在 QUIC 流上发送,因此所有流上的所有帧都在 HTTP/3 中进行流量控制。

由于其他单向流类型的存在,HTTP/3 并不完全依赖并发单向流的数量来控制并发进行中推送的数量。 相反,HTTP/3 客户端使用 MAX_PUSH_ID 帧来控制从 HTTP/3 服务器接收的推送数量。

帧(Frame)

帧(frame) 是 HTTP/3 中流上的最小通信单元,由标头和根据帧类型构造的可变长度字节序列组成。 每种帧类型都有不同的用途。

HTTP 帧在 QUIC 流上承载,HTTP/3 帧和流类型概述:

FrameControl StreamRequest StreamPush Stream

DATA

No

Yes

Yes

HEADERS

No

Yes

Yes

CANCEL_PUSH

Yes

No

No

SETTINGS

Yes (1)

No

No

PUSH_PROMISE

No

Yes

No

GOAWAY

Yes

No

No

MAX_PUSH_ID

Yes

No

No

所有帧都具有以下结构:

HTTP/3 Frame Format {
  Type (i), // 标识帧类型的可变长度整数。
  Length (i), // 描述帧有效负载字节长度的可变长度整数。
  Frame Payload (..), // 有效负载,其语义由 Type 字段确定。
}

DATA 帧

DATA 帧传送与 HTTP 请求或响应内容相关的任意、可变长度的字节序列。

DATA Frame {
  Type (i) = 0x00,
  Length (i),
  Data (..),
}

HEADERS 帧

HEADERS 帧用于携带使用 QPACK 编码的 HTTP 字段部分。

HEADERS Frame {
  Type (i) = 0x01,
  Length (i),
  Encoded Field Section (..),
}

CANCEL_PUSH 帧

CANCEL_PUSH 帧用于取消服务器推送,在推送流被接受到之前。

CANCEL_PUSH Frame {
  Type (i) = 0x03,
  Length (i),
  Push ID (i),
}

SETTINGS 帧

SETTINGS 帧传达影响端点通信方式的配置参数。

Setting {
  Identifier (i),
  Value (i),
}

SETTINGS Frame {
  Type (i) = 0x04,
  Length (i),
  Setting (..) ...,
}

设置帧必须作为每个控制流的第一帧发送,由每个对等方发送,并且不得随后发送。

设置帧不得在控制流以外的任何流上发送。

PUSH_PROMISE 帧

PUSH_PROMISE 帧用于在请求流中从服务器向客户端传输承诺的请求标头部分。

当客户端发送一个请求给服务器时,服务器可以选择性地使用 PUSH_PROMISE 来主动推送额外的资源给客户端。

PUSH_PROMISE Frame {
  Type (i) = 0x05,
  Length (i),
  Push ID (i),
  Encoded Field Section (..),
}

GOAWAY 帧

GOAWAY 帧(用于通过任一端点启动 HTTP/3 连接的正常关闭。 GOAWAY 允许端点停止接受新的请求或推送,同时仍完成对先前收到的请求和推送的处理。

GOAWAY Frame {
  Type (i) = 0x07,
  Length (i),
  Stream ID/Push ID (i),
}

MAX_PUSH_ID 帧

客户端使用 MAX_PUSH_ID 帧来控制服务器可以发起的服务器推送数量。

MAX_PUSH_ID Frame {
  Type (i) = 0x0d,
  Length (i),
  Push ID (i),
}

对比 HTTP/2 的帧

HTTP/2 中的许多帧概念可以在 QUIC 上省略。 包括 PRIORITYRST_STREAMEND_STREAMPINGWINDOW_UPDATECONTINUATION

HTTP/2 在所有流中的帧之间提供绝对排序,而 QUIC 仅在每个流上提供这种保证。 因此,如果帧类型假设仍会按照发送的顺序接收来自不同流的帧,则 HTTP/3 将破坏它们。

在 HTTP/3 中表达 HTTP 语义

HTTP 报文结构

类似于 HTTP/2,HTTP/3 中的 HTTP 消息(请求或响应)包含:

  • 标头部分,包括消息控制数据,单个 HEADER 帧;

  • 内容,由一系列 DATA 帧组成;

  • 预告部分,单个 HEADER 帧。

客服端请求

客户端在请求流上发送 HTTP 请求,这是客户端发起的双向 QUIC 流。 一个 HTTP 请求对应一个请求流,在给定的流上,收到多个请求视为格式错误。

服务器在与请求流上发送零个或多个临时 HTTP 响应,然后发送一个最终 HTTP 响应。 收到最终 HTTP 响应之后的附加 HTTP 响应视为格式错误。

HTTP 请求/响应交换完全消耗客户端发起的双向 QUIC 流。 发送请求后,客户端必须关闭“发送流”。 除非使用 CONNECT 方法,否则客户端不得依赖于接收对其请求的响应来关闭流。 发送最终响应后,服务器必须关闭“发送流”。 至此,QUIC 流完全关闭。

由于某些消息很大或不受限制,一旦收到足够的消息以取得进展,端点就应该开始处理部分 HTTP 消息。 如果响应不依赖于请求中尚未发送和接收的任何部分,则服务器可以在客户端发送整个请求之前发送完整的响应。

请求取消和拒绝

当服务器不需要接收请求的其余部分时,它可以中止读取请求流,发送完整的响应,并干净地关闭流的发送部分

一旦请求流已经打开,请求可以被任一端点取消。 如果客户不再对响应感兴趣,则取消请求; 如果服务器无法响应或选择不响应,则会取消请求。 如果可能,建议服务器发送带有适当状态代码的 HTTP 响应,而不是取消已经开始处理的请求。

如果流在收到完整响应后被取消,客户端可以忽略取消并使用响应。

HTTP 字段

与 HTTP/2 一样,HTTP/3 对于字段名称、连接标头字段和伪标头字段中的字符使用也有其他注意事项。 将 HTTP/1.x 消息转换为 HTTP/3 的中介必须删除连接特定的标头字段

HTTP/3 不使用 Connection 标头字段来指示特定于连接的字段;在此协议中,特定于连接的元数据通过其他方式传送。

HTTP 字段压缩

与 HTTP/2 一样,HTTP/3 使用使用一系列伪标头字段,压缩消息的标头和尾部部分,包括标头部分中存在的控制数据。

由于 HPACK 依赖于压缩字段部分的按序传输(QUIC 未提供保证), 因此 HTTP/3 用 QPACK 替换 HPACK。 QPACK 使用单独的单向流来修改和跟踪字段表状态,而编码字段部分引用表的状态而不修改它。

HTTP 升级

HTTP/3 不支持 HTTP 升级机制

服务器推送

在 HTTP/3 中,推送流的机制有所不同。服务器在首次接收到客户端的请求时,可以直接发送 PUSH_PROMISE 帧来创建一个推送流。 这个 PUSH_PROMISE 帧会指示客户端将要推送的资源和关联的流 ID。 客户端在接收到 PUSH_PROMISE 帧后可以决定是否接受这个推送,并建立相应的推送流来获取资源。

服务器可能在一个响应消息之前、中间、之后,发送一个或多个 PUSH_PROMISE 帧,这些 PUSH_PROMISE 帧不是响应的一部分。

服务器以与标准响应相同的方式发送零个或多个临时 HTTP 响应,然后发送单个最终 HTTP 响应。 收到最终 HTTP 响应之后的附加 HTTP 响应视为格式错误。

HTTP/3 链接

HTTP 替代服务

HTTP 源如何通告等效 HTTP/3 端点:

  • 使用 ALPN 令牌 h3

  • 通过 HTTP 响应标头字段 Alt-Svc

  • HTTP/2 ALTSVC 帧。

例如,源可以在 HTTP 响应中通过包含以下标头字段来指示 HTTP/3 在同一主机名的 UDP 端口 50781 上可用:

Alt-Svc: h3=":50781"

收到指示 HTTP/3 支持的 Alt-Svc 记录后,客户端可以尝试建立到指定主机和端口的 QUIC 连接;如果此连接成功,客户端可以使用本文档中描述的映射发送 HTTP 请求。

建立链接

HTTP/3 依赖 QUIC v1 作为底层传输。 QUIC v1 使用 TLS v1.3 或更高版本作为其握手协议

如果服务器由域名标识,则客户端必须发送服务器名称指示(SNI)TLS 扩展, 除非使用替代机制来指示目标主机。

在连接建立期间,通过在 TLS 握手中选择 ALPN 令牌 h3 来表明支持 HTTP/3

除非使用其他机制来选择 HTTP/3,否则 h3 将在 TLS 握手期间用于应用层协议协商

虽然与核心 QUIC 协议相关的连接级选项是在初始加密握手中设置的。 特定于 HTTP/3 的设置是在 SETTINGS 帧中, 建立链接成功后,SETTINGS 必须由每个端点作为其各自 HTTP 控制流的初始帧发送。

服务降级

连接问题(例如,阻止 UDP)可能会导致无法建立 QUIC 连接;在这种情况下,客户端应该尝试使用基于 TCP 的 HTTP 版本。

链接复用

HTTP/3 连接在多个请求中是持久的。 为了获得最佳性能,预计客户端不会关闭连接,直到确定不需要与服务器进行进一步通信(例如,当用户离开特定网页时)或直到服务器关闭连接。

链接建立成功后,多个不同 URI 权限组件的请求可以在同一链接里。

要将现有连接用于新源(New Origin),必须验证服务器为新源服务器提供的证书。 如果由于任何原因该证书对于新源不可接受,则不得重用该连接,并且应该为新源建立新连接。

客户端不应打开多个到给定 IP 地址和 UDP 端口的 HTTP/3 连接。 其中 IP 地址和端口可能源自 URI、选定的替代服务、配置的代理或名称解析。

客户端可以使用不同的传输或 TLS 配置打开到相同 IP 地址和 UDP 端口的多个 HTTP/3 连接, 但应避免使用相同的配置创建多个连接。

鼓励服务器尽可能长时间地保持开放的 HTTP/3 连接,但允许在必要时终止空闲连接。

关闭连接

一旦建立,HTTP/3 连接可用于随着时间的推移进行许多请求和响应,直到连接关闭。

空闲连接

每个 QUIC 端点在握手期间声明空闲超时。如果 QUIC 连接保持空闲状态(未收到数据包)的时间超过此持续时间,则对等方将假定连接已关闭。

如果现有连接的空闲时间超过了 QUIC 握手期间协商的空闲超时,则 HTTP/3 实现将需要为新请求打开一个新的 HTTP/3 连接

当请求或服务器推送有未完成的响应时,HTTP 客户端应请求传输保持连接打开。

如果客户端不期望服务器响应,那么允许空闲连接超时比花费精力维护可能不需要的连接更好。 网关可以在预期需要时维持连接,而不是产生与服务器建立连接的延迟成本。 服务器不应该主动保持连接打开。

连接关闭

即使连接不空闲,任一端点也可以决定停止使用该连接并发起正常的连接关闭。

当任一端点选择关闭 HTTP/3 连接时,终止端点应该首先发送 GOAWAY 帧,以便两个端点都可以可靠地确定先前发送的帧是否已被处理,并优雅地完成或终止任何必要的剩余任务。

参考

最后更新于