Перейти к содержимому

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.

  1. Базовая концепция
  2. Глубокое погружение (под капотом)
  3. Gotchas (12+)
  4. Production-практики
  5. Вопросы (25+)
  6. Practice
  7. Источники

HTTP/0.9 (1991) → только GET, plain text
HTTP/1.0 (1996) → headers, methods, status codes
HTTP/1.1 (1997) → keep-alive, pipelining, chunked encoding, Host header
HTTP/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.1HTTP/2HTTP/3
ТранспортTCPTCPQUIC (UDP)
EncodingTextBinary framesBinary frames
MultiplexingНетДа (streams)Да (streams)
Header compressionНетHPACKQPACK
Server pushНетДа (deprecated)Да
HoL blocking (transport)ДаДа (TCP-level)Нет
TLSОпциональноДе-факто обязателенВстроен в QUIC
Handshake RTT1-3 (TCP+TLS)2-30-1 (0-RTT возможен)

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.


Идея: одно 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 streams

Frame types:

  • HEADERS — заголовки запроса/ответа
  • DATA — тело
  • SETTINGS — параметры соединения
  • WINDOW_UPDATE — flow control
  • PRIORITY — приоритеты streams
  • RST_STREAM — отмена stream
  • PING — heartbeat
  • GOAWAY — закрытие соединения

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 = 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-ответе.


+-----------------------------------------------+
| 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 нет 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.

Static Table (предопределён, 61 запись):

1 :authority
2 :method GET
3 :method POST
4 :path /
5 :path /index.html
6 :scheme http
7 :scheme https
8 :status 200
...

Dynamic Table — заполняется по мере общения. После первого “User-Agent: curl/7.88” → в таблице, последующие отправки = 1 байт индекс.

Кодирование: Huffman encoding для строк → ещё меньше.

