Кэширование
Кэширование HTTP
Обзор
Кэш HTTP хранит ответ, связанный с запросом, и повторно использует сохранённый ответ для последующих запросов.
Существует несколько преимуществ повторной используемости. Во-первых, поскольку нет необходимости доставлять запрос на сервер происхождения, то чем ближе клиент и кэш, тем быстрее будет ответ. Наиболее типичным примером является хранение кэша браузером для запросов браузера.
Кроме того, если ответ можно повторно использовать, сервер происхождения не нуждается в обработке запроса — ему не нужно парсить и маршрутизировать запрос, восстанавливать сессию на основе cookie, обращаться к базе данных за результатами или рендерить шаблонный движок. Это уменьшает нагрузку на сервер.
Правильная работа кэша имеет решающее значение для стабильности системы.
Типы кэшей
В спецификации HTTP Caching существуют два основных типа кэшей: частные кэши и общие кэши.
Частные кэши
Частный кэш связан с конкретным клиентом — обычно это кэш браузера. Поскольку сохранённый ответ не разделятся с другими клиентами, частный кэш может хранить персонализированный ответ для этого пользователя.
С другой стороны, если персонализированное содержимое хранится в кэше, отличном от частного, другие пользователи могут получить доступ к этому содержимому, что может привести к непреднамеренной утечке информации.
Если ответ содержит персонализированное содержимое, и вы хотите сохранить ответ только в частном кэше, вы должны указать директиву private.
Cache-Control: private
Персонализированное содержимое обычно контролируется cookie, но присутствие cookie не всегда указывает на то, что оно частное, и, следовательно, одна cookie не делает ответ частным.
Обратите внимание, что если ответ содержит заголовок Authorization, его нельзя хранить в частном кэше (или общем кэше, если не указан public).
Общий кэш
Общий кэш расположен между клиентом и сервером и может хранить ответы, которые могут быть разделены между пользователями. Общие кэши можно далее подразделить на прокси-кэши и управляемые кэши.
Прокси-кэши
Помимо функции контроля доступа, некоторые прокси реализуют кэширование для уменьшения трафика за пределами сети. Обычно разработчики сервисов этим не управляют, поэтому это необходимо контролировать с помощью соответствующих HTTP-заголовков и т. д. Однако в прошлом устаревшие реализации прокси-кэшей — такие как реализации, которые не понимают стандарт HTTP Caching — часто создавали проблемы для разработчиков.
Для обхода реализаций «старых и не обновлённых прокси-кэшей», которые не понимают современные директивы HTTP Caching, такие как no-store, используются заголовки «всего», например, следующие:
Cache-Control: no-store, no-cache, max-age=0, must-revalidate, proxy-revalidate
Однако в последние годы, по мере распространения HTTPS и шифрования коммуникаций клиент-сервер, прокси-кэши на пути могут только передавать ответ и не могут вести себя как кэш во многих случаях. Поэтому в этом сценарии нет необходимости беспокоиться об устаревших реализациях прокси-кэшей, которые не могут даже видеть ответ.
С другой стороны, если прокси-сервер TLS расшифрует все коммуникации по принципу «человек посередине», установив сертификат от управляемого организацией CA (центра сертификации) на ПК и выполняет контроль доступа и т. д. — возможно просмотреть содержимое ответа и кэшировать его. Однако, поскольку CT (прозрачность сертификатов) стала широко распространённой в последние годы, и некоторые браузеры позволяют использовать только сертификаты, выпущенные с SCT (отметкой времени подписанного сертификата), для этого метода требуется применение корпоративной политики. В такой контролируемой среде нет необходимости беспокоиться о том, что прокси-кэш «устарел и не обновляется».
Управляемые кэши
Управляемые кэши явно развертываются разработчиками сервисов для разгрузки сервера происхождения и эффективной доставки содержимого. Примеры включают обратные прокси, CDN и service worker в сочетании с API кэша.
Характеристики управляемых кэшей варьируются в зависимости от развернутого продукта. В большинстве случаев вы можете контролировать поведение кэша через заголовок Cache-Control и свои собственные конфигурационные файлы или панели управления.
Например, спецификация HTTP Caching фактически не определяет способ явного удаления кэша — но с управляемым кэшем сохранённый ответ можно удалить в любое время через операции в панели управления, вызовы API, перезагрузки и т. д. Это позволяет использовать более проактивную стратегию кэширования.
Также можно игнорировать стандартные протоколы HTTP Caching в пользу явного управления. Например, можно указать следующее, чтобы исключить частный кэш или прокси-кэш, используя свою собственную стратегию кэширования только в управляемом кэше.
Cache-Control: no-store
Например, Varnish Cache использует логику VCL (язык конфигурации Varnish, тип DSL), для обработки хранения кэша, в то время как service worker в сочетании с API кэша позволяют создавать эту логику на JavaScript.
Это означает, что если управляемый кэш намеренно игнорирует директиву no-store, нет необходимости рассматривать его как «не соответствующий» стандарту. Вам следует избегать использования заголовков «всего», но внимательно изучить документацию используемого механизма управляемого кэша и убедиться, что вы правильно контролируете кэш способами, предоставляемыми выбранным механизмом.
Обратите внимание, что некоторые CDN предоставляют свои собственные заголовки, эффективные только для этой CDN (например, Surrogate-Control). В настоящее время ведётся работа над определением заголовка CDN-Cache-Control для стандартизации этих заголовков.
Эвристическое кэширование
HTTP разработан для кэширования по максимуму, поэтому даже если не указан Cache-Control, ответы будут сохраняться и повторно использоваться, если выполняются определённые условия. Это называется эвристическим кэшированием.
Например, рассмотрим следующий ответ. Этот ответ был обновлён в последний раз год назад.
HTTP/1.1 200 OK Content-Type: text/html Content-Length: 1024 Date: Tue, 22 Feb 2022 22:22:22 GMT Last-Modified: Tue, 22 Feb 2021 22:22:22 GMT <!doctype html> …
Известно, что содержимое, которое не обновлялось в течение целого года, не будет обновляться в ближайшее время после этого. Поэтому клиент хранит этот ответ (несмотря на отсутствие max-age) и повторно использует его некоторое время. Срок повторного использования зависит от реализации, но спецификация рекомендует около 10% (в этом случае 0,1 года) времени после сохранения.
Эвристическое кэширование — это обходной путь, который существовал до того, как поддержка Cache-Control стала широко принятой, и в основном все ответы должны явно указывать заголовок Cache-Control.
Актуальность и устарелость на основе возраста
Сохранённые ответы HTTP имеют два состояния: актуальность и устарелость. Состояние актуальности обычно указывает, что ответ всё ещё действителен и может быть повторно использован, а состояние устарелости означает, что кэшированный ответ уже просрочен.
Критерием определения, когда ответ актуален, а когда устарел, является возраст. В HTTP возраст — это время, прошедшее с момента генерации ответа. Это аналогично TTL в других механизмах кэширования.
Рассмотрим следующий пример ответа (604800 секунд — это неделя):
HTTP/1.1 200 OK Content-Type: text/html Content-Length: 1024 Date: Tue, 22 Feb 2022 22:22:22 GMT Cache-Control: max-age=604800 <!doctype html> …
Кэш, который сохранил пример ответа, вычисляет время, прошедшее с момента генерации ответа, и использует результат как возраст ответа.
Для примера ответа значение max-age следующее:
- Если возраст ответа меньше одной недели, ответ актуален.
- Если возраст ответа больше одной недели, ответ устарел.
Пока сохранённый ответ остаётся актуальным, он будет использоваться для удовлетворения запросов клиентов.
Когда ответ сохраняется в общем кэше, необходимо сообщить клиенту о возрасте ответа. Продолжая пример, если общий кэш сохранил ответ на один день, общий кэш отправит следующий ответ последующим запросам клиентов.
HTTP/1.1 200 OK Content-Type: text/html Content-Length: 1024 Date: Tue, 22 Feb 2022 22:22:22 GMT Cache-Control: max-age=604800 Age: 86400 <!doctype html> …
Клиент, который получит этот ответ, посчитает его актуальным на оставшиеся 518400 секунд, разницу между max-age и Age ответа.
Expires или max-age
В HTTP/1.0 актуальность использовалась заголовок Expires.
Заголовок Expires указывает срок жизни кэша, используя явное время, а не указание времени, прошедшего.
Expires: Tue, 28 Feb 2022 22:22:22 GMT
Однако формат времени сложно анализировать, были обнаружены многочисленные ошибки реализации, и возможно вызвать проблемы, намеренно смещая системные часы; поэтому max-age — для указания прошедшего времени — был принят для Cache-Control в HTTP/1.1.
Если оба Expires и Cache-Control: max-age доступны, max-age определён как предпочтительный. Поэтому сейчас нет необходимости предоставлять Expires с учётом широкого использования HTTP/1.1.
Изменение
Способ различения ответов в основном основан на их URL:
Однако содержимое ответов не всегда одинаково, даже если у них одинаковый URL. Особенно при выполнении согласования содержимого ответ сервера может зависеть от значений заголовков запроса Accept, Accept-Language, и Accept-Encoding.
Например, для английского содержимого, возвращаемого с заголовком Accept-Language: en и кэшируемого, нежелательно повторно использовать этот кэшированный ответ для запросов с заголовком запроса Accept-Language: ja. В этом случае вы можете заставить ответы кэшироваться отдельно — в зависимости от языка — добавив "Accept-Language" в значение заголовка Vary.
Vary: Accept-Language
Это заставляет кэш использовать составной ключ, состоящий из URL ответа и заголовка запроса Accept-Language, а не только из URL ответа.
Также, если вы оптимизируете контент (например, для адаптивного дизайна) на основе пользовательского агента, вы можете добавить "User-Agent" в значение заголовка Vary. Однако заголовок запроса User-Agent обычно имеет очень большое количество вариантов, что значительно снижает вероятность повторного использования кэша. Поэтому, если возможно, рассмотрите способ изменения поведения на основе обнаружения функций, а не на основе заголовка запроса User-Agent.
Для приложений, которые используют cookie для предотвращения повторного использования кэшированного персонализированного контента, вы должны указать Cache-Control: private вместо указания cookie для Vary.
Валидация
Застарелые ответы не отбрасываются сразу. HTTP имеет механизм преобразования устаревшего ответа в свежий, обращаясь к серверу источника. Это называется валидацией, или иногда перевалидацией.
Валидация выполняется с помощью условного запроса, который включает заголовок запроса If-Modified-Since или If-None-Match.
Если не изменялось с
Следующий ответ был сгенерирован в 22:22:22 и имеет max-age 1 час, поэтому вы знаете, что он актуальный до 23:22:22.
HTTP/1.1 200 OK Content-Type: text/html Content-Length: 1024 Date: Tue, 22 Feb 2022 22:22:22 GMT Last-Modified: Tue, 22 Feb 2022 22:00:00 GMT Cache-Control: max-age=3600 <!doctype html> …
В 23:22:22 ответ становится устаревшим, и кэш не может быть повторно использован. Следующий запрос демонстрирует клиент, отправляющий запрос с заголовком запроса If-Modified-Since, чтобы спросить сервер, были ли изменения после указанного времени.
GET /index.html HTTP/1.1 Host: example.com Accept: text/html If-Modified-Since: Tue, 22 Feb 2022 22:00:00 GMT
Сервер ответит 304 Not Modified, если содержимое не изменилось с указанного времени.
Поскольку этот ответ только указывает на «отсутствие изменений», тела ответа нет — есть только код состояния — поэтому размер передачи очень мал.
HTTP/1.1 304 Not Modified Content-Type: text/html Date: Tue, 22 Feb 2022 23:22:22 GMT Last-Modified: Tue, 22 Feb 2022 22:00:00 GMT Cache-Control: max-age=3600
Получив этот ответ, клиент восстанавливает хранящийся устаревший ответ в актуальный и может повторно использовать его в течение оставшегося 1 часа.
Сервер может получить время изменения из файловой системы операционной системы, что относительно легко сделать для обслуживания статических файлов. Однако существуют проблемы; например, формат времени сложен и трудно парсится, а распределённые серверы испытывают трудности с синхронизацией времени обновления файлов.
Для решения таких проблем был стандартизирован заголовок ответа ETag в качестве альтернативы.
ETag/Если не совпадает
Значение заголовка ответа ETag — произвольное значение, сгенерированное сервером. Нет ограничений на то, как сервер должен генерировать значение, поэтому серверы могут задавать значение на основе любого метода — например, хеша содержимого тела или номера версии.
Например, если для заголовка ETag используется значение хеша, а значение хеша ресурса index.html — 33a64df5, ответ будет следующим:
HTTP/1.1 200 OK Content-Type: text/html Content-Length: 1024 Date: Tue, 22 Feb 2022 22:22:22 GMT ETag: "33a64df5" Cache-Control: max-age=3600 <!doctype html> …
Если этот ответ устарел, клиент берёт значение заголовка ответа ETag для кэшированного ответа и помещает его в заголовок запроса If-None-Match, чтобы спросить сервер, был ли ресурс изменён:
GET /index.html HTTP/1.1 Host: example.com Accept: text/html If-None-Match: "33a64df5"
Сервер вернёт 304 Not Modified, если значение заголовка ETag, которое он определяет для запрошенного ресурса, совпадает со значением If-None-Match в запросе.
Но если сервер определяет, что запрошенный ресурс теперь должен иметь другое значение ETag, сервер вместо этого ответит с кодом 200 OK и самой последней версией ресурса.
Примечание: При оценке использования ETag и Last-Modified учитывайте следующее: во время повторной валидации кэша, если присутствуют как ETag, так и Last-Modified, ETag имеет приоритет. Поэтому, если вы рассматриваете только кэширование, вы можете подумать, что Last-Modified излишне. Однако Last-Modified полезно не только для кэширования; это стандартный заголовок HTTP, также используемый системами управления контентом (CMS) для отображения времени последнего изменения, поисковыми роботами для регулирования частоты сканирования и в других целях. Поэтому, учитывая общую экосистему HTTP, предпочтительнее указывать как ETag, так и Last-Modified.
Принудительная перевалидация
Если вы не хотите, чтобы ответ был повторно использован, а хотите всегда получать последний контент с сервера, вы можете использовать директиву no-cache для принудительной валидации.
Добавив Cache-Control: no-cache к ответу вместе с Last-Modified и ETag, как показано ниже, клиент получит ответ 200 OK, если запрошенный ресурс был обновлён, или ответ 304 Not Modified, если запрошенный ресурс не был обновлён.
HTTP/1.1 200 OK Content-Type: text/html Content-Length: 1024 Date: Tue, 22 Feb 2022 22:22:22 GMT Last-Modified: Tue, 22 Feb 2022 22:00:00 GMT ETag: deadbeef Cache-Control: no-cache <!doctype html> …
Часто указывается, что комбинация max-age=0 и must-revalidate имеет тот же смысл, что и no-cache.
Cache-Control: max-age=0, must-revalidate
max-age=0 означает, что ответ сразу считается устаревшим, а must-revalidate означает, что он не должен использоваться повторно без перевалидации после того, как станет устаревшим — поэтому в сочетании семантика кажется такой же, как у no-cache.
Однако это использование max-age=0 — остаток от того, что многие реализации до HTTP/1.1 не могли обрабатывать директиву no-cache — и для решения этой проблемы использовалась max-age=0 в качестве обходного пути.
Но теперь, когда серверы, совместимые с HTTP/1.1, широко распространены, нет необходимости использовать эту комбинацию max-age=0 и must-revalidate — вы должны использовать просто no-cache.
Не кэшировать
Директива no-cache не препятствует хранению ответов, а вместо этого предотвращает повторное использование ответов без перевалидации.
Если вы не хотите хранить ответ ни в одном кэше, используйте no-store.
Cache-Control: no-store
Однако, в общем случае, требование «не кэшировать» фактически сводится к следующему набору обстоятельств:
- Нежелательно, чтобы ответ хранился кем-либо, кроме конкретного клиента, по соображениям конфиденциальности.
- Необходимо всегда предоставлять актуальную информацию.
- Неизвестно, что может произойти в устаревших реализациях.
В этом наборе обстоятельств no-store не всегда является наиболее подходящей директивой.
Следующие разделы рассматривают обстоятельства более подробно.
Не делиться с другими
Было бы проблематично, если бы ответ с персонализированным контентом неожиданно стал видимым другим пользователям кэша.
В таком случае использование директивы private заставит персонализированный ответ храниться только у конкретного клиента и не будет выдан никому другому пользователю кэша.
Cache-Control: private
В таком случае, даже если задан no-store, также необходимо задать private.
Обеспечение актуального контента каждый раз
Директива no-store предотвращает хранение ответа, но не удаляет уже сохранённый ответ для того же URL.
Другими словами, если уже существует устаревший ответ для определённого URL, возвращение no-store не помешает повторному использованию старого ответа.
Однако директива no-cache заставит клиента отправить запрос валидации перед повторным использованием сохранённого ответа.
Cache-Control: no-cache
Если сервер не поддерживает условные запросы, вы можете принудить клиента каждый раз обращаться к серверу и получать самый последний ответ с помощью 200 OK.
Работа с устаревшими реализациями
В качестве обходного пути для устаревших реализаций, которые игнорируют no-store, возможно использование «многофункциональных» заголовков, таких как следующие.
Cache-Control: no-store, no-cache, max-age=0, must-revalidate, proxy-revalidate
Рекомендуется использовать no-cache в качестве альтернативы для работы с такими устаревшими реализациями, и это не проблема, если no-cache задаётся изначально, так как сервер всегда получит запрос.
Если вы беспокоитесь о кэше общего пользования, вы можете гарантировать предотвращение непреднамеренного кэширования, добавив также private.
Cache-Control: no-cache, private
Что теряется при no-store
Вы можете подумать, что добавление no-store — правильный способ отказа от кэширования.
Однако не рекомендуется свободно предоставлять no-store, потому что вы теряете многие преимущества HTTP и браузеров, включая кэш браузера «Назад/Вперёд».
Поэтому, чтобы получить преимущества полного функционала веб-платформы, предпочтительнее использовать no-cache в сочетании с private.
Перезагрузка и принудительная перезагрузка
Валидация может выполняться как для запросов, так и для ответов.
Действия перезагрузки и принудительной перезагрузки являются распространёнными примерами валидации, выполняемой со стороны браузера.
Перезагрузка
Для восстановления после повреждения окна или обновления до последней версии ресурса браузеры предоставляют пользователям функцию перезагрузки.
Упрощенное представление HTTP-запроса, отправляемого во время перезагрузки браузера, выглядит следующим образом:
GET / HTTP/1.1 Host: example.com Cache-Control: max-age=0 If-None-Match: "deadbeef" If-Modified-Since: Tue, 22 Feb 2022 20:20:20 GMT
(Запросы из Chrome, Edge и Firefox очень похожи на приведенный выше; запросы из Safari будут немного отличаться.)
Директива max-age=0 в запросе указывает «использование ответов с возрастом 0 или меньше» — таким образом, промежуточные сохранённые ответы не используются.
В результате запрос валидируется с помощью If-None-Match и If-Modified-Since.
Это поведение также определено в стандарте Fetch и может быть воспроизведено в JavaScript, вызвав fetch() с режимом кэша, установленным в no-cache (обратите внимание, что reload — не правильный режим для этого случая):
// Note: "reload" is not the right mode for a normal reload; "no-cache" is fetch("/", { cache: "no-cache" });
Принудительная перезагрузка
Браузеры используют max-age=0 во время перезагрузки по соображениям обратной совместимости — потому что многие устаревшие реализации до HTTP/1.1 не понимали no-cache. Но no-cache сейчас в порядке в этом случае, и принудительная перезагрузка — это дополнительный способ обойти кэшированные ответы.
HTTP-запрос во время принудительной перезагрузки браузера выглядит следующим образом:
GET / HTTP/1.1 Host: example.com Pragma: no-cache Cache-Control: no-cache
(Запросы из Chrome, Edge и Firefox очень похожи на приведенный выше; запросы из Safari будут немного отличаться.)
Поскольку это не условный запрос с no-cache, вы можете быть уверены, что получите 200 OK от сервера источника.
Это поведение также определено в стандарте Fetch и может быть воспроизведено в JavaScript, вызвав fetch() с режимом кэша, установленным в reload (обратите внимание, что это не force-reload):
// Note: "reload" — rather than "no-cache" — is the right mode for a "force reload" fetch("/", { cache: "reload" });
Избежание перевалидации
Контент, который никогда не меняется, должен иметь длительный max-age с помощью обхода кэширования — то есть, путём включения номера версии, хэш-значения и т. п. в URL запроса.
Однако при перезагрузке пользователем отправляется запрос на перевалидацию, даже если сервер знает, что содержимое неизменно.
Для предотвращения этого можно использовать директиву immutable для явного указания, что перевалидация не требуется, потому что содержимое никогда не изменяется.
Cache-Control: max-age=31536000, immutable
Это предотвращает ненужную перевалидацию при перезагрузках.
Обратите внимание, что вместо реализации этой директивы, Chrome изменил свою реализацию, так что перевалидация не выполняется во время перезагрузок для подресурсов.
Удаление сохранённых ответов
В принципе, нет способа удалить ответы, которые уже были сохранены с длительным max-age.
Представьте, что следующий ответ от https://example.com/ был сохранён.
HTTP/1.1 200 OK Content-Type: text/html Content-Length: 1024 Cache-Control: max-age=31536000 <!doctype html> …
Вы можете захотеть перезаписать этот ответ, когда он истечёт на сервере, но сервер ничего не может сделать, когда ответ сохранён — так как больше запросов к серверу не поступает из-за кэширования.
Один из методов, упомянутых в спецификации, — отправка запроса для того же URL с небезопасным методом, таким как POST, но это обычно сложно сделать намеренно для многих клиентов.
Также есть спецификация для заголовка Clear-Site-Data: cache и значения, но не все браузеры его поддерживают — и даже когда он используется, это влияет только на кэши браузеров и не влияет на промежуточные кэши.
Следовательно, следует полагать, что любой сохранённый ответ останется на срок max-age до тех пор, пока пользователь вручную не выполнит перезагрузку, принудительную перезагрузку или очистку истории.
Кэширование уменьшает доступ к серверу, что означает, что сервер теряет контроль над этим URL. Если сервер не хочет терять контроль над URL — например, в случае, если ресурс часто обновляется — необходимо добавить no-cache, чтобы сервер всегда получал запросы и отправлял ожидаемые ответы.
Сжатие запросов
Общий кэш в основном расположен перед сервером источника и предназначен для уменьшения трафика к серверу источника.
Таким образом, если несколько идентичных запросов одновременно поступают в общий кэш, промежуточный кэш перенаправит один запрос от своего имени к источнику, который затем может использовать результат для всех клиентов. Это называется сжатием запросов.
Сжатие запросов происходит, когда запросы поступают одновременно, поэтому даже если max-age=0 или no-cache указаны в ответе, он будет повторно использован.
Если ответ персонализирован для конкретного пользователя, и вы не хотите, чтобы он был общим при сжатии, необходимо добавить директиву private:
Общие шаблоны кэширования
В спецификации Cache-Control много директив, и понять их все может быть сложно. Но большинство веб-сайтов можно покрыть комбинацией нескольких шаблонов.
В этом разделе описываются общие шаблоны проектирования кэшей.
Настройки по умолчанию
Как упоминалось выше, поведение по умолчанию для кэширования (то есть для ответа без Cache-Control) — не просто «не кэшировать», а неявное кэширование в соответствии с так называемым «эвристическим кэшированием».
Для предотвращения этого эвристического кэширования предпочтительнее явно задавать всем ответам заголовок по умолчанию Cache-Control.
Для обеспечения того, что по умолчанию будут передаваться самые последние версии ресурсов, принято устанавливать значение по умолчанию Cache-Control в виде no-cache:
Cache-Control: no-cache
Кроме того, если служба реализует куки или другие методы входа в систему, и содержимое персонализировано для каждого пользователя, необходимо также указать private, чтобы предотвратить совместное использование с другими пользователями:
Cache-Control: no-cache, private
Кэширование ресурсов
Ресурсы, которые лучше всего работают с кэшированием, — это статические неизменяемые файлы, содержимое которых никогда не меняется. А для ресурсов, которые изменяются, общепринятой лучшей практикой является изменение URL-адреса каждый раз при изменении содержимого, чтобы единица URL могла кэшироваться в течение более длительного периода.
В качестве примера рассмотрим следующий HTML:
<script src="bundle.js"></script> <link rel="stylesheet" href="build.css" /> <body> hello </body>
В современном веб-разработке JavaScript и CSS-ресурсы часто обновляются по мере прогресса разработки. Кроме того, если версии JavaScript и CSS-ресурсов, используемые клиентом, не синхронизированы, отображение может нарушиться.
Поэтому приведенный выше HTML затрудняет кэширование bundle.js и build.css с помощью max-age.
Поэтому вы можете предоставлять JavaScript и CSS с URL-адресами, которые включают изменяемую часть, основанную на номере версии или хэш-значении. Некоторые способы сделать это показаны ниже.
# version in filename bundle.v123.js # version in query bundle.js?v=123 # hash in filename bundle.YsAIAAAA-QG4G6kCMAMBAAAAAAAoK.js # hash in query bundle.js?v=YsAIAAAA-QG4G6kCMAMBAAAAAAAoK
Поскольку кэш различает ресурсы по их URL-адресам, кэш не будет повторно использован, если URL-адрес изменится при обновлении ресурса.
<script src="bundle.v123.js"></script> <link rel="stylesheet" href="build.v123.css" /> <body> hello </body>
С таким дизайном JavaScript и CSS-ресурсы могут кэшироваться в течение длительного времени. Так как долго должен быть установлен max-age? Спецификация QPACK предоставляет ответ на этот вопрос.
QPACK — это стандарт для сжатия полей заголовков HTTP, в котором определены таблицы наиболее часто используемых значений полей.
Ниже приведены некоторые часто используемые значения заголовков кэша.
36 cache-control max-age=0 37 cache-control max-age=604800 38 cache-control max-age=2592000 39 cache-control no-cache 40 cache-control no-store 41 cache-control public, max-age=31536000
Если вы выберете один из этих пронумерованных вариантов, вы сможете сжать значения в 1 байт при передаче через HTTP3.
Числа 37, 38, и 41 соответствуют срокам в одну неделю, один месяц и один год соответственно.
Поскольку кэш удаляет старые записи при сохранении новых, вероятность того, что сохранённый ответ всё ещё существует через неделю, не так высока — даже если max-age установлен на 1 неделю. Поэтому на практике выбор того или иного значения не имеет большого значения.
Обратите внимание, что число 41 имеет самый длительный max-age (1 год), но при этом public.
Значение public имеет эффект, позволяющий хранить ответ даже при наличии заголовка Authorization.
Примечание: Директива public должна использоваться только в том случае, если необходимо хранить ответ, когда установлен заголовок Authorization. В противном случае это не требуется, поскольку ответ будет сохранен в общем кэше, если задан max-age.
Таким образом, если ответ персонализирован с помощью базовой аутентификации, наличие public может вызвать проблемы. Если вы об этом обеспокоены, вы можете выбрать второе по длительности значение, 38 (1 месяц).
# response for bundle.v123.js # If you never personalize responses via Authorization Cache-Control: public, max-age=31536000 # If you can't be certain Cache-Control: max-age=2592000
Проверка
Не забудьте установить заголовки Last-Modified и ETag, чтобы не приходилось повторно передавать ресурс при перезагрузке. Для предварительно собранных статических файлов эти заголовки легко сгенерировать.
Значение ETag здесь может быть хэшем файла.
# response for bundle.v123.js Last-Modified: Tue, 22 Feb 2022 20:20:20 GMT ETag: YsAIAAAA-QG4G6kCMAMBAAAAAAAoK
Кроме того, immutable можно добавить, чтобы предотвратить проверку при перезагрузке.
Объединённый результат показан ниже.
# bundle.v123.js HTTP/1.1 200 OK Content-Type: application/javascript Content-Length: 1024 Cache-Control: public, max-age=31536000, immutable Last-Modified: Tue, 22 Feb 2022 20:20:20 GMT ETag: YsAIAAAA-QG4G6kCMAMBAAAAAAAoK
Кэширование ресурсов — это метод, позволяющий сделать ответ кэшируемым в течение длительного периода путём изменения URL-адреса при изменении содержимого. Этот метод можно применять ко всем дочерним ресурсам, таким как изображения.
Примечание: При оценке использования immutable и QPACK: если вы обеспокоены тем, что immutable изменяет предопределённое значение, предоставленное QPACK, учтите, что в этом случае часть immutable может быть закодирована отдельно путём разделения значения Cache-Control на две строки — хотя это зависит от алгоритма кодирования, используемого конкретной реализацией QPACK.
Cache-Control: public, max-age=31536000 Cache-Control: immutable
Основные ресурсы
В отличие от дочерних ресурсов, основные ресурсы не могут быть кэшированы с помощью метода кэширования ресурсов, так как их URL-адреса нельзя модифицировать таким же образом, как URL-адреса дочерних ресурсов.
Если сам следующий HTML хранится, то даже если содержимое обновится на стороне сервера, последняя версия не будет отображаться.
<script src="bundle.v123.js"></script> <link rel="stylesheet" href="build.v123.css" /> <body> hello </body>
В этом случае, no-cache было бы более уместным — а не no-store — так как мы не хотим хранить HTML, но хотим, чтобы он всегда был актуальным.
Кроме того, добавление Last-Modified и ETag позволит клиентам отправлять условные запросы, а 304 Not Modified может быть возвращён, если обновлений HTML не было:
HTTP/1.1 200 OK Content-Type: text/html Content-Length: 1024 Cache-Control: no-cache Last-Modified: Tue, 22 Feb 2022 20:20:20 GMT ETag: AAPuIbAOdvAGEETbgAAAAAAABAAE
Это подходит для неперсонализированного HTML, но для ответа, который персонализируется с помощью файлов cookie (например, после входа), не забудьте также указать private:
HTTP/1.1 200 OK Content-Type: text/html Content-Length: 1024 Cache-Control: no-cache, private Last-Modified: Tue, 22 Feb 2022 20:20:20 GMT ETag: AAPuIbAOdvAGEETbgAAAAAAABAAE Set-Cookie: __Host-SID=AHNtAyt3fvJrUL5g5tnGwER; Secure; Path=/; HttpOnly
То же самое можно использовать для favicon.ico, manifest.json, .well-known, и API-точек, URL-адреса которых нельзя изменить с помощью кэширования ресурсов.
Большую часть веб-содержимого можно охватить комбинацией двух описанных выше шаблонов.
Подробнее о кэшировании
С помощью метода, описанного в предыдущих разделах, дочерние ресурсы могут быть кэшированы в течение длительного времени с помощью кэширования ресурсов, но основные ресурсы (которые обычно являются HTML-документами) — нет.
Кэширование основных ресурсов сложно, так как с помощью стандартных директив из спецификации HTTP-кэширования нет способа активно удалять содержимое кэша при обновлении содержимого на сервере.
Однако это возможно при развертывании управляемого кэша, такого как CDN или сервис-воркер.
Например, CDN, позволяющий очищать кэш через API или панель управления, позволит реализовать более агрессивную стратегию кэширования, храня основной ресурс и явно очищая соответствующий кэш только при обновлении на сервере.
Сервис-воркер может сделать то же самое, если он может удалить содержимое в API кэша при обновлении на сервере.
Для получения дополнительной информации ознакомьтесь с документацией вашего CDN и обратитесь к документации сервис-воркера.
См. также
© 2005–2022 MDN contributors.
Licensed under the Creative Commons Attribution-ShareAlike License v2.5 or later.
https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching