HTTP/3#

Клиент HTTP/3 (RFC 9114) работает поверх стека QUIC (RFC 9000/9001/9002) и кодека QPACK (RFC 9204), реализованных в этом репозитории — без quic-go, без cgo. Из стандартной библиотеки берётся только сам хендшейк TLS 1.3 (crypto/tls); защита пакетов, восстановление после потерь, управление перегрузкой и flow control — код poseidon.

API запросов — те же Do / DoStream, что и в HTTP/2. client.Request и client.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-стриме. Вызывайте Do / DoStream из параллельных горутин на одном клиенте; варианты с пулом и с service discovery дополнительно распределяют стримы по соединениям.

Шифросьюты#

Для защиты пакетов QUIC поддерживаются все три AEAD-сьюта TLS 1.3: AES-128-GCM, AES-256-GCM и ChaCha20-Poly1305. Сьют выбирает сервер во время хендшейка; на клиенте настраивать ничего не нужно. Сьют вне этого набора (например, TLS_AES_128_CCM_8_SHA256) даёт ошибку при установке ключей — типизированную quic.ErrCryptoSuite — без зависаний и паник.

QPACK#

Сжатие заголовков — динамический QPACK в обе стороны: клиент кодирует заголовки запросов по динамической таблице и декодирует вставки сервера на decoder-стриме. Всё происходит автоматически; настроек нет.

Аллокации#

Wire-кодек HTTP/3 не аллоцирует: QUIC-фреймы и заголовки пакетов, HTTP/3-фреймы и секции полей QPACK кодируются и декодируются за 0 B/op, 0 allocs/op. Тот же CI bench-гейт, что обеспечивает это для кодека фреймов HTTP/2 и HPACK, покрывает Go-пакеты qpack, quic и http3. Исключение — путь отправки QUIC-пакетов: сборка и шифрование исходящего пакета стоит небольшого, ограниченного числа аллокаций на пакет. Поэтому запрос по HTTP/3 обходится малым числом аллокаций, но не нулём.

Управление перегрузкой#

По умолчанию — NewReno. BBR включается явно:

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

BBR реализован корректно и покрыт тестами, но его выигрыш в пропускной способности по сравнению с NewReno виден только на WAN-пути с узким местом и реальной задержкой в очередях. На LAN или loopback разницы вы не намеряете. Если проверить целевой путь бенчмарком негде — оставьте NewReno по умолчанию.

Пакетный ввод-вывод на Linux#

На Linux транспорт QUIC использует GSO (generic segmentation offload), чтобы отдавать ядру пачки исходящих пакетов одним сисколлом, и GRO для приёма склеенных пачек. Оба механизма включаются сами — без конфигурации и build-тегов. На остальных платформах клиент отправляет и принимает по одной датаграмме на сисколл.

Вне рамок#

Сознательно не входит в 1.0:

  • 0-RTT / возобновление сессии — каждое соединение проходит полный хендшейк.
  • Миграция QUIC-соединения — соединение привязано к пути, на котором открыто.
  • Server push в HTTP/3 — никогда не включается.

Клиент ничего из этого не инициирует. Если пир предлагает такую возможность, она просто не используется; ничего не ломается, ошибок не возникает. Единственный жёсткий отказ в этой области — неподдерживаемый шифросьют, который возвращает типизированную quic.ErrCryptoSuite, описанную выше.