HTTP Versions: HTTP/1.1, HTTP/2, HTTP/3, QUIC
Зачем знать: Понимание разницы между версиями HTTP критично для performance-чувствительных систем. HTTP/2 — стандарт для gRPC, HTTP/3 — для CDN и mobile. Без знания multiplexing, HPACK, QUIC streams невозможно правильно настроить продакшен-сервер, диагностировать head-of-line blocking или объяснить, почему gRPC требует HTTP/2.
Содержание
Заголовок раздела «Содержание»- Базовая концепция
- Глубокое погружение (под капотом)
- Gotchas (12+)
- Production-практики
- Вопросы (25+)
- Practice
- Источники
1. Базовая концепция
Заголовок раздела «1. Базовая концепция»Эволюция HTTP
Заголовок раздела «Эволюция HTTP»HTTP/0.9 (1991) → только GET, plain textHTTP/1.0 (1996) → headers, methods, status codesHTTP/1.1 (1997) → keep-alive, pipelining, chunked encoding, Host headerHTTP/2 (2015) → binary framing, multiplexing, HPACK, server push (RFC 7540 → 9113)HTTP/3 (2022) → QUIC, UDP-based, 0-RTT, без head-of-line blocking (RFC 9114)Ключевые отличия одной таблицей
Заголовок раздела «Ключевые отличия одной таблицей»| Свойство | HTTP/1.1 | HTTP/2 | HTTP/3 |
|---|---|---|---|
| Транспорт | TCP | TCP | QUIC (UDP) |
| Encoding | Text | Binary frames | Binary frames |
| Multiplexing | Нет | Да (streams) | Да (streams) |
| Header compression | Нет | HPACK | QPACK |
| Server push | Нет | Да (deprecated) | Да |
| HoL blocking (transport) | Да | Да (TCP-level) | Нет |
| TLS | Опционально | Де-факто обязателен | Встроен в QUIC |
| Handshake RTT | 1-3 (TCP+TLS) | 2-3 | 0-1 (0-RTT возможен) |
HTTP/1.1: persistent connections и pipelining
Заголовок раздела «HTTP/1.1: persistent connections и pipelining»Persistent connections (keep-alive) — переиспользование TCP соединения для нескольких запросов:
Client Server | --- GET /index.html --- > | | < -- 200 OK ----------- | | | (соединение НЕ закрыто) | | --- GET /style.css ---- > | | < -- 200 OK ----------- | |В Go клиент по умолчанию делает keep-alive:
package main
import ( "fmt" "io" "net/http" "time")
func main() { // http.DefaultClient использует http.DefaultTransport // который держит keep-alive pool на 100 idle conns client := &http.Client{ Timeout: 10 * time.Second, Transport: &http.Transport{ MaxIdleConns: 100, MaxIdleConnsPerHost: 10, IdleConnTimeout: 90 * time.Second, DisableKeepAlives: false, // keep-alive enabled }, }
for i := 0; i < 3; i++ { resp, err := client.Get("https://example.com/") if err != nil { panic(err) } io.Copy(io.Discard, resp.Body) resp.Body.Close() fmt.Println(i, resp.Proto) }}Pipelining — отправить несколько запросов, не дожидаясь ответов:
Client: --- GET /a ---> --- GET /b ---> --- GET /c --->Server: <-- 200 a --- <-- 200 b --- <-- 200 c ---⚠️ Pipelining почти нигде не работает на практике — большинство серверов/прокси не поддерживают, ответы должны строго в порядке (HoL blocking). В Go это отключено по умолчанию и удалено как фича.
Head-of-line blocking в HTTP/1.1: один медленный ответ блокирует следующие.
Compression в HTTP/1.1: только тело (gzip, deflate, br), headers сжимаются → каждый запрос ~ 600-1000 байт headers.
HTTP/2: binary framing и multiplexing
Заголовок раздела «HTTP/2: binary framing и multiplexing»Идея: одно TCP-соединение, на нём параллельно много логических streams.
HTTP/1.1 HTTP/2 ┌────────┐ ┌──────────────────┐ │ Conn 1 │--req/resp 1 │ Single TCP │ ├────────┤ │ Connection │ │ Conn 2 │--req/resp 2 │ ┌─── stream 1 │ ├────────┤ │ ├─── stream 3 │ │ Conn 3 │--req/resp 3 │ ├─── stream 5 │ │ ... │ │ ├─── stream 7 │ │ Conn 6 │ │ └─── stream 9 │ └────────┘ └──────────────────┘ 6 TCP (по 6 на host) 1 TCP, N streamsFrame types:
HEADERS— заголовки запроса/ответаDATA— телоSETTINGS— параметры соединенияWINDOW_UPDATE— flow controlPRIORITY— приоритеты streamsRST_STREAM— отмена streamPING— heartbeatGOAWAY— закрытие соединения
HPACK — сжатие headers через статическую таблицу (Static Table) общих заголовков и динамическую таблицу (Dynamic Table) для повторов.
Server push — сервер может отправить ресурс до запроса. Deprecated (RFC 9113 §8.4) — браузеры выключают, плохая адопция.
В Go: для HTTPS-сервера HTTP/2 включён автоматически с Go 1.6+:
package main
import ( "fmt" "net/http")
func main() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Proto: %s\nMajor: %d\nMinor: %d\n", r.Proto, r.ProtoMajor, r.ProtoMinor) })
// ListenAndServeTLS поднимет HTTP/2 автоматически через ALPN http.ListenAndServeTLS(":8443", "cert.pem", "key.pem", nil)}h2c (HTTP/2 over cleartext) — HTTP/2 без TLS. Не поддерживается стандартным net/http напрямую, нужна golang.org/x/net/http2/h2c:
package main
import ( "fmt" "net/http"
"golang.org/x/net/http2" "golang.org/x/net/http2/h2c")
func main() { h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "hello, proto=%s\n", r.Proto) }) h2s := &http2.Server{} srv := &http.Server{ Addr: ":8080", Handler: h2c.NewHandler(h, h2s), } srv.ListenAndServe()}⚠️ h2c важен для service-to-service: внутри кластера TLS terminate на ingress, бэкенду нужен h2c.
HTTP/3 и QUIC
Заголовок раздела «HTTP/3 и QUIC»HTTP/3 = HTTP-семантика + QUIC-транспорт. QUIC — это UDP-based надёжный транспорт со встроенным TLS 1.3.
┌─────────────────┐ ┌─────────────────┐ │ HTTP/2 │ │ HTTP/3 │ ├─────────────────┤ ├─────────────────┤ │ TLS │ │ │ ├─────────────────┤ │ QUIC │ │ TCP │ │ (TLS 1.3, │ ├─────────────────┤ │ streams, │ │ IP │ │ congestion) │ └─────────────────┘ ├─────────────────┤ │ UDP │ ├─────────────────┤ │ IP │ └─────────────────┘Преимущества HTTP/3:
- 0-RTT handshake при повторном соединении: client помнит keys → шлёт application data сразу.
- No HoL blocking на transport уровне: каждый QUIC stream имеет независимое flow control. Потеря пакета в одном streame не блокирует другие.
- Connection migration: при смене IP (Wi-Fi → LTE) соединение продолжает работать благодаря Connection ID.
- Improved congestion control: BBR, CUBIC встроены, обновляются без kernel.
В Go: стандартной библиотеки HTTP/3 нет, используют github.com/quic-go/quic-go и github.com/quic-go/http3:
package main
import ( "crypto/tls" "fmt" "net/http"
"github.com/quic-go/quic-go/http3")
func main() { mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "via HTTP/3, proto=%s\n", r.Proto) })
server := http3.Server{ Addr: ":4433", Handler: mux, TLSConfig: &tls.Config{}, // Загрузите cert/key } server.ListenAndServeTLS("cert.pem", "key.pem")}Клиент:
package main
import ( "crypto/tls" "fmt" "io" "net/http"
"github.com/quic-go/quic-go/http3")
func main() { client := &http.Client{ Transport: &http3.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, }, } resp, err := client.Get("https://example.com:4433/") if err != nil { panic(err) } defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) fmt.Println(resp.Proto, string(body))}⚠️ Production-адопция HTTP/3: Cloudflare, Google, Facebook, Cloudfront — все используют. Для приложения важно, что HTTP/3 advertised через Alt-Svc header в HTTP/2-ответе.
2. Глубокое погружение (под капотом)
Заголовок раздела «2. Глубокое погружение (под капотом)»HTTP/2: формат frame
Заголовок раздела «HTTP/2: формат frame»+-----------------------------------------------+| Length (24) |+---------------+---------------+---------------+| Type (8) | Flags (8) |+-+-------------+---------------+-------------------------------+|R| Stream Identifier (31) |+=+=============================================================+| Frame Payload (0...) ...+---------------------------------------------------------------+- Length — 24 бит длина payload (макс 2^24 - 1 = 16 МБ, по дефолту SETTINGS_MAX_FRAME_SIZE=16384).
- Type — тип frame (HEADERS=0x1, DATA=0x0, …).
- Flags — флаги типа END_STREAM, END_HEADERS.
- Stream Identifier — odd numbers от client, even от server (server push). Stream 0 — control.
HTTP/2: pseudo-headers
Заголовок раздела «HTTP/2: pseudo-headers»В HTTP/2 нет request line “GET /path HTTP/1.1”, вместо них pseudo-headers (начинаются с :):
Request::method: GET:scheme: https:authority: example.com:path: /index.html
Response::status: 200⚠️ Pseudo-headers ДОЛЖНЫ идти до обычных headers. Иначе RST_STREAM с PROTOCOL_ERROR.
HPACK: как сжимаются headers
Заголовок раздела «HPACK: как сжимаются headers»Static Table (предопределён, 61 запись):
1 :authority2 :method GET3 :method POST4 :path /5 :path /index.html6 :scheme http7 :scheme https8 :status 200...Dynamic Table — заполняется по мере общения. После первого “User-Agent: curl/7.88” → в таблице, последующие отправки = 1 байт индекс.
Кодирование: Huffman encoding для строк → ещё меньше.
HTTP/1.1 GET /index.html HTTP/1.1\r\nHost: example.com\r\nUser-Agent: curl/7.88.1\r\nAccept: */*\r\n → ~120 байт
HTTP/2 (после первого запроса):HEADERS frame: [82] (:method GET) [86] (:scheme https) [64] (:path /index.html)[1] (:authority) <indexed in dynamic table> → ~10-20 байт⚠️ HPACK гарантирует целостность через decoder state — если frame теряется/декодируется криво, весь connection broken.
HTTP/2: stream lifecycle
Заголовок раздела «HTTP/2: stream lifecycle» (send H) (send H + END_STREAM) -----------------> -----+ | | | v +---->idle--->reserved | | | | | | | v v | | half-closed --->closed | | (remote) | v | open <----------+ | | | | v | | half-closed | | (local) | | | | | +--->closed---+ | (recv RST_STREAM)Сервер может закрыть stream через RST_STREAM (например, отмена клиентом отправляет RST_STREAM с CANCEL).
HTTP/2: flow control
Заголовок раздела «HTTP/2: flow control»В отличие от TCP, у HTTP/2 свой flow control НА УРОВНЕ streams и connection:
Initial window = 65535 bytes (default)Client Server | ---DATA(15000)----> | | ---DATA(40000)----> | | (window остался 65535-15000-40000 = 10535) | | <----WINDOW_UPDATE(stream=1, increment=55000)----| | (window теперь 65535) |⚠️ HoL blocking в HTTP/2 на TCP уровне: если пакет TCP потерян, ВСЕ streams ждут retransmit. QUIC решает это.
QUIC: формат пакета
Заголовок раздела «QUIC: формат пакета»+-+-+-+-+-+-+-+-+|1|1|T|T|X|X|X|X| Long Header (handshake, version-negotiation)+-+-+-+-+-+-+-+-+| Version (32) |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+|DCID Len(8)| Destination Connection ID (0..160) ......Short header (после handshake) — без version, минимизация overhead.
QUIC streams vs HTTP/2 streams
Заголовок раздела «QUIC streams vs HTTP/2 streams»HTTP/2: stream IDs share TCP byte order [stream1 byte][stream2 byte][stream1 byte] -- если потерян пакет TCP — все streams ждут
HTTP/3: каждый QUIC stream имеет свой sequence потеря пакета влияет только на конкретный stream другие streams продолжают доставлять данныеQUIC connection migration
Заголовок раздела «QUIC connection migration»Connection идентифицируется не (IP+port), а Connection ID (DCID, SCID). Когда клиент переходит с Wi-Fi на 4G:
- IP меняется
- Connection ID остаётся
- Сервер видит пакет с тем же CID → продолжает session
- Перешифровка не нужна
0-RTT и replay attacks
Заголовок раздела «0-RTT и replay attacks»0-RTT данные могут быть replay’нуты атакующим:
Client → Server: 0-RTT данные [POST /buy product=X][Атакующий захватывает]Атакующий → Server: тот же 0-RTT пакет → дубликат заказа⚠️ Правило: 0-RTT использовать только для idempotent запросов (GET, OPTIONS, HEAD).
Сравнение производительности
Заголовок раздела «Сравнение производительности»| Сценарий | HTTP/1.1 | HTTP/2 | HTTP/3 |
|---|---|---|---|
| 100 параллельных GET (web page) | 6-12 conn | 1 conn | 1 conn |
| Page load (десктоп, fast network) | baseline | -15% | -10..-20% |
| Page load (mobile, lossy) | baseline | -10% | -30..-40% |
| Single large download | comparable | comparable | comparable |
| gRPC streaming | impossible | ok | ok |
| Initial handshake (cold) | 2-3 RTT | 2-3 RTT | 1 RTT |
| Initial handshake (warm, 0-RTT) | 1 RTT | 1 RTT | 0 RTT |
Browser support (2026)
Заголовок раздела «Browser support (2026)»| Браузер | HTTP/2 | HTTP/3 |
|---|---|---|
| Chrome | 41+ | 87+ (по умолчанию) |
| Firefox | 36+ | 88+ |
| Safari | 9+ | 14+ |
| Edge | да | да |
3. Gotchas (⚠️)
Заголовок раздела «3. Gotchas (⚠️)»-
⚠️ HTTP/2 prior knowledge vs ALPN:
- ALPN (Application-Layer Protocol Negotiation) во время TLS handshake — клиент шлёт список (“h2”, “http/1.1”), сервер выбирает.
- Prior knowledge (h2c) — клиент сразу шлёт HTTP/2 preface без negotiation, для cleartext.
- В Go:
tls.Config.NextProtos = []string{"h2", "http/1.1"}критичен для HTTP/2 over TLS.
-
⚠️ http.Client не использует HTTP/2 по умолчанию для не-HTTPS:
// Это HTTP/1.1, даже если сервер h2c:resp, _ := http.Get("http://service.local/")// Чтобы получить h2c — нужно настраивать Transport вручную через http2.Transport -
⚠️ HTTP/2: max concurrent streams по умолчанию 250 в Go. Если ваш клиент шлёт 1000 параллельных запросов на один upstream — gRPC будет queue их. Увеличивайте:
srv := &http2.Server{MaxConcurrentStreams: 1000,} -
⚠️ Connection: close header в HTTP/2 — PROTOCOL_ERROR. Нельзя устанавливать. Если ваш middleware ставит — HTTP/2 connection упадёт.
-
⚠️ HTTP/1.1 keep-alive + LB: L4 LB может закрыть idle connection раньше, чем клиент. Клиент использует “мертвое” соединение → EOF. Решение: меньший
IdleConnTimeoutна клиенте, чем на LB. -
⚠️ HTTP/2 + reverse proxy: многие proxies (старый NGINX) терминируют HTTP/2 → HTTP/1.1 на backend, теряя multiplexing. Используйте gRPC-aware proxy (Envoy, NGINX 1.19+).
-
⚠️ HTTP/2 transport не закрывает идлящие соединения через keep-alive timers:
t := &http2.Transport{ReadIdleTimeout: 30 * time.Second, // ping каждые 30sPingTimeout: 15 * time.Second,}Иначе мёртвые соединения «зомби» висят и accept’ят запросы.
-
⚠️ HTTP/3 NEEDS UDP firewall open: UDP/443. Многие корп. firewall блокируют UDP по умолчанию. Клиент должен gracefully fallback на HTTP/2 через Alt-Svc + retry logic.
-
⚠️ QUIC: MTU и Path MTU Discovery: если фрагментации избежать нельзя, performance падает. Дефолт MTU 1200 байт для QUIC.
-
⚠️ HPACK injection attacks: контролируемый клиентом header
X-Foo: <very long>может «вытолкнуть» полезные записи из dynamic table → каждый последующий header снова занимает много байт. -
⚠️ HTTP/2 server push в Chrome 106+ removed. Если вы написали код с
pusher, _ := w.(http.Pusher); pusher.Push(...)— он молча игнорируется браузером. Используйте 103 Early Hints вместо push. -
⚠️ HTTP/3 ALPN identifier — “h3” (а не “h3-29” из drafts). Старые библиотеки могут использовать draft-версии.
-
⚠️ Compression atak (BREACH/CRIME): HPACK теоретически уязвим. Не помещайте secrets (CSRF tokens) в headers, если есть пользовательский ввод в тех же headers.
-
⚠️ http.Transport не разделяет соединения для HTTP/1.1 и HTTP/2 — если сервер ответил Upgrade: HTTP/2 на одном порту, последующие requests на тот же host пойдут через HTTP/2 connection.
4. Production-практики
Заголовок раздела «4. Production-практики»Включение HTTP/2 правильно (Go-сервер)
Заголовок раздела «Включение HTTP/2 правильно (Go-сервер)»package main
import ( "crypto/tls" "log" "net/http" "time"
"golang.org/x/net/http2")
func main() { mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("hello")) })
srv := &http.Server{ Addr: ":8443", Handler: mux, ReadHeaderTimeout: 5 * time.Second, ReadTimeout: 30 * time.Second, WriteTimeout: 30 * time.Second, IdleTimeout: 120 * time.Second, TLSConfig: &tls.Config{ MinVersion: tls.VersionTLS12, NextProtos: []string{"h2", "http/1.1"}, // ALPN }, } // Опционально: тонкая настройка HTTP/2 http2.ConfigureServer(srv, &http2.Server{ MaxConcurrentStreams: 1000, MaxReadFrameSize: 1 << 20, // 1 MiB IdleTimeout: 120 * time.Second, }) log.Fatal(srv.ListenAndServeTLS("cert.pem", "key.pem"))}Tuning HTTP/2 client transport
Заголовок раздела «Tuning HTTP/2 client transport»package main
import ( "crypto/tls" "net/http" "time"
"golang.org/x/net/http2")
func newH2Client() *http.Client { t := &http2.Transport{ TLSClientConfig: &tls.Config{ NextProtos: []string{"h2"}, }, ReadIdleTimeout: 30 * time.Second, PingTimeout: 15 * time.Second, WriteByteTimeout: 30 * time.Second, } return &http.Client{ Transport: t, Timeout: 60 * time.Second, }}h2c для service-to-service
Заголовок раздела «h2c для service-to-service»В Kubernetes pod-to-pod без TLS:
package main
import ( "log" "net/http"
"golang.org/x/net/http2" "golang.org/x/net/http2/h2c")
func main() { h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Write([]byte(r.Proto)) }) srv := &http.Server{ Addr: ":8080", Handler: h2c.NewHandler(h, &http2.Server{}), } log.Fatal(srv.ListenAndServe())}Клиент с h2c:
package main
import ( "context" "crypto/tls" "net" "net/http"
"golang.org/x/net/http2")
func newH2CClient() *http.Client { return &http.Client{ Transport: &http2.Transport{ AllowHTTP: true, DialTLSContext: func(ctx context.Context, network, addr string, _ *tls.Config) (net.Conn, error) { d := net.Dialer{} return d.DialContext(ctx, network, addr) }, }, }}Alt-Svc для HTTP/3 fallback
Заголовок раздела «Alt-Svc для HTTP/3 fallback»srv.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Сообщаем браузеру: "Я также доступен по HTTP/3 на UDP/443" w.Header().Set("Alt-Svc", `h3=":443"; ma=86400`) w.Write([]byte("hello"))})Метрики (Prometheus)
Заголовок раздела «Метрики (Prometheus)»var ( httpVersionCounter = prometheus.NewCounterVec( prometheus.CounterOpts{ Name: "http_requests_by_version_total", Help: "HTTP requests by protocol version", }, []string{"proto"}, ))
func instrument(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { httpVersionCounter.WithLabelValues(r.Proto).Inc() next.ServeHTTP(w, r) })}Когда что выбирать (2026)
Заголовок раздела «Когда что выбирать (2026)»- Public website / API через CDN → HTTP/2 + HTTP/3 (Alt-Svc). CDN на стороне Cloudflare/Cloudfront уже HTTP/3.
- Internal microservices (gRPC) → HTTP/2 (mandatory) + h2c (если mesh уже зашифровано).
- WebSocket-heavy app → HTTP/1.1 для WS upgrade (WS не работает поверх HTTP/2/3 без extension RFC 8441).
- Mobile API → HTTP/3 даёт большой выигрыш при lossy network. Но fallback на HTTP/2 обязателен.
- Legacy clients (старый софт) → HTTP/1.1.
Tools для тестинга и debug
Заголовок раздела «Tools для тестинга и debug»# curl c HTTP/2curl -v --http2 https://example.com/
# curl c HTTP/3 (требует curl собранный с ngtcp2/quiche)curl --http3 https://cloudflare-quic.com/
# nghttp clientnghttp -nv https://example.com/
# Полная разборка фреймовnghttp -nvy https://example.com/ 2>&1 | grep -E "send|recv"
# h2load (бенчмарк)h2load -n 10000 -c 100 -m 10 https://example.com/
# wireshark (для дебага), фильтр:http2 || quic
# Проверка ALPNopenssl s_client -connect example.com:443 -alpn h2,http/1.1 -tlsextdebugDisabling HTTP/2 для дебага
Заголовок раздела «Disabling HTTP/2 для дебага»// Способ 1: env variable до запуска// GODEBUG=http2client=0,http2server=0
// Способ 2: пустой TLSNextProtosrv.TLSNextProto = make(map[string]func(*http.Server, *tls.Conn, http.Handler))Graceful shutdown с long-lived streams
Заголовок раздела «Graceful shutdown с long-lived streams»ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)defer cancel()
if err := srv.Shutdown(ctx); err != nil { // Forces close для висящих gRPC streams srv.Close()}⚠️ Shutdown НЕ закрывает active streams в HTTP/2. Для gRPC server streaming придётся также сообщить хендлерам остановиться через context cancellation.
5. Вопросы (25+)
Заголовок раздела «5. Вопросы (25+)»-
Чем отличается HTTP/1.1 от HTTP/2 на уровне формата сообщений? HTTP/1.1 — текстовый, HTTP/2 — binary framing. Каждое сообщение разбивается на frames (HEADERS, DATA), у каждого frame есть stream ID. Это позволяет multiplexing.
-
Что такое multiplexing в HTTP/2 и как он отличается от pipelining HTTP/1.1? Multiplexing — независимые streams в одном TCP-соединении, могут interleav’иться в произвольном порядке. Pipelining требует строгого порядка ответов → head-of-line blocking.
-
Что такое HPACK? Алгоритм сжатия headers в HTTP/2: статическая таблица (61 предопределённая запись), динамическая таблица (для повторяющихся), Huffman encoding строк.
-
Почему gRPC требует HTTP/2? gRPC использует streaming (bidi, server-side, client-side), которое возможно только с HTTP/2 streams. Также нужен binary framing.
-
Что такое h2c и когда он нужен? HTTP/2 over cleartext — без TLS. Используется service-to-service внутри кластера, где TLS terminate на ingress/mesh. В Go:
golang.org/x/net/http2/h2c. -
Что такое ALPN? Application-Layer Protocol Negotiation — расширение TLS, в ClientHello клиент отправляет список протоколов (“h2”, “http/1.1”), сервер выбирает один. Без ALPN включить HTTP/2 over TLS невозможно (RFC 7540).
-
Что такое QUIC? Транспорт поверх UDP с встроенным TLS 1.3, multiplexed streams без head-of-line blocking, connection migration через Connection ID, 0-RTT handshake.
-
Какие проблемы HTTP/2 решает HTTP/3?
- HoL blocking на TCP уровне: при потере пакета TCP блокируется ВСЕ streams.
- Сложный handshake (TCP + TLS = 2-3 RTT) → QUIC 0-1 RTT.
- Connection migration при смене IP.
-
Как происходит 0-RTT в QUIC? Клиент при первом соединении получает session ticket. При следующем соединении посылает application data в первом же пакете, шифруя их preshared secret из ticket’а. RTT = 0.
-
Что опасного в 0-RTT? Атакующий может replay’нуть пакет → дубликат запроса. Использовать только для idempotent методов.
-
В чём разница flow control HTTP/2 и TCP? HTTP/2 flow control — на уровне stream И connection, через WINDOW_UPDATE frames. TCP — на уровне connection, через window size в ACK.
-
Что такое server push в HTTP/2 и почему deprecated? Сервер мог отправить ресурс до того, как клиент запросил (например, CSS вместе с HTML). Deprecated, потому что push был эффективен только когда сервер знал кэш клиента — на практике cache mismatch → wasted bandwidth. Заменён на 103 Early Hints.
-
Как HTTP/2 обрабатывает приоритеты? Через PRIORITY frame: dependency tree (parent stream) + weight (1-256). На практике плохо работает между разными клиентами, в RFC 9113 упрощено.
-
Что такое connection migration в QUIC? QUIC connection идентифицируется Connection ID, а не (IP, port). При смене сетевого пути (Wi-Fi → 4G) клиент использует тот же CID, сервер продолжает session без re-handshake.
-
Почему net/http автоматически включает HTTP/2 только для HTTPS? Спецификация HTTP/2 (RFC 7540 / 9113) требует ALPN для negotiation. Для cleartext нужен h2c, который не входит в стандартный flow
ListenAndServeTLS. -
Что такое RST_STREAM? Frame отмены конкретного stream без закрытия connection. Например, при отмене запроса клиентом или при HTTP/2 PROTOCOL_ERROR.
-
Что такое GOAWAY? Frame, сигнализирующий о закрытии connection. Включает last-processed stream ID, чтобы клиент знал, какие streams retry’ить.
-
Pipelining в HTTP/1.1 — почему не используется? Ответы должны идти в строгом порядке отправки → HoL blocking при медленном ответе. Прокси/middleboxes часто неправильно обрабатывают → ошибки. Multiplexing в HTTP/2 решает.
-
Какие настройки HTTP/2 в Go стоит тюнить для high-load gRPC сервера?
MaxConcurrentStreams(default 250),MaxReadFrameSize,IdleTimeout, на клиентеReadIdleTimeout+PingTimeoutдля keepalive. -
Как WebSocket работает поверх HTTP/2? Нативно — никак. RFC 8441 определяет CONNECT-method extension с pseudo-header
:protocol: websocket. Не все прокси поддерживают. Большинство юзеров используют HTTP/1.1 для WS. -
Может ли HTTP/3 работать через NAT? Да. Connection ID не зависит от (IP, port), но в NAT’ах с короткими UDP-flow timeouts (10-30 сек) connection может быть rebound’нут. Клиент держит keep-alive PING.
-
Что делает Alt-Svc header?
Alt-Svc: h3=":443"; ma=86400сообщает: “Я также доступен по HTTP/3 на UDP/443 в течение 86400 секунд (max-age)”. Браузер запоминает и может использовать HTTP/3 при следующем визите. -
Какие риски HTTP/3 в продакшене 2026? UDP throttling/blocking на корпоративных сетях, fragmentation issues (path MTU), DDoS reflection attacks через UDP, легче DoS на handshake (нет TCP SYN cookies; есть QUIC retry tokens).
-
Почему HTTP/2 нельзя downgrade’ить на HTTP/1.1 prosto? HTTP/2 имеет concept’ы (binary, streams, HPACK), которых нет в HTTP/1.1. На границе прокси нужно полностью переписать сообщение. Также теряется multiplexing.
-
Что такое preface в HTTP/2? Клиент сразу после установки соединения должен отправить magic string
PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n— это маркер для прокси, что connection действительно HTTP/2 (а не HTTP/1.1 upgrade). -
Какова length frame в HTTP/2 по умолчанию и можно ли увеличить? Default
SETTINGS_MAX_FRAME_SIZE = 16384(16 КБ). Можно увеличить через SETTINGS до 16 МБ. В Go:http2.Transport.MaxReadFrameSize. -
Когда нужно явно ставить
Connection: closeв HTTP/1.1? Когда вы знаете, что после этого ответа соединение не нужно держать (например, error при потере состояния). Для HTTP/2 — этот header запрещён.
6. Practice
Заголовок раздела «6. Practice»Задача 1 — Запустить HTTPS-сервер с HTTP/2
Заголовок раздела «Задача 1 — Запустить HTTPS-сервер с HTTP/2»Создайте сервер на :8443 с self-signed cert. Убедитесь через curl: curl -v --http2 https://localhost:8443/ показывает using HTTP/2.
Задача 2 — h2c handler
Заголовок раздела «Задача 2 — h2c handler»Реализуйте h2c handler, который на каждый запрос возвращает r.Proto и r.RemoteAddr. Проверьте, что curl --http2-prior-knowledge http://localhost:8080/ работает.
Задача 3 — HTTP/2 client с keep-alive
Заголовок раздела «Задача 3 — HTTP/2 client с keep-alive»Напишите клиент, который шлёт 1000 запросов на свой же сервер. Проверьте через netstat -an | grep :8443, что используется ОДНО соединение.
Задача 4 — Tracing requests
Заголовок раздела «Задача 4 — Tracing requests»Используйте net/http/httptrace для логирования: какой proto использовался, был ли reused connection, время handshake.
trace := &httptrace.ClientTrace{ GotConn: func(info httptrace.GotConnInfo) { log.Printf("reused=%v proto=...\n", info.Reused) }, TLSHandshakeStart: func() { log.Println("TLS handshake start") }, TLSHandshakeDone: func(state tls.ConnectionState, err error) { log.Printf("TLS done, ALPN=%s\n", state.NegotiatedProtocol) },}req, _ := http.NewRequestWithContext(httptrace.WithClientTrace(ctx, trace), "GET", url, nil)Задача 5 — HTTP/3 ping
Заголовок раздела «Задача 5 — HTTP/3 ping»Запустите HTTP/3 сервер с quic-go, реализуйте /ping endpoint, протестируйте curl --http3 https://localhost:4433/ping.
Задача 6 — Migration test
Заголовок раздела «Задача 6 — Migration test»Внутри HTTP/3 теста переподключите клиента с другого IP/порта (можно через net.Dial локально). Убедитесь, что connection живёт через Connection ID.
Задача 7 — HoL blocking demo
Заголовок раздела «Задача 7 — HoL blocking demo»Создайте HTTP/2 сервер с двумя endpoints: /fast (мгновенно) и /slow (sleep 5s). Через один client отправьте параллельно много /slow + один /fast. Замерьте latency /fast (должна быть малой благодаря multiplexing).
Задача 8 — Tune для 10K concurrent streams
Заголовок раздела «Задача 8 — Tune для 10K concurrent streams»Настройте HTTP/2 сервер для обработки 10K concurrent streams на одном connection (для gRPC mesh). Что нужно изменить: MaxConcurrentStreams, MaxReadFrameSize, IdleTimeout?
7. Источники
Заголовок раздела «7. Источники»- RFC 9113 — HTTP/2 (актуальная редакция, заменяет 7540): https://datatracker.ietf.org/doc/html/rfc9113
- RFC 9114 — HTTP/3: https://datatracker.ietf.org/doc/html/rfc9114
- RFC 9000 — QUIC: A UDP-Based Multiplexed and Secure Transport
- RFC 9001 — Using TLS to Secure QUIC
- RFC 7541 — HPACK: Header Compression for HTTP/2
- RFC 9204 — QPACK: Header Compression for HTTP/3
- Go HTTP/2 implementation: https://pkg.go.dev/golang.org/x/net/http2
- quic-go library: https://github.com/quic-go/quic-go
- Cloudflare blog: HTTP/3 deep dive: https://blog.cloudflare.com/http-3-from-root-to-tip/
- Caddy server (включает HTTP/3 by default): https://caddyserver.com/docs/
- Google QUIC paper: “The QUIC Transport Protocol: Design and Internet-Scale Deployment” (SIGCOMM 2017)