linuxlab.io
Учебники▾
  • Линукс и сети
    Файловая система, процессы, TCP/IP, BGP и OSPF
    →
  • Terraform и IaC
    HCL, state, plan/apply на sandbox LocalStack
    →
  • Git и GitHub
    Объектная модель, plumbing, ветвление, GitHub Actions
    →
Все учебники →
ЦеныО платформеВойтиСоздать аккаунт
/
  • Введение
  • Уроки
  • How it works
  • Симулятор
  • База знаний
  • Собеседование
Index
Categories
All entries
Footer
linuxlab-УчебникиЦеныО платформеКонфиденциальность и куки
Copyright © 2026 LinuxLab. Все права защищены.
home/linux/kb/Сеть: L4 и выше/http2-internals

kb/network-l4 ── Сеть: L4 и выше ── advanced

HTTP/2 internals - binary framing, HPACK, stream multiplexing

HTTP/2 - бинарный мультиплексинг поверх одного TCP-соединения. HPACK сжимает headers через индексированный словарь. Streams независимы. Server push deprecated. На loss-friendly link HoL-blocking - проблема, которую решил QUIC.

view as markdownaka: http2, http-2, http-2-internals, hpack, h2

Зачем HTTP/2

HTTP/1.1 (RFC 7230) текстовый, head-of-line-blocking на уровне request'ов, нельзя посылать parallel запросы в одном соединении. Браузеры открывали 6 параллельных TCP-соединений на host - 6 TLS handshake'ов, дублирование headers, плохое использование congestion control.

В 2015 IETF опубликовал HTTP/2 (RFC 7540, переиздан как RFC 9113):

  • Бинарный - не парсится глазами, но быстрее и однозначнее
  • Один TCP на host - меньше handshake'ов, лучше congestion window
  • Stream multiplexing - параллельные запросы как независимые streams
  • HPACK - сжатие headers через словарь
  • Server push (теперь deprecated)

Отношение к HTTP/1.1 - не replacement, а transport optimization. Семантика (метод, URI, status codes, body) такая же. Меняется только wire format.

В 2020 пришёл [[quic-http3|HTTP/3]] - тот же мультиплекс, но поверх QUIC/UDP, без TCP HoL.

Binary framing layer

Каждое HTTP/2 communication разбивается на frame'ы (минимальная единица). Frame header - 9 байт:

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                 Length (24)                   |
+---------------+---------------+---------------+
|   Type (8)    |   Flags (8)   |
+-+-------------+---------------+-------------------------------+
|R|                 Stream Identifier (31)                      |
+=+=============================================================+
|                   Frame Payload (0...)                      ...
+---------------------------------------------------------------+
  • Length - размер payload (max 2^14 = 16384 байт по умолчанию, может быть увеличен до 2^24)
  • Type - тип frame'а (см. ниже)
  • Flags - bit-flags зависящие от type
  • Stream Identifier - 0 для control, иначе идентификатор stream

Frame types

TypeНазначение
DATA (0x0)request/response body
HEADERS (0x1)request/response headers (после HPACK)
PRIORITY (0x2)priority hint (deprecated в RFC 9113)
RST_STREAM (0x3)завершить stream с error code
SETTINGS (0x4)connection params (frame size, max streams, ...)
PUSH_PROMISE (0x5)server push (deprecated)
PING (0x6)keepalive + RTT measurement
GOAWAY (0x7)initiate connection close
WINDOW_UPDATE (0x8)flow control credit
CONTINUATION (0x9)продолжение HEADERS если не вместился

Streams - independent virtual connections

Каждый запрос/ответ - stream с уникальным 31-битным ID. Streams независимы: фреймы разных streams могут чередоваться в TCP-соединении.

Lifecycle stream:

idle → reserved (server push) ─┐
     └→ open ──┐                │
               ├→ half-closed ──┼→ closed
               └→ half-closed ──┘

ID правила:

  • Чётные ID - инициированы сервером (server push)
  • Нечётные ID - инициированы клиентом
  • Каждый раз увеличиваются - reuse запрещён

Один TCP может держать тысячи streams (ограничено SETTINGS_MAX_CONCURRENT_STREAMS).

HPACK - сжатие headers

HTTP-headers повторяются: User-Agent, Accept-Encoding, Cookie - одинаковые на каждом запросе. HTTP/1.1 шлёт текстом каждый раз.

HPACK (RFC 7541) сжимает через:

  1. Static table - 61 предопределённый header (:method GET, content-type text/html, ...)
  2. Dynamic table - расширяемый, headers с предыдущих запросов
  3. Huffman coding - сжатие литералов

