HTTP/3#

HTTP/3 客户端(RFC 9114)运行在本仓库自行实现的 QUIC 协议栈(RFC 9000/9001/9002)和 QPACK 编解码器(RFC 9204)之上——不依赖 quic-go,不使用 cgo。只有 TLS 1.3 握手本身来自标准库 crypto/tls;数据包保护、丢包恢复、拥塞控制和流量控制均为 poseidon 自己的代码。

请求 API 与 HTTP/2 完全相同,仍是 Do / DoStreamclient.Requestclient.Response 在不同传输层之间没有任何区别;把一个压测从 H2 切换到 H3,只需换一个构造函数。

示例#

examples/http3/main.go

// Command http3-example issues a single HTTP/3 GET over QUIC with the poseidon
// client, and shows how to opt into BBR congestion control.
//
//	go run ./examples/http3
package main

import (
	"context"
	"crypto/tls"
	"fmt"
	"log"
	"time"

	"github.com/lodgvideon/poseidon-http-client/client"
	"github.com/lodgvideon/poseidon-http-client/quic"
)

func main() {
	// The simple path: one QUIC connection, buffered response.
	//
	//	c, err := client.NewH3Client("www.cloudflare.com:443",
	//	    &tls.Config{ServerName: "www.cloudflare.com"})
	//
	// Below uses NewClient so we can also select BBR congestion control.
	c, err := client.NewClient(client.ClientOptions{
		Addr:      "www.cloudflare.com:443",
		Transport: client.TransportH3,
		TLSConfig: &tls.Config{ServerName: "www.cloudflare.com"},
		H3ConnOptions: []quic.ConnOption{
			quic.WithCongestionControl(quic.CCBBR), // default is NewReno
		},
	})
	if err != nil {
		log.Fatalf("build client: %v", err)
	}
	defer func() { _ = c.Close() }()

	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	resp := &client.Response{}
	if err := c.Do(ctx, client.GET("/"), resp); err != nil {
		log.Fatalf("GET /: %v", err)
	}
	fmt.Printf("HTTP/3 %d — %d bytes\n", resp.Status, len(resp.Body))
}

构造函数#

三个构造函数,与 HTTP/2 的一一对应:

// One QUIC connection. Buffered (Do) and streaming (DoStream) requests.
client.NewH3Client(addr string, tlsConfig *tls.Config, opts ...Option) (*Client, error)

// Pool of QUIC connections to one host.
client.NewH3PoolClient(addr string, tlsConfig *tls.Config, pool PoolOptions, opts ...Option) (*Client, error)

// Service discovery: a Resolver supplies addresses, requests spread across them.
client.NewManagedH3Client(resolver Resolver, tlsConfig *tls.Config, opts ...Option) (*Client, error)

对于这些构造函数没有暴露的设置——比如拥塞控制——请改用 client.NewClient(client.ClientOptions{...}) 并设置 Transport: client.TransportH3,如上面的示例所示。

并发请求#

一条 HTTP/3 连接可以同时承载多个在途请求,每个请求走独立的 QUIC 流。可以从多个 goroutine 并发地对同一个客户端调用 Do / DoStream;池化和托管两种变体还会进一步把流分散到多条连接上。

密码套件#

QUIC 数据包保护支持全部三种 TLS 1.3 AEAD 套件:AES-128-GCM、AES-256-GCM 和 ChaCha20-Poly1305。由服务器在握手时选择,客户端无需任何配置。若协商出此范围之外的套件(例如 TLS_AES_128_CCM_8_SHA256),会在密钥安装阶段以类型化错误 quic.ErrCryptoSuite 失败——不会挂起,不会 panic。

QPACK#

头部压缩在两个方向上都使用动态表 QPACK:客户端针对动态表编码请求头,并在 decoder 流上解码服务器的插入。这一切自动完成,没有需要调节的开关。

内存分配#

HTTP/3 线路编解码器是零分配的:QUIC 帧和数据包头、HTTP/3 帧以及 QPACK 字段区段的编解码均为 0 B/op、0 allocs/op。对 HTTP/2 帧和 HPACK 编解码器施加这一约束的同一个 CI 基准门禁,同样覆盖 qpackquichttp3 包。QUIC 数据包发送路径是例外:构建并加密封装一个出站数据包会产生少量、有上界的分配。因此一次 HTTP/3 请求是低分配,而非零分配。

拥塞控制#

默认是 NewReno。BBR 可以按需启用:

H3ConnOptions: []quic.ConnOption{
	quic.WithCongestionControl(quic.CCBBR),
},

BBR 的实现正确且经过测试,但它相对 NewReno 的吞吐优势只在存在真实排队延迟的瓶颈 WAN 路径上才体现得出来。在局域网或 loopback 上测不出差异。如果无法在目标路径上做基准测试,就保持 NewReno 默认值。

Linux 批量 I/O#

在 Linux 上,QUIC 传输层使用 GSO(generic segmentation offload)在一次系统调用中把成批的出站数据包交给内核,并用 GRO 接收合并后的批次。两者都是自动的——无需配置,无需 build tag。在其他平台上,客户端每次系统调用只收发一个数据报。

非目标#

1.0 有意不包含以下功能:

  • 0-RTT / 会话恢复 —— 每条连接都执行完整握手。
  • QUIC 连接迁移 —— 连接绑定在建立时所用的路径上。
  • HTTP/3 服务器推送 —— 从不启用。

客户端从不主动发起以上任何一项。对端即使提供这些能力,也只是不被使用;不会失败,也不会产生错误。该领域唯一的硬性失败是不支持的密码套件,它会返回上文所述的类型化错误 quic.ErrCryptoSuite