Загружаем научный разбор
Подготавливаем текст, источники и редакционные примечания без изменения разметки страницы.
Подготавливаем текст, источники и редакционные примечания без изменения разметки страницы.
Автор: Казачкин Даниил Михайлович · Обновлено
Разбор RFC 9111 на собственном сценарии обновления статьи: max-age, no-cache, 304, Vary и различие частного и общего кэша.
Редактор исправил статью, а читатель ещё несколько минут видит старую версию. При этом повторный запрос может вообще не попасть на сервер: браузер использует сохранённый ответ согласно его политике свежести. Прежде чем отключать всё кэширование, полезно понять, какое обещание сервер уже дал клиенту и когда клиент обязан проверить, не изменилась ли версия.
Кэширование решает две связанные задачи: повторно использовать данные и определить, когда такое использование допустимо. Проверка версии также может экономить передачу: клиент уже хранит тело документа, а серверу достаточно подтвердить его актуальность. Для диагностики важно различать сохранение ответа, использование без обращения к серверу и условную проверку.
RFC 9111 описывает хранение, свежесть и валидацию HTTP-ответов. Директива max-age задаёт срок свежести; no-cache требует успешной проверки перед повторным использованием, а no-store запрещает кэшу сохранять соответствующий обмен. Private ограничивает хранение частным кэшем. Эти директивы нельзя понимать по бытовому переводу их названий.
RFC 9110 определяет валидаторы и условные запросы, включая ETag, If-None-Match и ответ 304. Валидатор идентифицирует представление ресурса, а не просто наличие файла с тем же именем. Для правильно выбранного GET сервер может подтвердить, что уже имеющаяся версия подходит, не отправляя её тело повторно.
Пусть в 12:00 браузер получает статью с ETag "article-v7" и Cache-Control: public, max-age=60. В 12:00:20 редактор сохраняет новую версию. Запрос того же читателя в 12:00:30 может получить старое тело из свежего кэша. Это ожидаемое последствие выбранной политики, а не доказательство, что сохранение на сервере не сработало.
После истечения свежести кэш отправляет условный запрос. Если версия изменилась, сервер возвращает новое тело и новый ETag. Если версия осталась прежней, ответ 304 позволяет продолжить использование сохранённого тела с обновлёнными метаданными. Ниже показан второй случай; это учебная последовательность заголовков, а не готовый сервер.
GET /articles/cache-example HTTP/1.1
Host: learning.example
If-None-Match: "article-v7"
HTTP/1.1 304 Not Modified
Date: Fri, 25 Sep 2026 09:02:00 GMT
ETag: "article-v7"
Cache-Control: public, max-age=60Теперь поменяем требование: редакторские исправления должны проверяться при каждом повторном использовании. Для такого сценария возможно хранение с no-cache и валидатором. Браузер может сохранить тело, но должен сверять его перед использованием. Если же хранение вообще недопустимо по требованиям продукта, нужна другая политика; сходство названий no-cache и no-store не делает их взаимозаменяемыми.
Собственная функция ниже иллюстрирует только развилку «использовать свежий ответ или проверять версию». Она получает уже рассчитанный возраст, не разбирает HTTP-заголовки и не реализует RFC целиком. Проверки показывают граничный момент и принудительную валидацию. Такая маленькая модель удобна для согласования продуктового требования перед настройкой сервера.
def next_action(age_seconds, max_age_seconds, must_validate=False):
if must_validate or age_seconds >= max_age_seconds:
return "validate"
return "reuse"
assert next_action(30, 60) == "reuse"
assert next_action(60, 60) == "validate"
assert next_action(0, 60, must_validate=True) == "validate"
print(next_action(61, 60)) # validateРеальный возраст ответа нельзя всегда считать как время с последней загрузки в браузере: между origin и пользователем могут находиться общие кэши. Поэтому CDN, reverse proxy и браузер нужно рассматривать как цепочку. В диагностике сохраняйте Date, Age, Cache-Control, ETag и фактический статус ответа. Без этой информации повторная загрузка страницы мало говорит о том, какой слой вернул старую версию.
Представим, что страница зависит от выбранного языка. Если кэш считает ключом только URL, одна языковая версия может быть ошибочно использована для другого запроса. Заголовок Vary позволяет учесть соответствующие поля запроса. Однако бесконтрольное добавление множества измерений создаёт много вариантов и снижает пользу кэша: ключ должен отражать реальные причины изменения ответа.
Персонализированный ответ требует отдельного решения. Не переносите политику публичной статьи на кабинет пользователя только потому, что оба ответа имеют тип text/html. Определите, кто может разделять экземпляр ответа, какие cookies или авторизационные данные влияют на него и что разрешено хранить. Для теста используйте двух пользователей и проверяйте фактическое содержимое, а не только красивый заголовок в конфигурации.
Наши примеры не воспроизводят полную реализацию кэша: опущены расчёт возраста, эвристическая свежесть, объединение метаданных и специальные условия запросов. Они показывают, как из требования к актуальности получается проверяемое поведение. Для реального приложения проверяйте первую загрузку, использование свежей копии, истечение срока, изменение версии и неизменившийся ответ.
Для файлов с версией в имени допустима одна стратегия, для редактируемой статьи — другая. Сначала задайте, сколько устаревания приемлемо и какие ответы могут разделяться между пользователями. Затем согласуйте заголовки на всех слоях и проверьте их в реальном запросе. Тогда HTTP-кэш становится управляемой частью приложения, а исправление проблемы перестаёт сводиться к просьбе читателю очистить браузер.
Самостоятельный русскоязычный разбор ЯдроКода. Описания первоисточников отделены от авторских учебных примеров и инженерных выводов. Материал не является переводом или перепечаткой.
Учебные данные, расчёты, таблицы и программные примеры созданы для этой публикации. Иллюстрации и программный код из первоисточников не воспроизводятся.