Эволюция HTTP

Предпосылки: HTTP (запрос-ответ, заголовки), TCP (соединение, handshake, congestion control), TLS (рукопожатие, латентность).

TLS | WebSocket

Ранний веб делал почти всё через отдельные TCP-соединения. Пока страница состояла из одного HTML-файла, это терпимо. Но как только страницы стали собираться из CSS, JavaScript, изображений и API-вызовов, выяснилось, что проблема уже не только в размере ответа, а в накладных расходах на соединения и в блокировках внутри них. Каждая следующая версия HTTP снимала конкретное ограничение предыдущей.

HTTP/1.0: одно соединение — один запрос

В HTTP/1.0 после получения ответа соединение закрывается. На следующий запрос — новое TCP-соединение, новый handshake, новый slow start. Для страниц с несколькими ресурсами это работало, но к концу 90-х веб стал сложнее: страницы стали содержать десятки файлов, и большую часть времени браузер тратил не на получение данных, а на установку соединений.

HTTP/1.1: keep-alive и предел одного соединения

HTTP/1.1 добавил keep-alive — по умолчанию соединение остаётся открытым, несколько запросов проходят через одно TCP-соединение. Это устранило накладные расходы на рукопожатия.

Но keep-alive решил только первую проблему: соединение перестало закрываться после каждого ответа. Вторая проблема осталась: как провести через одно соединение несколько независимых запросов так, чтобы медленный ответ не задерживал остальные.

В обычной модели HTTP/1.1 браузер либо ждёт завершения текущего обмена, либо открывает ещё одно TCP-соединение. Теоретически стандарт поддерживал pipelining — несколько запросов подряд по одному соединению, без ожидания первого ответа. Но ответы всё равно обязаны возвращаться по порядку. Если первый запрос медленный или застрял, всё, что стоит за ним, тоже ждёт. Это и есть head-of-line blocking на уровне HTTP/1.1.

Браузеры обходили это, открывая 6–8 параллельных TCP-соединений к одному серверу. Это работало, но не масштабировалось: каждое соединение проходит отдельный slow start и потребляет ресурсы на обоих концах.

HTTP/2: мультиплексирование

HTTP/2 добавил именно тот механизм, которого не хватало HTTP/1.1. Вместо текстового формата HTTP/2 использует бинарный протокол: данные разбиваются на фреймы, а фреймы группируются в потоки (streams) — независимые логические последовательности внутри одного соединения. Одно TCP-соединение может нести много таких потоков одновременно. Это и есть мультиплексирование.

Ответ на медленный запрос больше не блокирует быстрые: фреймы разных потоков чередуются в произвольном порядке. Браузер отправляет 50 запросов через одно соединение, и ответы приходят по мере готовности.

Дополнительно HTTP/2 добавил сжатие заголовков (HPACK). В HTTP/1.1 заголовки передаются текстом с каждым запросом — Cookie, User-Agent, Accept повторяются десятки раз. HPACK использует таблицу часто встречающихся заголовков и передаёт только различия.

Server Push позволял серверу отправить ресурс до того, как клиент его запросит — например, отдать CSS вместе с HTML. На практике эта фича оказалась сложна в управлении и в большинстве реализаций отключена или удалена.

HTTP/2 — основа для gRPC, который использует мультиплексирование и бинарные фреймы для вызова функций между сервисами.

Оставшаяся проблема: TCP HOL blocking

HTTP/2 устранил HOL blocking на уровне HTTP, но проблема осталась ниже — на уровне TCP. TCP гарантирует порядок доставки: если один пакет потерялся, все последующие пакеты ждут его повторной передачи, даже если они принадлежат другим HTTP-потокам. Потеря одного пакета блокирует все потоки в соединении.

При стабильном соединении (потери < 0.1%) это незаметно. Но при нестабильном (мобильная сеть, Wi-Fi) потери растут, и TCP HOL blocking становится ощутимым.

HTTP/3 и QUIC: переход на UDP

HTTP/3 решает TCP HOL blocking радикально — заменой TCP на QUIC. QUIC — транспортный протокол поверх UDP, который реализует надёжность, управление потоком и congestion control самостоятельно, но с ключевым отличием: потоки изолированы. Потеря пакета в одном потоке не влияет на другие.

QUIC интегрирует TLS 1.3 прямо в протокол — транспортное и криптографическое рукопожатие происходят одновременно, а не последовательно. Результат: установка соединения за 1 RTT вместо 2–3 (TCP handshake + TLS handshake). При повторном подключении — 0-RTT.

QUIC также лучше справляется с миграцией соединения: при переключении телефона с Wi-Fi на мобильную сеть IP-адрес меняется. TCP-соединение при этом разрывается (оно привязано к IP и порту). QUIC идентифицирует соединение по connection ID, а не по IP — переключение происходит бесшовно.

Хронология

ВерсияГодТранспортКлючевое улучшение
HTTP/1.01996TCPБазовый запрос-ответ
HTTP/1.11997TCPKeep-alive, chunked transfer
HTTP/22015TCPМультиплексирование, сжатие заголовков
HTTP/32022QUIC/UDPУстранение TCP HOL blocking, быстрый handshake

Sources


TLS | WebSocket