HTTP protocol evolution from 1.0 to 3 (QUIC). Covers request/response structure, methods (GET/POST/PUT/DELETE/PATCH), status codes (1xx/2xx/3xx/4xx/5xx), headers, caching with ETag/Cache-Control, conditional requests, idempotency. Three transport stacks side-by-side: HTTP/1.1 (text, keep-alive, head-of-line blocking), HTTP/2 (binary framing, multiplexing on 1 TCP, HPACK header compression, TCP HoL still present), HTTP/3 (over QUIC/UDP, true multiplex, 0-RTT, connection migration). Seven scenarios: simple GET 200, POST 201 Created, conditional GET with ETag returning 304, HTTP/1.1 head-of-line blocking, HTTP/2 multiplexing with TCP HoL caveat, HTTP/3 over QUIC with packet loss isolation, status codes 401/403/429/502/504.
HTTP — каркас, на котором держится почти весь публичный трафик. Каждая версия (1.1, 2, 3) исправляет конкретную боль предыдущей, и трейд-офф этих болей определяет, как ты строишь web-приложение: открывать ли 6 TCP в браузере, переходить ли на HTTP/3 ради мобильных, какой Cache-Control поставить, что вернуть на upstream-таймаут — 502 или 504. Без этой модели ты тюнишь систему наугад: гоняешь Brotli там, где боль в pipeline blocking, или включаешь HTTP/2 на lossy-сети и делаешь хуже, чем было.
«HTTP/1.1 = текст по TCP, одна заявка in-flight, head-of-line на pipeline. HTTP/2 = бинарь, мультиплекс N стримов по 1 TCP, HoL уехал на TCP-уровень. HTTP/3 = HTTP/2 поверх QUIC/UDP, true multiplex, 0-RTT, mobile-friendly.»
HTTP — это request/response поверх какого-то транспорта. Эволюция версий — это история про то, что является единицей мультиплексирования и где сидит HoL-блокировка:
| Версия | Транспорт | Единица мультиплекса | Где HoL |
|---|---|---|---|
| HTTP/1.1 | TCP (текст) | TCP connection | На pipeline (де-факто запрос) |
| HTTP/2 | TCP+TLS (бинарь) | stream внутри 1 TCP | На уровне TCP-пакетов |
| HTTP/3 | QUIC/UDP | stream внутри QUIC | Только внутри одного stream |
Полезно держать в голове два разных слоя: семантика (методы, статусы, headers, кеширование, idempotency) почти не меняется между версиями. Меняется только транспорт. 401 и ETag работают одинаково в /1.1 и /3.
Три параллельные «полосы» транспорта между клиентом и сервером, чтобы видеть разницу глазами:
TCP conn #1 и TCP conn #2..6 (классический браузерный приём — 6 параллельных TCP на хост).pulse-стрелки к HTTP/3 нарисованы как UDP — это маленький визуальный сигнал, что транспорт датаграмный, а не байт-стрим. Все три стека сходятся в один lb, чтобы было видно: для приложения это тот же origin, отличие только в L4/L5.
В скрипте 7 сценариев, разбитых на две группы — семантика и транспорт.
Семантика HTTP (от версии не зависит):
Простой GET. Видно, что request — это method + path + version + headers + (опционально body), а ответ — статус + headers + body. Cache хранит ответ вместе с ETag и Cache-Control: max-age=60. Это полезный baseline: «что вообще летит по проводу».
POST создаёт ресурс, возвращает 201 Created и Location: /api/orders/9001. Самое важное — POST не идемпотентен: повтор без Idempotency-Key создаст второй заказ. Это причина, по которой все production payment-API требуют идемпотентный ключ от клиента.
Conditional GET. Клиент шлёт If-None-Match: "v17", сервер сравнивает с текущим ETag, отвечает 304 Not Modified без тела. Экономия — ~5KB body превращаются в ~200 байт headers. Это основа долговечного HTTP-кэша на CDN.
Парад типичных кодов: 401 (нет токена) vs 403 (есть, но нет прав), 429 (rate limit, плюс Retry-After), 504 (upstream не уложился в таймаут) vs 502 (upstream вернул мусор). Это те границы, которые путают чаще всего на ревью.
Транспорт (что отличает версии):
HTTP/1.1 head-of-line. На одном TCP можно одну заявку in-flight; pipelining де-факто выключен из-за глючных прокси. Браузеры открывают 6 TCP на хост — это 6 handshake'ов, 6 TLS, 6 slow-start окон. Дорого, но единственный способ распараллелить.
HTTP/2 multiplex. Бинарные фреймы с stream-id интерливятся в одном TCP. App-уровень видит независимые стримы, HPACK жмёт повторяющиеся headers. Но при потере одного TCP-пакета все стримы стоят — TCP требует in-order delivery. В мобильной lossy-сети HTTP/2 иногда хуже 6×HTTP/1.1.
HTTP/3 поверх QUIC. Стримы независимы на транспорте: потеря пакета в stream-1 не блокирует stream-2 и stream-3. Плюс 0-RTT (повторное подключение шлёт request в первой же UDP-датаграмме) и connection migration (Wi-Fi → LTE без переустановки connection — Connection ID сохраняется).
HTTP/1.1: оставаться, потому что просто.
tcpdump читается глазами, отлажить тривиально.HTTP/2: дефолт для public web.
HTTP/3: дефолт для mobile и edge.
ETag + Cache-Control: всегда. Это не trade-off, это must-have. Conditional GET экономит bandwidth даже на самом плохом транспорте. Стоит дёшево, окупается сразу.
Idempotency-Key на POST: всегда для платежей и заказов. POST не идемпотентен по семантике. Клиент обязан прислать ключ, сервер обязан дедуплицировать. Без этого retry на flaky-сети создаст дубли.
Upgrade: websocket, потом меняет протокол. На HTTP/2 есть rfc 8441 (CONNECT over stream), на HTTP/3 — rfc 9220.Cache-Control: no-cache думая, что это «не кэшировать». Это «кэшируй, но revalidate каждый раз». Чтобы реально запретить — no-store.Idempotency-Key в платежах. Retry на 5xx или timeout создаст дубли. Это не «edge case», это гарантированный сценарий в production.Accept-CH (Client Hints).HTTP в принципе имеет очень узкие случаи неприменимости — он де-факто стандарт. Но конкретные версии и фичи — да:
В курсе:
Снаружи: