Архитектурные ограничения HTTP/1.1
Протокол HTTP/1.1, утверждённый в 1999 году стандартом RFC 2616, создавался для интернета с простыми страницами. Обычный документ состоял из базовой HTML-разметки и нескольких картинок. Для получения каждого элемента браузер открывал отдельное TCP-соединение либо использовал режим Keep-Alive, пропуская запросы по очереди внутри одного соединения.
С ростом сложности веб-приложений число ресурсов на одной странице выросло до сотен. Здесь разработчики столкнулись с блокировкой начала очереди, известной как Head-of-Line Blocking на уровне протокола приложений. Если первый тяжелый запрос, например большой скрипт или изображение, обрабатывался медленно, все последующие команды ожидали своей очереди в этом канале связи.
Браузеры начали обходить это ограничение искусственными путями. Программы открывали к одному домену до шести параллельных TCP-сокетов, что увеличивало нагрузку на сетевую инфраструктуру и память серверов. Инженеры прибегали к доменному шардированию, раздавая статику с разных поддоменов, склеивали графику в спрайты и объединяли сотни модулей JavaScript в монолитные файлы, чтобы сократить число сетевых вызовов.
Бинарный слой кадрирования и мультиплексирование
Инженерная группа IETF в мае 2015 года зафиксировала спецификацию RFC 7540, положив в основу проекта протокол SPDY от Google. Главным изменением стал слой бинарного кадрирования (Binary Framing Layer). Протокол перестал использовать текстовые команды вроде GET или POST в чистом виде, преобразовав обмен данными в структурированные бинарные единицы.
Вся коммуникация в HTTP/2 организована через три взаимосвязанные сущности:
- Кадр представляет собой наименьшую неделимую единицу передачи, содержащую заголовок длиной 9 байт с указанием типа, флагов и идентификатора потока.
- Сообщение формирует законченный логический HTTP-запрос или ответ, скомпонованный из одного или нескольких кадров.
- Поток выступает двунаправленным виртуальным каналом внутри установленного TCP-соединения, по которому передаются сообщения с одинаковым идентификатором.
Мультиплексирование работает за счёт чередования таких независимых кадров. Клиент отправляет фреймы заголовков HEADERS и данных DATA сразу для десятков файлов в рамках одной сессии. Сервер принимает этот непрерывный поток байтов, разбирает кадры по их числовым идентификаторам (Stream ID) и передаёт в обработку соответствующим процессам.
Ответы сервера возвращаются точно так же. Страница получает фрагменты стилей, скриптов и изображений параллельно. Задержка при чтении крупного файла больше не замораживает доставку мелких служебных файлов, поскольку их фреймы проскакивают между фреймами тяжелого пакета.
Приоритизация потоков и сжатие HPACK
Параллельная передача требует координации, ведь критический CSS-файл для отрисовки первого экрана страницы важнее фонового аналитического скрипта или картинки из подвала сайта. HTTP/2 поддерживает дерево зависимостей потоков и систему весовых коэффициентов от 1 до 256. Клиент сообщает серверу, какие ресурсы обладают наивысшим приоритетом, чтобы распределить доступную полосу пропускания в первую очередь на них.
Второй фактор ускорения загрузки заключается в алгоритме сжатия метаданных HPACK (RFC 7541). В HTTP/1.1 заголовки передавались открытым текстом в каждом запросе, многократно дублируя строки User-Agent, Cookie и Accept. HPACK использует статическую таблицу из 61 часто встречающегося заголовка, динамическую таблицу для сохранения уникальных значений конкретной сессии и алгоритм Хаффмана. Это сокращает объем служебного трафика заголовков на 80-90 процентов.
Server Push: концепция, реализация и реальные трудности
Технология Server Push задумывалась как инструмент ликвидации лишних сетевых кругов обращения (Round Trip Time, RTT). Классический сценарий требует сначала загрузить HTML, распарсить разметку, найти ссылки на стили и отправить повторный запрос за CSS. Server Push позволял серверу сразу после запроса index.html отправить фрейм PUSH_PROMISE, а следом начать передачу файла main.css до явного требования браузера.
Фрейм PUSH_PROMISE содержит метаданные обещанного ресурса и резервирует чётный номер потока (клиентские потоки всегда имеют нечётные номера). Браузер, получив такое уведомление, понимал намерение сервера и сохранял поступающие данные во временный кэш до востребования рендерером.
На практике механизм вызвал серьезные технические проблемы. Серверу крайне сложно достоверно узнать, есть ли уже данный файл в локальном кэше пользователя. При каждом переходе по страницам система повторно проталкивала через сеть килобайты стилей и скриптов, растрачивая мобильный трафик и создавая конкуренцию за пропускную способность соединения.
В итоге браузерный движок Chromium полностью отключил поддержку HTTP/2 Server Push в версии Chrome 106 осенью 2022 года. На замену этой идее пришёл статус ответа 103 Early Hints, который отправляет клиенту ссылки на критические ресурсы через заголовок Link с атрибутом rel=preload, сохраняя контроль за кэшем на стороне браузера.
Как изменилась разработка веб-приложений
Переход на вторую версию протокола перевернул старые правила оптимизации фронтенда. Практики, считавшиеся обязательными десять лет назад, стали антипаттернами.
- Объединение графики в CSS-спрайты потеряло актуальность, так как отдельные мелкие SVG или WebP-файлы теперь передаются параллельно и кэшируются независимо друг от друга.
- Доменное шардирование стало вредить производительности, разрушая мультиплексирование и требуя дополнительных DNS-резолвов и повторных TLS-рукопожатий для каждого имени хоста.
- Инлайнинг тяжелых стилей прямо в HTML-разметку перестал давать выигрыш, поскольку внешние файлы CSS скачиваются мгновенно по общему мультиплексированному каналу.
Частые вопросы
Можно ли развернуть HTTP/2 без HTTPS-шифрования?
Формально стандарт RFC 7540 описывает работу по открытому каналу под идентификатором h2c (HTTP/2 Cleartext). Однако на практике все популярные современные браузеры поддерживают HTTP/2 исключительно поверх TLS с использованием расширения ALPN. Для реального применения шифрование обязательно.
Почему HTTP/2 использует нечётные и чётные номера потоков?
Разделение номеров потоков предотвращает конфликты идентификаторов при одновременной инициализации передачи. Потоки, созданные клиентом, нумеруются нечётными числами (1, 3, 5). Потоки, которые открывает сервер в рамках отправки уведомлений или Push-сообщений, получают чётные номера (2, 4, 6).
Чем 103 Early Hints лучше технологии Server Push?
При использовании ответа 103 Early Hints сервер лишь подсказывает браузеру список важных ресурсов, пока генерирует основной HTML-документ. Браузер сам проверяет свой локальный кэш и запрашивает файлы только при реальной необходимости, исключая холостую передачу лишних данных.
Как мультиплексирование влияет на расход оперативной памяти сервера?
Работа с одним TCP-сокетом вместо шести снижает число выделяемых системных дескрипторов и буферов сокетов. Одновременно с этим сервер вынужден выделять память под поддержание бинарного контекста, динамических таблиц HPACK и очередей фреймов для каждого активного клиента.