В блоге Web Optimizer (продукта для автоматизации клиентской оптимизации) Николай Мациевский регулярно публикует интересные посты о клиентской оптимизации, раскрывая всё новые подробности этого перспективного направления в веб-разработке. По ряду причин Николай пишет статьи на английском языке. Чтобы все статьи были доступны и на русском языке, я предложил ему свою помощь с переводом. Далее перевод первой статьи из серии, посвященной тонкостям кэширования в клиентской оптимизации.
Определение правильного подхода для организации клиентского кэширования с учетом всех возможных нюансов отняло у меня достаточно много времени. Давайте рассмотрим весь путь по шагам.
Основы кэширования
Очень просто кэшировать один произвольный файл. Достаточно лишь добавить к запросу заголовок Cache-Control (для HTTP/1.1) и заголовок Expires (для HTTP/1.0). Заголовки будут выглядеть следующим образом:
Expires: Thu, 31 Dec 2019 23:55:55 GMT
Cache-Control: max-age=315360000
Все действительно настолько просто? Ну, практически.
Кэширование на прокси-серверах
Интернет состоит из большого количества серверов. Некоторые из них содержат веб-сайты, а некоторые являются лишь транспортными узлами, перенаправляющими трафик от пользователя к веб-сайту и обратно. Провайдеры заинтересованы в уменьшении объема трафика. Для этого они применяют технологию кэширования на своих серверах, чтобы отвечать на часть пользовательских запросов, не пересылая их дальше внутренней сети.
Можно ли влиять на этот процесс? В RFC 2616 описан способ, позволяющий сообщить прокси-серверам, что содержимое желательно кэшировать: Cache-Control: public. Приведенный выше пример примет следующий вид:
Expires: Thu, 31 Dec 2037 23:55:55 GMT
Cache-Control: max-age=315360000, public
Автоматизация кэширования
Но как применить этот подход ко всем файлам на сервере? В Apache есть специальный модуль, предназначенный для подобных ситуаций: mod_expires. Включить его можно при помощи следующих директив в конфигурационном файле:
ExpiresActive On
ExpiresDefault "access plus 10 years"
Выглядит достаточно просто, какие могут быть проблемы?
Что именно следует кэшировать?
Практически весь контент веб-сайтов является статическим (кроме, разве что, HTML-документов) и весь этот контент можно кэшировать. Один из подходов состоит в исключении из кэша всех HTML-документов. Это может быть сделано при помощи следующих строк в конфигурации Apache:
ExpiresActive On
ExpiresDefault "access plus 10 years"
<FilesMatch \.(html|xhtml|xml|shtml|phtml|php)$>
ExpiresActive Off
Но что если часть CSS- и JS-файлов, а может быть и часть изображений на сайте создаются динамически? Например, если картинки для предпросмотра и ряд стилей создаются при помощи PHP на лету?
ExpiresByType – наша волшебная палочка!
Мы можем использовать директиву ExpiresByType, чтобы кэшировать необходимые файлы исходя из их MIME-типа (который обычно корректно задается сервером). В этом случае директивы могут быть такими:
ExpiresActive On
ExpiresByType text/css A315360000
Этот способ в большинстве случаев лучше, так как кэширующие заголовки будут установлены в зависимости от типа содержимого, а не от расширения. На этом всё?
Возможные препятствия
При использовании описанных подходов может возникнуть множество менее распространенных проблем:
- У ряда ресурсов (например, у файлов шрифтов) могут быть нестандартные MIME-типы. В подобных ситуациях заголовки кэширования могут быть применены только на основания расширения файлов с использованием FilesMatch. Но поскольку сложно гарантировать одинаковое поведение произвольного серверного окружения, лучше сочетать оба подхода: устанавливать заголовки с помощью FilesMatch и ExpiresByType.
- Как определять какие файлы кэшировать? Web Optimizer имеет огромное количество предопределенных MIME-типов и расширений, позволяющих обработать практически любые статические ресурсы на сервере. В нем также есть несколько вариантов управления режимами кэширования.
- Если на сервере отсутствует поддержка mod_expires, ее можно эмулировать при помощи light PHP proxy. В случае, если отсутствует mod_rewrite, можно организовать внутренние редиректы для всех ресурсов на этот прокси и все файлы будут закэшированы.
- Если же на сервере нет поддержки ни mod_expires ни mod_rewrite, необходимо разбирать HTML-код и заменять все вызовы статических ресурсов их эквивалентами используя PHP proxy. Только и всего.
Web Optimizer не только имеет великолепную поддержку различных техник кэширования для различных окружений, но и предоставляет несколько путей для форсированного обновления кэша (если контент изменился на сервере). Эту тему мы поднимем в одном из следующих постов.
комментарии (14)
Цитаты из HTTP/1.1 Header Field Definitions:
Там же написано, что
#sec14.21
И главное:
Так что почитайте спеки, gkond. ^_~
www.ietf.org/rfc/rfc1945.txt
Раздел 10.7
2. should not ни к чему не обязывает. Это просто рекомендация. Если бы было must not, тогда совсем другое дело
3. Если Вас смущает дублирование Expires / Cache-Control, то Expires нужен для клиентов, поддерживающих только HTTP/1.0, а Cache-Control в данном случае нужен для public / private
В таком случае зачем там max-age?
А как же HTTP/1.1 секция 13.4:
RFC 2119:
Перевожу на русский язык: не знаешь – не лезь. Зачем вам ставить expires >+1y? Все ли опасности знаете? Весь ли смысл этого действия осознаёте?
Вы еще приведите оттуда раздел про ограничение в 2 соединения на хост. Много чего за 10 лет изменилось.
Объясните смысл установки max-age/expires в 28 лет.
Какая от этого польза? Вдруг кому-то не придётся обновлять кеш, если год назад он что-то сохранил? Т.е. вероятность мизерна. Польза — экономия одного запроса в год.
Опасность? Некоторые клиенты и прокси могут проигнорировать такую установку, решив, что 28 лет — абсурд. В результате будут бить динамически с каждым запросом.
Вероятность — похожая. Ущерб — сотни запросов в год.
Expected return — ущерб 0,01 запроса в год. Зачем?
Аналогия не корректна. Приведу цитату полностью:
Сейчас можно рассчитывать, что и с 6 соединениями не будет больших проблем с response times и congestion.
Выгода очевидна: скорость для пользователя.
Вероятность выгоды: 100%
Опастность: затормозим некий сервер.
Вероятность: 5% (из задницы)
Expected return: положительна.
Нет?
Много букв. Не хочу кормить тролля. Успокойтесь уже наконец.