Expires, Cache-Control и сброс кэша

Кэширование играет одну из основных ролей в быстродействии сайтов и сравнительно просто настраивается на стороне сервера. Веб-разработчики часто сталкиваются с кэшированием, ибо браузеры и проксирующие серверы, пытаясь ускорить работу сайтов для пользователя, очень часто стараются сохранить у себя максимально большое количество документов в локальном кэше.

При открытии страницы сайта в браузере (если эта страница была не первой на нем или если вы уже заходили на этот сайт в недалеком прошлом) браузер возьмет все (или почти все) ресурсы, необходимые для отображения страницы из кэша, чем весьма сильно может ускорить загрузку страницы. Однако при этом может потеряться актуальность представляемых данных, поэтому политика кэширования во всех браузерах реализована немного по-разному: каждый из них по-своему добивается компромисса между целостностью и актуальностью данных.

В качестве базовой настройки обычно используется инструкция браузеру от сервера для бессрочного кэширования каждого URL. Чтобы дать серверу понять, что файл был изменен, требуется использовать другое имя файла. На больших веб-сайтах обычно устанавливается такой процесс изменения этих файлов, что номер каждой новой версии добавляется к имени файла (например, common.v1.css становится common.v2.css). Соответственно, ссылки на эти файлы тоже должны быть программно обновлены, и это не зависит от языка программирования или системы шаблонов.

Настройка заголовка HTTP Expires

Заголовок Expires является частью спецификации HTTP 1.0. Когда HTTP-сервер отправляет ресурс (например, HTML-страницу или изображение) браузеру, он может дополнительно с ответом отправить этот заголовок с меткой времени. Браузеры обычно хранят ресурс вместе с информацией об истечении его срока действия в локальном кэше. При последующих пользовательских запросах к одному и тому же ресурсу браузер может сравнить текущее время и метку времени у закэшированного ресурса. Если метка времени указывает на дату в будущем, то браузер может просто загрузить ресурс из кэша вместо того, чтобы запрашивать его с сервера.

Даже если дата истечения срока действия ресурса находится в будущем, браузеры (включая Internet Explorer 4.0) по-прежнему выполнят дополнительный GET-запрос на сервер для определения, является ли закэшированная версия ресурса такой же, как на сервере. После тщательного анализа было установлено, что издержки на дополнительный запрос не являются ни оптимальными, ни необходимыми. По этой причине поведение всех браузеров было изменено следующим образом: если окончание срока действия закэшированного ресурса позже, чем время запроса, то браузер загрузит ресурс напрямую из кэша без запросов к серверу. Сайты, которые используются заголовок Expires для часто посещаемых, но редко обновляемых страниц, получат ощутимый выигрыш в трафике, а у пользователей быстрее отобразятся страницы.

Кэширование можно оптимизировать и дальше путем настройки заголовков, которые сервер отправляет при запросе требуемых ресурсов. Вместо того чтобы отдавать его содержание, сервер может отправить заголовки, что у клиента уже имеется закэшированная версия файла, для которого установлено, что срок его хранения истечет в будущем. Картинки обычно так часто не обновляют, зачем же их запрашивать снова и снова? Подробнее этот аспект будет рассмотрен в следующем разделе.

Установка и Cache-Control, и Expires должна обеспечить поддержку протоколов HTTP 1.0 и 1.1. Типичный ответ сервера, урезанный только до заголовков с кэширующей информацией, может выглядеть следующим образом:

Date: Tue, 17 Apr 2008 18:39:57 GMT
Cache-Control: max-age=315360000
Expires: Fri, 14 Apr 2018 18:39:57 GMT
Last-Modified: Mon, 16 Apr 2008 23:39:48 GMT

Спецификация кэширования

В спецификации RFC-2616 HTTP-кэшированию посвящена целая глава. В ней подробно рассматривается, что означают отдельные заголовки. Давайте остановимся на ключевых моментах.

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

Expires: Mon, 13 Oct 2019 00:00:00 GMT

Cache-Control определяет набор директив, относящихся непосредственно ко времени и специфике кэширования документа. Для запрета кэширования можно выставить его следующим образом:

