2026 г.

HTTP/2 и HTTP/3: эволюция протокола веба

Проект ЦИТадель

HTTP/2 стандартизован в 2015 году, HTTP/3 — в 2022-м. Обе версии сохранили знакомую семантику HTTP: методы, статусы и поля. Менялось прежде всего представление обмена и его связь с транспортом. Одна из главных причин этих изменений — блокировка начала очереди (head-of-line blocking): независимая работа задерживается из-за более ранней. В HTTP/1.1 эта проблема проявлялась в очереди ответов, HTTP/2 устранил её на уровне HTTP, но оставил на уровне TCP, а QUIC перенёс независимые потоки в транспорт. Это важная, хотя и не единственная линия эволюции: новые версии также меняли сжатие полей, приоритеты, установление соединения и его поведение при смене сети.

1. HTTP/1.1 и его пределы

У сообщения HTTP/1.1 текстовые начальная строка и поля, а тело может содержать произвольные байты. Соединение TCP можно использовать повторно, но без конвейеризации следующий запрос обычно отправляют после ответа на предыдущий. Стандарт разрешает pipelining — несколько запросов без ожидания, — однако ответы всё равно должны идти в порядке запросов. Один медленный ответ поэтому задерживает готовые ответы за ним, а неодинаковая поддержка посредниками и серверами мешала надёжно применять конвейеризацию в браузерах.

Историческим обходом стали несколько параллельных TCP-соединений к одному источнику — часто до шести в браузере. Это создавало несколько независимых очередей, но каждое соединение отдельно проходило установление, управление перегрузкой и занимало ресурсы клиента и сервера. Сайты также распределяли ресурсы по поддоменам и объединяли их в спрайты или крупные пакеты. После появления мультиплексирования эти приёмы перестали быть безусловными оптимизациями: дополнительный домен требует отдельного разрешения и соединения, а чрезмерный пакет ухудшает кэширование и доставляет неиспользуемый код. Объединение всё ещё может быть полезно, но его проверяют измерением, а не применяют по привычке.

2. HTTP/2: бинарные кадры и потоки

HTTP/2 вырос из SPDY; действующая спецификация — RFC 9113. Он оставил семантику HTTP прежней и добавил бинарный слой кадров:

  • Мультиплексирование. Запрос и ответ образуют поток с номером, а кадры разных потоков передаются вперемешку по одному соединению. Медленный ответ больше не требует задерживать готовый ответ другого потока на уровне HTTP. Число одновременных потоков и окна управления потоком согласуются сторонами, поэтому «одно соединение» не означает неограниченный параллелизм.
  • Сжатие полей HPACK. Статическая таблица кодирует распространённые имена и значения, динамическая — повторяющиеся поля данного соединения, а строковые литералы могут использовать фиксированный код Хаффмана. Это не обычный потоковый gzip: после атаки CRIME формат специально ограничили и добавили представление «никогда не индексировать» для чувствительных значений. Тем не менее размер остаётся побочным каналом, а BREACH относится к отдельному случаю сжатия тела HTTP-ответа и одним HPACK не устраняется.
  • Приоритеты и server push. Исходное дерево зависимостей потоков оказалось сложным и применялось непоследовательно. RFC 9218 определил более простую расширяемую схему с приоритетом срочности и признаком постепенной передачи. Server push остаётся частью протокола, но основные браузеры отказались от него: серверу трудно знать состояние клиентского кэша, и лишняя передача часто перекрывает выигрыш. Для раннего обнаружения ресурса обычно применяют preload и ответ 103 Early Hints.

Спецификация допускает HTTP/2 без шифрования (h2c), но обычные веб-браузеры используют HTTP/2 для сайтов поверх TLS. Клиент и сервер выбирают идентификатор h2 через ALPN в рукопожатии, разобранном в статье о TLS.

3. Блокировка на уровне TCP

HTTP/2 разделяет запросы логически, но TCP предоставляет приложению один упорядоченный поток байтов. Если сегмент потерян, уже пришедшие более поздние байты не выдаются приложению до заполнения разрыва. Поэтому потеря перед кадрами одного HTTP/2-потока задерживает также последующие кадры других потоков. TCP может продолжать приём и быстро повторить потерянные данные, однако блокировка доставки приложению остаётся.

У HTTP/1.1 несколько соединений имели независимые потоки TCP, поэтому потеря в одном не останавливала остальные. В HTTP/2 обычно используется одно соединение, и на пути с потерями общая TCP-очередь иногда уменьшает или обращает выигрыш от мультиплексирования. Результат зависит от RTT, характера потерь, алгоритма перегрузки, числа и размера ресурсов; одного процента потерь недостаточно, чтобы заранее назвать победителя. Но причина возможного ухудшения находится ниже HTTP, поэтому исправлять её потребовалось в транспорте.

4. QUIC: потоки в транспорте