HTTP/1.1 GET /index.html HTTP/1.1\r\n
Host: example.com\r\n
User-Agent: curl/7.88.1\r\n
Accept: */*\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.

(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).

В отличие от 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 решает это.

+-+-+-+-+-+-+-+-+
|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.

HTTP/2: stream IDs share TCP byte order
[stream1 byte][stream2 byte][stream1 byte] -- если потерян пакет TCP — все streams ждут
HTTP/3: каждый QUIC stream имеет свой sequence
потеря пакета влияет только на конкретный stream
другие streams продолжают доставлять данные

Connection идентифицируется не (IP+port), а Connection ID (DCID, SCID). Когда клиент переходит с Wi-Fi на 4G:

  • IP меняется
  • Connection ID остаётся
  • Сервер видит пакет с тем же CID → продолжает session
  • Перешифровка не нужна

0-RTT данные могут быть replay’нуты атакующим:

Client → Server: 0-RTT данные [POST /buy product=X]
[Атакующий захватывает]
Атакующий → Server: тот же 0-RTT пакет → дубликат заказа

⚠️ Правило: 0-RTT использовать только для idempotent запросов (GET, OPTIONS, HEAD).

СценарийHTTP/1.1HTTP/2HTTP/3
100 параллельных GET (web page)6-12 conn1 conn1 conn
Page load (десктоп, fast network)baseline-15%-10..-20%
Page load (mobile, lossy)baseline-10%-30..-40%
Single large downloadcomparablecomparablecomparable
gRPC streamingimpossibleokok
Initial handshake (cold)2-3 RTT2-3 RTT1 RTT
Initial handshake (warm, 0-RTT)1 RTT1 RTT0 RTT
БраузерHTTP/2HTTP/3
Chrome41+87+ (по умолчанию)
Firefox36+88+
Safari9+14+
Edgeдада

  1. ⚠️ 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.
  2. ⚠️ http.Client не использует HTTP/2 по умолчанию для не-HTTPS:

    // Это HTTP/1.1, даже если сервер h2c:
    resp, _ := http.Get("http://service.local/")
    // Чтобы получить h2c — нужно настраивать Transport вручную через http2.Transport
  3. ⚠️ HTTP/2: max concurrent streams по умолчанию 250 в Go. Если ваш клиент шлёт 1000 параллельных запросов на один upstream — gRPC будет queue их. Увеличивайте:

    srv := &http2.Server{
    MaxConcurrentStreams: 1000,
    }
  4. ⚠️ Connection: close header в HTTP/2 — PROTOCOL_ERROR. Нельзя устанавливать. Если ваш middleware ставит — HTTP/2 connection упадёт.

  5. ⚠️ HTTP/1.1 keep-alive + LB: L4 LB может закрыть idle connection раньше, чем клиент. Клиент использует “мертвое” соединение → EOF. Решение: меньший IdleConnTimeout на клиенте, чем на LB.

  6. ⚠️ HTTP/2 + reverse proxy: многие proxies (старый NGINX) терминируют HTTP/2 → HTTP/1.1 на backend, теряя multiplexing. Используйте gRPC-aware proxy (Envoy, NGINX 1.19+).

  7. ⚠️ HTTP/2 transport не закрывает идлящие соединения через keep-alive timers:

    t := &http2.Transport{
    ReadIdleTimeout: 30 * time.Second, // ping каждые 30s
    PingTimeout: 15 * time.Second,
    }

    Иначе мёртвые соединения «зомби» висят и accept’ят запросы.

  8. ⚠️ HTTP/3 NEEDS UDP firewall open: UDP/443. Многие корп. firewall блокируют UDP по умолчанию. Клиент должен gracefully fallback на HTTP/2 через Alt-Svc + retry logic.

  9. ⚠️ QUIC: MTU и Path MTU Discovery: если фрагментации избежать нельзя, performance падает. Дефолт MTU 1200 байт для QUIC.

  10. ⚠️ HPACK injection attacks: контролируемый клиентом header X-Foo: <very long> может «вытолкнуть» полезные записи из dynamic table → каждый последующий header снова занимает много байт.

  11. ⚠️ HTTP/2 server push в Chrome 106+ removed. Если вы написали код с pusher, _ := w.(http.Pusher); pusher.Push(...) — он молча игнорируется браузером. Используйте 103 Early Hints вместо push.

  12. ⚠️ HTTP/3 ALPN identifier — “h3” (а не “h3-29” из drafts). Старые библиотеки могут использовать draft-версии.

  13. ⚠️ Compression atak (BREACH/CRIME): HPACK теоретически уязвим. Не помещайте secrets (CSRF tokens) в headers, если есть пользовательский ввод в тех же headers.

  14. ⚠️ http.Transport не разделяет соединения для HTTP/1.1 и HTTP/2 — если сервер ответил Upgrade: HTTP/2 на одном порту, последующие requests на тот же host пойдут через HTTP/2 connection.


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"))
}
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,
}
}

В 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)
},
},
}
}
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"))
})
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)
})
}
  • 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.
Окно терминала
# curl c HTTP/2
curl -v --http2 https://example.com/
# curl c HTTP/3 (требует curl собранный с ngtcp2/quiche)
curl --http3 https://cloudflare-quic.com/
# nghttp client
nghttp -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
# Проверка ALPN
openssl s_client -connect example.com:443 -alpn h2,http/1.1 -tlsextdebug
// Способ 1: env variable до запуска
// GODEBUG=http2client=0,http2server=0
// Способ 2: пустой TLSNextProto
srv.TLSNextProto = make(map[string]func(*http.Server, *tls.Conn, http.Handler))
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.


  1. Чем отличается HTTP/1.1 от HTTP/2 на уровне формата сообщений? HTTP/1.1 — текстовый, HTTP/2 — binary framing. Каждое сообщение разбивается на frames (HEADERS, DATA), у каждого frame есть stream ID. Это позволяет multiplexing.

  2. Что такое multiplexing в HTTP/2 и как он отличается от pipelining HTTP/1.1? Multiplexing — независимые streams в одном TCP-соединении, могут interleav’иться в произвольном порядке. Pipelining требует строгого порядка ответов → head-of-line blocking.

  3. Что такое HPACK? Алгоритм сжатия headers в HTTP/2: статическая таблица (61 предопределённая запись), динамическая таблица (для повторяющихся), Huffman encoding строк.

  4. Почему gRPC требует HTTP/2? gRPC использует streaming (bidi, server-side, client-side), которое возможно только с HTTP/2 streams. Также нужен binary framing.

  5. Что такое h2c и когда он нужен? HTTP/2 over cleartext — без TLS. Используется service-to-service внутри кластера, где TLS terminate на ingress/mesh. В Go: golang.org/x/net/http2/h2c.

  6. Что такое ALPN? Application-Layer Protocol Negotiation — расширение TLS, в ClientHello клиент отправляет список протоколов (“h2”, “http/1.1”), сервер выбирает один. Без ALPN включить HTTP/2 over TLS невозможно (RFC 7540).

  7. Что такое QUIC? Транспорт поверх UDP с встроенным TLS 1.3, multiplexed streams без head-of-line blocking, connection migration через Connection ID, 0-RTT handshake.

  8. Какие проблемы HTTP/2 решает HTTP/3?

    • HoL blocking на TCP уровне: при потере пакета TCP блокируется ВСЕ streams.
    • Сложный handshake (TCP + TLS = 2-3 RTT) → QUIC 0-1 RTT.
    • Connection migration при смене IP.
  9. Как происходит 0-RTT в QUIC? Клиент при первом соединении получает session ticket. При следующем соединении посылает application data в первом же пакете, шифруя их preshared secret из ticket’а. RTT = 0.

  10. Что опасного в 0-RTT? Атакующий может replay’нуть пакет → дубликат запроса. Использовать только для idempotent методов.

  11. В чём разница flow control HTTP/2 и TCP? HTTP/2 flow control — на уровне stream И connection, через WINDOW_UPDATE frames. TCP — на уровне connection, через window size в ACK.

  12. Что такое server push в HTTP/2 и почему deprecated? Сервер мог отправить ресурс до того, как клиент запросил (например, CSS вместе с HTML). Deprecated, потому что push был эффективен только когда сервер знал кэш клиента — на практике cache mismatch → wasted bandwidth. Заменён на 103 Early Hints.

  13. Как HTTP/2 обрабатывает приоритеты? Через PRIORITY frame: dependency tree (parent stream) + weight (1-256). На практике плохо работает между разными клиентами, в RFC 9113 упрощено.

  14. Что такое connection migration в QUIC? QUIC connection идентифицируется Connection ID, а не (IP, port). При смене сетевого пути (Wi-Fi → 4G) клиент использует тот же CID, сервер продолжает session без re-handshake.

  15. Почему net/http автоматически включает HTTP/2 только для HTTPS? Спецификация HTTP/2 (RFC 7540 / 9113) требует ALPN для negotiation. Для cleartext нужен h2c, который не входит в стандартный flow ListenAndServeTLS.

  16. Что такое RST_STREAM? Frame отмены конкретного stream без закрытия connection. Например, при отмене запроса клиентом или при HTTP/2 PROTOCOL_ERROR.

  17. Что такое GOAWAY? Frame, сигнализирующий о закрытии connection. Включает last-processed stream ID, чтобы клиент знал, какие streams retry’ить.

  18. Pipelining в HTTP/1.1 — почему не используется? Ответы должны идти в строгом порядке отправки → HoL blocking при медленном ответе. Прокси/middleboxes часто неправильно обрабатывают → ошибки. Multiplexing в HTTP/2 решает.

  19. Какие настройки HTTP/2 в Go стоит тюнить для high-load gRPC сервера? MaxConcurrentStreams (default 250), MaxReadFrameSize, IdleTimeout, на клиенте ReadIdleTimeout+PingTimeout для keepalive.

  20. Как WebSocket работает поверх HTTP/2? Нативно — никак. RFC 8441 определяет CONNECT-method extension с pseudo-header :protocol: websocket. Не все прокси поддерживают. Большинство юзеров используют HTTP/1.1 для WS.

  21. Может ли HTTP/3 работать через NAT? Да. Connection ID не зависит от (IP, port), но в NAT’ах с короткими UDP-flow timeouts (10-30 сек) connection может быть rebound’нут. Клиент держит keep-alive PING.

  22. Что делает Alt-Svc header? Alt-Svc: h3=":443"; ma=86400 сообщает: “Я также доступен по HTTP/3 на UDP/443 в течение 86400 секунд (max-age)”. Браузер запоминает и может использовать HTTP/3 при следующем визите.

  23. Какие риски HTTP/3 в продакшене 2026? UDP throttling/blocking на корпоративных сетях, fragmentation issues (path MTU), DDoS reflection attacks через UDP, легче DoS на handshake (нет TCP SYN cookies; есть QUIC retry tokens).

  24. Почему HTTP/2 нельзя downgrade’ить на HTTP/1.1 prosto? HTTP/2 имеет concept’ы (binary, streams, HPACK), которых нет в HTTP/1.1. На границе прокси нужно полностью переписать сообщение. Также теряется multiplexing.

  25. Что такое preface в HTTP/2? Клиент сразу после установки соединения должен отправить magic string PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n — это маркер для прокси, что connection действительно HTTP/2 (а не HTTP/1.1 upgrade).

  26. Какова length frame в HTTP/2 по умолчанию и можно ли увеличить? Default SETTINGS_MAX_FRAME_SIZE = 16384 (16 КБ). Можно увеличить через SETTINGS до 16 МБ. В Go: http2.Transport.MaxReadFrameSize.

  27. Когда нужно явно ставить Connection: close в HTTP/1.1? Когда вы знаете, что после этого ответа соединение не нужно держать (например, error при потере состояния). Для HTTP/2 — этот header запрещён.


Создайте сервер на :8443 с self-signed cert. Убедитесь через curl: curl -v --http2 https://localhost:8443/ показывает using HTTP/2.

Реализуйте h2c handler, который на каждый запрос возвращает r.Proto и r.RemoteAddr. Проверьте, что curl --http2-prior-knowledge http://localhost:8080/ работает.

Напишите клиент, который шлёт 1000 запросов на свой же сервер. Проверьте через netstat -an | grep :8443, что используется ОДНО соединение.

Используйте 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)

Запустите HTTP/3 сервер с quic-go, реализуйте /ping endpoint, протестируйте curl --http3 https://localhost:4433/ping.

Внутри HTTP/3 теста переподключите клиента с другого IP/порта (можно через net.Dial локально). Убедитесь, что connection живёт через Connection ID.

Создайте HTTP/2 сервер с двумя endpoints: /fast (мгновенно) и /slow (sleep 5s). Через один client отправьте параллельно много /slow + один /fast. Замерьте latency /fast (должна быть малой благодаря multiplexing).

Настройте HTTP/2 сервер для обработки 10K concurrent streams на одном connection (для gRPC mesh). Что нужно изменить: MaxConcurrentStreams, MaxReadFrameSize, IdleTimeout?