Кратко

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

В журнале появляется событие «HPACK cache hit». Переданный блок действительно оказался коротким: вместо длинной строки декодер получил ссылку на уже известную запись. Но слово «кеш» незаметно превратило экономию байтов в утверждение о повторном использовании ответа.

HPACK хранит пары имени и значения, чтобы восстановить список полей. HTTP-кеш хранит сообщения-ответы, чтобы при определённых условиях обслужить будущий запрос. Оба механизма используют повторяемость, однако их объекты, область действия и полномочия различны.

Роберто Пеон вместе с Эрве Рюэлланом написал RFC 7541, определивший HPACK. Его профиль IETF также перечисляет RFC 7540 — первоначальную спецификацию HTTP/2. Биография докладчика QCon San Francisco 2012 называла Пеона в тот момент инженером Google и одним из создателей SPDY. Это исторический снимок, а не сведения о нынешней должности. Авторы действующей RFC 9113 — другие люди.

Сначала восстанавливается порядок байтов

RFC 7541 определяет список заголовков как упорядоченный набор пар «имя — значение». Одинаковые пары могут повторяться. Для уровня сжатия имена и значения — непрозрачные последовательности октетов, а их порядок должен сохраниться после декодирования.

Поэтому восстановление cache-control: private ещё не означает применение private. HPACK не разбирает смысл директивы и не решает, куда положить ответ. Он возвращает октеты; HTTP-слой интерпретирует их позже.

В формате есть статическая и динамическая таблицы. Статическая задана заранее и не меняется. Динамическая сначала пуста, пополняется при обработке блоков, допускает дубликаты и вытесняет старые записи, чтобы уложиться в ограничение памяти.

Индекс указывает в изменчивое пространство. Новая запись занимает младшую динамическую позицию и сдвигает старые. После вытеснения прежнее число уже не сохраняет объект. В другом соединении оно может означать иное поле или оказаться недопустимым. Это координата состояния, а не постоянный идентификатор.

У каждого направления своя история

RFC 7541 требует полностью разделять динамические таблицы кодирования и декодирования на одном узле. Клиент кодирует запросы в одном контексте, а декодирует ответы в другом. Сервер поддерживает встречную пару.

RFC 9113 закрепляет эти контексты за соединением HTTP/2. Каждый узел имеет кодер и декодер HPACK, которыми пользуются все блоки полей в соответствующем направлении. Динамическая таблица — основное изменяемое состояние.

Записи «поле было в таблице соединения» недостаточно. Нужны сторона, направление, номер блока и предыдущее поколение таблицы. Если получатель пропустит вставку, следующий индекс может указывать у него на другие октеты, чем у отправителя.

Даже блок, сообщение которого планируется отбросить, приходится полностью собрать и распаковать. Он способен изменить контекст, необходимый последующим потокам. Решение приложения отказаться от одного потока не отменяет последовательность сжатия всего направления.

Лимит памяти проходит через подтверждение

В начале соединения HTTP/2 начальный максимум таблицы равен 4096 байтам. Декодер объявляет предел через SETTINGS_HEADER_TABLE_SIZE; кодер выбирает эффективный размер не выше него и сообщает изменения инструкцией HPACK.

4096 — начальное правило протокола, а не результат обследования всех реализаций. Уменьшение предела зависит от порядка настройки и её подтверждения. После подтверждения следующий блок при необходимости должен начаться с корректного обновления размера.

Вставка вытесняет старые записи, пока таблица не уложится. Если одна запись больше максимума, таблица очищается, а запись не сохраняется. Такое поведение описывает ограниченный словарь, но не долговременное хранилище.

Ошибка имеет масштаб соединения. Если блок полей нельзя декодировать, RFC 9113 предписывает завершить соединение с COMPRESSION_ERROR. Проблема проявилась в одном потоке, но повреждённый контекст нужен и другим потокам. Это доказательство рассинхронизации компрессии, а не оценка смысла запроса.

HTTP-кеш отвечает за повторное использование

RFC 9111 называет кешем локальное хранилище сообщений-ответов и подсистему, которая управляет сохранением, поиском и удалением. Цель кеша — использовать прежний ответ для нового запроса, если правила это допускают.

Для выбора требуется ключ, включающий как минимум метод и целевой URI. Vary может добавить сравнение полей запроса. Затем проверяются свежесть, допустимость устаревшего ответа или успешная валидация. Директивы no-store, private, no-cache и правила авторизованных запросов влияют на решение.

У динамической таблицы нет ни URI, ни тела ответа, ни часов свежести. Она способна запомнить октеты etag: "blue", не сохранив представление. Она сжимает age: 300, но не пересчитывает возраст. Она возвращает vary: accept-language, не сравнивая языки двух запросов.

Особенно нагляден cache-control: private. Кодер может индексировать поле из-за его повторяемости. Вставка не создаёт частный кеш и не выносит решения для общего кеша. Семантику применяет кеш-компонент после разбора сообщения. Запись HPACK может исчезнуть, пока ответ остаётся в хранилище, или сохраниться, хотя ответ никогда не кэшировался.

Верно и обратное: настоящий кешированный ответ может уйти с буквальными полями, статическими ссылками либо по новому соединению с пустой динамической таблицей. Два события не являются ни обязательными условиями, ни доказательствами друг друга.

Успешная распаковка доказывает немногое

Допустимый индекс показывает, что в данной точке получатель имел совместимое состояние и смог восстановить поле. После этого ещё нужно проверить допустимость имени и значения, свойства канала, преобразования посредников и реакцию приложения.

Источник мог не получить запрос. Посредник мог переписать поле. Приложение может отвергнуть синтаксически корректное значение. HPACK не заполняет эти пробелы.

Есть и граница конфиденциальности. RFC 7541 предупреждает: тот, кто может влиять на поля и наблюдать длину сжатого представления, способен прощупывать состояние таблицы. TLS скрывает содержимое, но не всю информацию о длине. Не каждое изменение коэффициента сжатия означает атаку, однако и экономия байтов не доказывает безопасность.

Форма «никогда не индексировать» оставляет значение буквальным и обязывает посредника сохранить этот выбор при перекодировании. Она сокращает конкретный риск, но не делает предсказуемый секрет неугадываемым, не скрывает все длины и не заменяет контроль доступа.

Два журнала для двух решений

Журнал сжатия должен содержать идентификатор соединения, роли узлов, направление, поток и порядок блоков. В него входят объявленный предел, подтверждение, фактический размер, вставки, вытеснения, обновления, способ представления и результат декодирования.

Журнал HTTP начинается после восстановления: метод, URI, статус, путь к источнику и разобранная семантика. При работе кеша он добавляет ключ, значения Vary, решение о сохранении, возраст, свежесть, валидаторы, правила авторизации, результат повторной проверки и ответ приложения.

Сетевой захват может доказать синхронность HPACK, ничего не зная о кеше. Журнал кеша может доказать повторное использование, не показывая сжатие на проводе. Отсутствующие поля нельзя достраивать догадкой. Память о поле не даёт права повторно использовать ответ.

Источники