Развёртыванию нового транспорта мешает окостенение сети: межсетевые экраны, NAT и другие посредники могут отбрасывать незнакомые IP-протоколы или предполагать конкретное устройство TCP. QUIC (RFC 9000) передаёт пакеты в UDP-датаграммах, что позволяет разворачивать его в пользовательском пространстве и проходить через большее число существующих устройств, чем с новым номером IP-протокола. UDP всё же блокируется в части сетей, поэтому запасной путь необходим.

  • Независимые потоки. QUIC предоставляет несколько упорядоченных потоков внутри соединения. При потере пакета ждут данные только тех потоков, кадры которых находились в этом пакете; остальные могут продвигаться. Если один пакет содержал кадры нескольких потоков, ждать будут все они. Кроме того, реакция управления перегрузкой на потерю уменьшает скорость всего соединения — QUIC устраняет межпоточную блокировку доставки, а не последствия потерь вообще.
  • Встроенное рукопожатие TLS 1.3. QUIC использует сообщения TLS, но не слой записей TLS поверх отдельного транспортного рукопожатия. При первом контакте защищённые данные приложения доступны после одного сетевого круга; для TCP с TLS 1.3 обычно нужны отдельный круг TCP и круг TLS. Возобновление допускает 0-RTT с теми же ограничениями повторного воспроизведения.
  • Защита служебных данных. Полезная нагрузка пакетов и большая часть управляющих полей зашифрованы, чтобы ограничить вмешательство посредников и будущие жёсткие предположения о формате. Видимыми остаются данные, необходимые для доставки и установления соединения, в том числе UDP/IP-заголовки, часть заголовка QUIC, идентификаторы соединения и длины.
  • Миграция соединения. TCP-соединение определяется адресами и портами. QUIC использует выданные сторонами идентификаторы и проверяет новый путь, поэтому реализация может сохранить соединение при смене адреса, например после перехода с Wi-Fi на мобильную сеть. Это возможность протокола, а не обещание каждого клиента и сервера; политика приложения или отсутствие пригодного идентификатора может привести к новому соединению.
  • Обновляемая реализация. QUIC часто работает в библиотеке приложения, поэтому восстановление потерь и управление перегрузкой можно обновлять вместе с браузером или сервером. Цена — дополнительная работа процессора и необходимость новых средств диагностики; пакетная обработка, GSO и аппаратная разгрузка постепенно уменьшают разрыв с TCP.

5. HTTP/3: семантика HTTP поверх QUIC

HTTP/3 (RFC 9114) отображает семантику RFC 9110 на QUIC. Каждому запросу и его ответу соответствует один двунаправленный поток, инициированный клиентом; отдельные однонаправленные потоки несут управление соединением и состояние сжатия. Сам QUIC отвечает за мультиплексирование и упорядочение внутри каждого потока.

HTTP/2 не может без изменений перенести HPACK на независимую доставку: декодирование очередного блока может зависеть от обновления общей таблицы, которое ещё не пришло в другом потоке. QPACK выносит обновления таблицы в отдельные однонаправленные потоки и позволяет кодировщику ограничивать число блоков, способных ждать это состояние. Так реализация выбирает компромисс между степенью сжатия и риском блокировки.

Клиент может узнать об HTTP/3 из заголовка Alt-Svc, полученного по HTTP/1.1 или HTTP/2, и запомнить альтернативу. DNS-запись HTTPS с параметром ALPN позволяет сообщить поддержку до первого HTTP-соединения. Эти механизмы не гарантируют использование HTTP/3: клиент учитывает свой кэш, поддержку, доступность UDP и результат попытки соединения.

6. Практические следствия

  • Где возможен выигрыш. QUIC особенно полезен при большом RTT, потерях и смене клиентской сети: он совмещает установление транспорта с TLS и не задерживает неповреждённые потоки из-за разрыва в другом. На стабильном коротком пути HTTP/2 может дать близкий результат, а конкретная реализация HTTP/3 — даже проиграть по затратам процессора.
  • Резервный путь. UDP/443 блокируют или ухудшают некоторые корпоративные сети и посредники. Клиент должен уметь вернуться к доступной версии HTTP, обычно HTTP/2, а серверу следует сохранять рабочий TCP-путь. HTTP/2 не является формально обязательным резервом для HTTP/3, но переход сразу к HTTP/1.1 обычно лишает соединение мультиплексирования.
  • Эксплуатация. Проверяют не только среднее время, но и долю успешных h3-соединений, задержку перед переходом на TCP, CPU, память и ошибки по сетям и регионам. Неудачная попытка QUIC перед быстрым HTTP/2 может увеличить время ожидания пользователя, хотя каждый протокол по отдельности исправен.
  • Состояние реализаций. HTTP/3 поддерживают основные браузеры, CDN и несколько серверов. Возможности пакета надо проверять, а не выводить из номера версии: например, модуль HTTP/3 в nginx по-прежнему помечен как экспериментальный и не собирается по умолчанию.

7. Практикум: измеряем блокировку очереди

Задание 1. Инвентаризация. В инструментах разработчика браузера включите колонку протокола и перезагрузите несколько сайтов с отключённым кэшем. Для h3 найдите Alt-Svc или HTTPS-запись DNS и различите «сервер объявил h3» и «запрос действительно ушёл по h3». В терминале проверьте возможности клиента командой curl --version, затем сравните curl -sv --http2 и, если сборка поддерживает QUIC, curl -sv --http3-only.

Задание 2. Сервер с h2 и h3. Для современного nginx нужны собранные модули HTTP/2 и HTTP/3, сертификат и совместимая TLS-библиотека. Минимальный фрагмент внутри одного блока server:

listen 443 ssl;                 # TCP: HTTP/1.1 и HTTP/2
http2 on;
listen 443 quic reuseport;      # UDP: HTTP/3
add_header Alt-Svc 'h3=":443"; ma=86400' always;

Проверьте конфигурацию штатной командой nginx до перезагрузки. Подтвердите h2 и h3 отдельными запросами и по журналу сервера. Первый визит может начаться сразу с h3 при подходящей DNS-записи или сохранённой альтернативе, поэтому не используйте номер посещения как доказательство. Заблокировав UDP/443 только в лабораторной среде, сравните curl --http3-only, который должен завершиться ошибкой, с curl --http3 или браузером: при рабочем TCP-пути они должны продолжить по нему. Запишите задержку перехода.

Задание 3. Подготовка измерения. Создайте страницу со ста небольшими ресурсами одинакового размера и запретите их кэширование. Используйте один и тот же клиент, одну версию сервера и один маршрут. Для h2 и h3 убедитесь по трассировке или журналу, что запросы мультиплексируются в одном соединении; цикл из ста отдельных процессов curl этого не проверяет. Для HTTP/1.1 ограничьте параллелизм шестью соединениями. Сделайте не менее десяти повторов каждого варианта и записывайте медиану и высокий перцентиль, а не один лучший результат.

Задание 4. Потери и задержка. На отдельном стенде последовательно задайте 0%, 0,5%, 1% и 3% потерь при фиксированной задержке, например:

$ tc qdisc replace dev eth0 root netem delay 40ms loss 1%
$ tc qdisc del dev eth0 root

Имя интерфейса замените фактическим и обязательно удалите правило после опыта. Для каждого режима измерьте HTTP/1.1, h2 и h3, постройте график «потери → полное время» и сохраните число повторных передач. Гипотеза состоит в том, что под потерями h3 меньше задерживает независимые ресурсы, чем одно h2-соединение. Результат может отличаться из-за алгоритмов перегрузки, размещения кадров в пакетах и реализации; это повод разобрать трассу, а не объявить опыт неудачным.

Задание 5*. Внутри QUIC. Экспортируйте сеансовые ключи способом из практикума TLS либо включите qlog в поддерживающем клиенте или сервере. Найдите потерянный пакет и определите, какие потоки содержали его кадры. Проверьте одновременно два эффекта: другие потоки продолжают доставку без ожидания этих байтов, но окно перегрузки соединения реагирует на потерю.

Задание 6*. Миграция. Начните достаточно долгую передачу по h3 и смените доступный сетевой путь. По qlog и журналу сервера установите, сохранился ли идентификатор соединения, прошла ли проверка нового пути и продолжился ли тот же поток. Затем повторите по h2. Если клиент создал новое QUIC-соединение, проверьте ограничения реализации и политики: сам факт поддержки HTTP/3 ещё не доказывает поддержку миграции в выбранном сценарии.

Итоги

  • HTTP/1.1 допускает конвейеризацию, но порядок ответов создаёт блокировку; несколько TCP-соединений исторически обходили её ценой дополнительных ресурсов.
  • HTTP/2 мультиплексирует бинарные кадры потоков в одном соединении, сжимает поля через HPACK и согласуется в браузерах через TLS ALPN.
  • Единый упорядоченный поток TCP переносит блокировку ниже: потерянные байты задерживают доставку последующих кадров всех HTTP/2-потоков.
  • QUIC предоставляет независимые потоки, совмещает транспортное и TLS-рукопожатия и поддерживает миграцию пути; потери всё равно влияют на потоки из потерянного пакета и на общее управление перегрузкой.
  • HTTP/3 использует один двунаправленный QUIC-поток на запрос и ответ, QPACK — для полей, а Alt-Svc и DNS HTTPS — для обнаружения.
  • HTTP/3 следует внедрять с рабочим резервным TCP-путём и оценивать по реальным сетям, включая задержку неудачных попыток и стоимость процессора.

Литература

  1. RFC 9110: HTTP Semantics, RFC 9113: HTTP/2, RFC 9114: HTTP/3 и RFC 9000: QUIC.
  2. RFC 7541: HPACK, RFC 9204: QPACK и RFC 9218: Extensible Prioritization Scheme for HTTP.
  3. Документация nginx: модуль HTTP/3 — актуальные требования, ограничения и пример конфигурации.
  4. I. Grigorik, «High Performance Browser Networking»: hpbn.co — основы TCP и HTTP/2; для изменяемых деталей следует сверяться с действующими RFC.
  5. Материалы ЦИТадели: «TLS: как устроено защищённое соединение», «Надёжность поверх ненадёжной сети» и «Веб-производительность».
404 Not Found

404 Not Found


nginx/1.24.0 (Ubuntu)

Связь с редакцией