Cache-Control: no-store, no-cache, must-revalidate

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

Cache-Control: max-age=31536000

в данном случае срок кэширования примерно равен году (60 * 60 *24 * 365 секунд).

Директива Pragma: no-cache используется для протокола HTTP/1.0. На данный момент можно с полным правом считать ее устаревшей конструкцией. Однако, для корректного запрета кэширования стоит все же выставлять и этот заголовок – никогда не знаешь наверняка, какой еще пользовательский агент обратится к серверу и какой протокол он будет использовать. Например, wget просто не поддерживает HTTP/1.1 (из-за Content- Encoding: chunked).

Практическое запрещение кэширования

Запретить кэширование можно и прямо из конфигурации Apache (подробная конфигурация для оптимальной производительности приводится в восьмой главе). Для этого нам нужны следующие строки:

# Проверяем, что подключен mod_headers
# Тогда выставляем заголовок Cache-Control
<IfModule mod_headers.c> 
  Header append Cache-Control “no-store, no-cache, must-revalidate”
  Header append Pragma “no-cache”
</IfModule>

# Теперь проверяем наличие mod_expires и активируем этот модуль для заголовка
# Expires
<IfModule mod_expires.c>
  ExpiresActive On
  ExpiresDefault “now”
</IfModule>

Разрешение кэширования

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

# Разрешим кэширование на 1 год, проверив наличие mod_expires
# Apache сам позаботится о выставлении корректного max-age
<IfModule mod_expires.c>
  ExpiresActive On
  ExpiresDefault “access plus 1 year”
</IfModule>

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

Форсированный сброс кэша

Если мы устанавливаем время кэширования на несколько лет (фактически, на бесконечность), то нам нужно каким-то образом сообщить клиентскому браузеру, что исходный ресурс-то у нас поменялся: иначе браузер его никогда повторно не запросит. Что для этого нужно?

Вообще говоря, для того, чтобы сообщить об обновлении файла в таком случае, нужно изменить его адрес – т. е. заявить обновленный файл под другим URL, что и будет гарантировать его обновление в локальном кэше. Однако это можно сделать двумя способами. Во-первых, мы можем в конце файла обновить GET-строку запроса, например, используя номер версии

http://webo.in/a.css?v23

или дату последнего изменения

http://webo.in/a.css?20081010

Оба этих способа изменяют адрес ресурса (в данном случае, это файл стилей), поэтому браузер обязан его запросить.

Во-вторых, мы можем номер версии добавить в сам файл

http://webo.in/a.v23.css

чтобы исключить возможные проблемы с локальными проксирующими серверами, которые могут не кэшировать у себя файлы с GET-параметрами. В этом случае (дабы не плодить новые физические файлы) нам нужно прописать в конфигурации сервера (например, Apache), чтобы при запросах такого вида отдавался каждый раз один и тот же физический файл. Это можно сделать примерно следующим образом (справедливо для CSS- и JavaScript-файлов):

RewriteRule ^(.*)\.(v[0-9]+)?\.(css|js)$ $1.$2 [QSA,L]

Таким образом, вместо файла a.v23.css будет отдаваться a.css.

Если текущая конфигурация позволяет использовать последний вариант, то стоит остановиться на нем. Иначе сброс кэша придется осуществлять через обновления GET- параметров исходного файла.

«Пробивка» вечного кеширования с помощью подмены директории несколько лучше, чем использование GET-переменной. В качестве основного аргумента можно привести следующее рассуждение: если хоть где-то в цепочке от сервера до браузера есть кеширующий прокси, то по умолчанию он сочт.т запрос с “?” динамическим и отправит запрос на сервер, не пытаясь его искать у себя в локальном кэше. Браузер, разумеется, будет ждать в этом случае несколько больше.

В каком-то смысле это будет экономия «на спичках», так как эффект будет заметен только при посещении сайта другим пользователем той же локальной сети, к примеру. Но тем не менее, эффект есть, и процент запросов с заголовком X-Forwarded-For достаточно велик.

Материалы близкой тематики:

Posted in Разгони свой сайт.