Передаётся индекс в таблице (1-2 байта) вместо текста (50+ байт). Сжатие 80-90% на типичных headers.

Минус: dynamic table требует per-connection state, sync между сторонами. CRIME/HEIST-style атаки требуют осторожности с user-controlled headers (cookie compression).

HTTP/3 использует QPACK (адаптация HPACK для QUIC) - похожая идея, но без head-of-line на decompression.

Stream priority - и почему deprecated

HTTP/2 (RFC 7540) ввёл сложный priority tree: каждый stream имеет parent stream, weight (1-256), exclusive flag. Идея - сервер обрабатывает critical streams (HTML > CSS > image) раньше.

На практике:

  • Браузеры реализовали по-разному
  • Сервера часто игнорировали
  • Race-conditions в дереве

RFC 9113 (2022) deprecated priority. Новый RFC 9218 ввёл Extensible Priorities - простые Priority: headers со scheme urgency=N, incremental=?1.

Flow control - per-stream и per-connection

HTTP/2 имеет credit-based flow control (как TCP receive window):

  • Каждый sender знает window size receiver'а
  • Шлёт DATA пока window > 0
  • Receiver шлёт WINDOW_UPDATE чтобы дать больше credit'ов

Два уровня: per-stream и per-connection. Без credit'а нельзя слать DATA - HEADERS можно (control plane).

Default window 65535 байт - очень мало, на 100ms RTT даёт ~5 Mbps per connection. Сервера обычно сразу шлют WINDOW_UPDATE до 16-64 МБ.

Server push - deprecated

Идея: сервер шлёт ресурсы до запроса (HTML и связанный CSS вместе). RFC 7540 описал, многие сервера реализовали.

На практике:

  • Браузеры уже имели ресурс в кэше → wasted bandwidth
  • Сложно правильно решить, что push'ить
  • 103 Early Hints покрывает использование лучше

Chrome отключил support в 2022. RFC 9113 (2022) пометил PUSH_PROMISE как deprecated.

Замена - 103 Early Hints:

HTTP/2 103 Early Hints
Link: </styles.css>; rel=preload; as=style
Link: </app.js>; rel=preload; as=script
HTTP/2 200 OK
Content-Type: text/html
...

Браузер начинает грузить preload'ы пока сервер генерирует основной ответ.

TCP head-of-line blocking - the problem

Big "but" HTTP/2: один TCP connection, один in-order byte-stream. Если один пакет потерян - все streams ждут retransmit:

Stream 1: [seg1][seg2][LOST][seg4]   ←─ Stream 1 ждёт seg3
Stream 2: [data][data][data][data]   ←─ И Stream 2 ждёт тоже!
Stream 3: [data][data][data][data]   ←─ И Stream 3!

Хотя данные Stream 2 уже в буфере NIC - kernel не отдаст app пока не дойдёт seg3 (TCP в order). Это и есть TCP HoL blocking.

На лоси-фрукт wifi/4G это убивает преимущество multiplexing'а - HTTP/1.1 с 6 connections иногда быстрее.

Решение - [[quic-http3|HTTP/3 / QUIC]]: streams в QUIC независимы на уровне UDP, потеря одного не блочит другие.

Negotiation - как клиент узнаёт что server поддерживает h2

Два механизма:

ALPN в TLS (стандарт)

В TLS ClientHello клиент шлёт ALPN (Application-Layer Protocol Negotiation) с списком: h2, http/1.1. Сервер выбирает в ServerHello. Browsers только так делают - HTTP/2 в clear text не поддерживается ни одним браузером.

Upgrade header (h2c, plaintext)

GET / HTTP/1.1
Host: example.com
Connection: Upgrade, HTTP2-Settings
Upgrade: h2c
HTTP2-Settings: <base64-encoded SETTINGS>

Сервер если поддерживает - отвечает 101 Switching Protocols. Используется только server-to-server (gRPC часто). RFC 9113 убрал Upgrade-механизм (но многие сервера и клиенты поддерживают).

HTTP/2 vs HTTP/3 - сравнение

СвойствоHTTP/2HTTP/3
ТранспортTCPQUIC (UDP)
TLSотдельный (TLS 1.2+)встроен в QUIC (TLS 1.3)
HandshakeTCP + TLS = 2-3 RTTQUIC = 1 RTT (или 0-RTT)
HoL на packet lossда (TCP)нет (per-stream)
Headers compressionHPACKQPACK
Multiplexingдада
Connection migrationнетда (мобильный сменил wifi → 4G)
Server pushdeprecatedне было
Adoption (2025)~95% сайтов~30% (в основном Cloudflare/Google)

Когда что-то пошло не так

  • HTTP/2: protocol error - frame с неправильным format'ом или state-violation. tcpdump + Wireshark dissector покажут конкретный frame. Часто в client/server stub'ах баги.
  • GOAWAY с last_stream_id - сервер закрывает connection. Запросы с stream_id > last - нужно retry в новом connection. Часто из-за rate limit или idle timeout.
  • Window starvation - app не отправляет WINDOW_UPDATE, sender залип. Проверь библиотеку, увеличь buffer'ы.
  • Plaintext h2c не работает в браузере - все требуют TLS+ALPN. Используй --insecure-http2 в curl для тестирования.
  • HPACK decompression bomb - в реализации может быть DoS если клиент шлёт много новых headers (раздувает dynamic table). Лимиты есть в RFC, но реализации варьируются (CVE-2019-9518).
  • Throughput хуже HTTP/1.1 - вероятно много packet loss и multiplexing блокирован TCP HoL. Перейти на HTTP/3.
  • SETTINGS_MAX_CONCURRENT_STREAMS = 100 - default многих серверов. Если клиент шлёт больше - стримы queue'ятся, latency растёт. Поднять или использовать pool connections.

§ команды

bash
curl --http2 -v https://example.com 2>&1 | head -40

Curl с HTTP/2 - в выводе видно ALPN handshake и frame'ы

bash
curl --http2-prior-knowledge http://localhost:8080/

h2c - HTTP/2 в plain text, минуя ALPN. Только если знаешь, что server h2c

bash
openssl s_client -alpn h2 -connect example.com:443 -tls1_2 < /dev/null

Проверить, что сервер договаривается на h2 через ALPN

bash
nghttp -nv https://example.com

nghttp из nghttp2-utils - подробный HTTP/2 client с frame-уровневым выводом

bash
h2load -n 1000 -c 10 -m 100 https://example.com/

Бенчмарк HTTP/2: 1000 запросов, 10 connections, 100 streams concurrent

bash
tshark -Y 'http2' -Tfields -e http2.streamid -e http2.headers -i eth0

Wireshark CLI - dissect HTTP/2 frames live

bash
echo 'h2 alpn alpn-list = h2' | nginx -t

В nginx http2 включается через 'listen 443 ssl http2;' (deprecated в 1.25.1+)

bash
curl --http2 -H 'Accept-Encoding: gzip' --raw -v https://example.com 2>&1 | grep -i hpack

HPACK не виден в curl напрямую - нужен h2c-dump или Wireshark

§ см. также

  • http-protocolHTTP/1.1, HTTP/2, HTTP/3HTTP/1.1 - текстовый протокол с keep-alive. HTTP/2 - бинарный с мультиплексированием в одном TCP-соединении. HTTP/3 = HTTP/2-семантика поверх QUIC/UDP без TCP-head-of-line blocking.
  • quic-http3QUIC и HTTP/3 - современный транспорт поверх UDPQUIC - транспорт поверх UDP. TLS 1.3 встроен (1 RTT, 0-RTT для resume). Multiplexing без head-of-line blocking. Connection migration (Wi-Fi → 4G без drop). HTTP/3 = HTTP-семантика поверх QUIC.
  • tls-handshakeTLS handshakeTLS - слой шифрования поверх TCP. Перед передачей данных стороны делают handshake: обмениваются ключами, проверяют сертификат, выбирают cipher.
  • grpc-basicsgRPC - HTTP/2 + Protobuf RPC frameworkgRPC = HTTP/2 + Protocol Buffers + кодогенерация. Четыре типа RPC: unary (как REST), server-stream, client-stream, bidirectional. Сильная типизация, бинарный wire format, multi-language. grpcurl как curl для gRPC.
  • cmd-curlcurl - HTTP-клиент из терминала`curl` - universal CLI для HTTP/HTTPS/FTP/etc. Делает запросы, видит заголовки, сертификаты, тайминги. Главный инструмент дебага HTTP API.
  • tcp-handshakeTCP three-way handshakeTCP-соединение открывается тремя пакетами: SYN от клиента, SYN-ACK от сервера, ACK от клиента. После - соединение Established, можно слать данные.
  • tracing-basicsDistributed tracing: span, context propagation, samplingTracing - граф spans (parent-child) одного логического запроса через сервисы. Context передаётся HTTP-header traceparent (W3C). Sampling: head (на edge, дёшево) или tail (в Collector, точнее). Backend: Jaeger, Tempo, Zipkin.
Footer
linuxlab-
Copyright © 2026 LinuxLab. Все права защищены.
Учебники
Цены
О платформе
Конфиденциальность и куки