Журнал «Компьютерра» № 20 от 30 мая 2006 года
Шрифт:
header(«Cache-Control: private»);
Слишком просто, чтобы быть правдой? Ну, в общем-то, да – некоторые агенты порой игнорируют этот заголовок. Чтобы по-настоящему запретить браузеру кэшировать документ, следует быть немного более убедительным:
# ‘Expires’ in the past
header(«Expires: Mon, 26 Jul 1997 05:00:00 GMT»);
# Always modified
header(«Last-Modified: „.gmdate(„D, d M Y H:i:s“).“ GMT»);
# HTTP/1.1
header(«Cache-Control: no-store, no-cache, must-revalidate»);
header(«Cache-Control: post-check=0, pre-check=0», false);
# HTTP/1.0
header(«Pragma: no-cache»);
Это
Использование entity tags похоже на работу с конструкцией if-modified-since. Apache на запрос к статическому ресурсу может отдавать заголовок Etag, содержащий контрольную сумму, сгенерированную из размера файла, времени последнего изменения и номера индексного дескриптора. Браузер может запросить заголовок файла, чтобы проверить e-tag документа перед загрузкой. Очевидно, что использование e-tag сопряжено с теми же накладными расходами, что и механизм if-modified-since, – клиент все еще вынужден делать лишний HTTP-запрос, чтобы определить валидность локальной копии.
Кроме того, нужно соблюдать осторожность с if-modified-since и e-tags, если выдача контента идет с нескольких серверов. В системе из двух хорошо сбалансированных серверов любой документ может быть запрошен одним и тем же агентом с любого из двух серверов – или с каждого (не одновременно). Это нормально. Для этого мы и выравнивали нагрузку. Однако если серверы генерируют разные e-tags или разные даты изменения документов, браузер не сможет нормально поддерживать актуальный кэш. По умолчанию e-tag генерируются с использованием индексных дескрипторов, которые на разных серверах разные. Это можно запретить с помощью следующей опции в настройках Apache:
FileETag MTime Size
Теперь Apache для генерации e-tag будет использовать только время последнего изменения и размер файла. Это, к сожалению, приводит нас к другой проблеме использования e-tag, которая тоже актуальна для if-modified-since (хоть и в меньшей степени). Поскольку e-tag зависит от времени последнего изменения, нам необходимо следить за синхронизацией. Если мы распределяем файлы по разным веб-серверам, всегда остается шанс, что на один из серверов файл попадет на секунду или две позже, чем на другой. В этом случае e-tag, сгенерированные серверами, будут отличаться. Мы можем изменить настройки так, чтобы генерировать e-tag только на основании размера файла, но это означает, что файл не обновится в браузере, если мы изменим его содержимое, а размер останется неизменным. Тоже не идеально.
Дело в том, что мы подходим к проблеме не с той стороны. Все возможные стратегии кэширования отталкиваются от того, что клиент спрашивает сервер, насколько актуальна копия, хранимая в кэше. Если бы сервер сам, без запроса, сообщал клиенту об изменениях файлов, то клиент в любой момент времени знал бы, что кэшированная копия валидна. Но веб устроен иначе – клиент запрашивает сервер, и никак иначе.
Или все же слегка иначе? Ведь перед отправкой любых JavaScript– или CSS-файлов клиент запрашивает страницу, которая на них ссылается с помощью тегов <script> и <link>. И мы можем использовать реакцию сервера для информирования клиентов о любых изменениях, произошедших с этими ресурсами. Звучит немного загадочно, поэтому скажу прямо: если мы будем изменять названия JavaScript– и CSS-файлов при каждом изменении их содержания, то сможем разрешить клиенту хранить их в кэше вечно (ведь содержимое файла по отдельно взятому адресу не меняется).
Если мы уверены в том, что конкретный ресурс никогда не изменится, то можем отправить несколько по-настоящему агрессивных заголовков. В PHP нам потребуется всего пара строк:
header(«Expires: „.gmdate(„D, d M Y H:i:s“, time+315360000).“ GMT»);
header(«Cache-Control: max-age=315360000»);
Так мы говорим браузеру, что содержимое файла останется неизменным в течение десяти лет (т.е. плюс-минус 315 360 000 секунд), поэтому, единожды загрузив файл, браузер может следующие десять лет использовать локальную копию. Конечно, необязательно использовать для отправки JavaScript и CSS именно PHP. Мы перейдем к этому через несколько минут.
Ручное изменение названий файлов при изменении их содержимого – опасное занятие. Что произойдет, если вы переименуете файл, но забудете переименовать шаблоны, указывающие на него? Что произойдет, если вы поменяете не все шаблоны, а только часть? Что случится, если вы измените шаблоны, но сам файл не переименуете? И наиболее вероятный вариант: что произойдет, если вы измените содержимое, но забудете переименовать файл или изменить ссылки на него? В лучшем случае, пользователи будут работать на старой версии сайта. В худшем – сайт перестанет работать совсем. В общем, ручное изменение названий – дурацкая идея.
На наше счастье, компьютеры с такими задачами – тупым повторением одних и тех же операций при совпадении определенных условий – справляются отменно.
Чтобы сделать этот процесс максимально безболезненным, следует для начала уяснить, что нам вообще не нужно переименовывать файлы. Между реальным расположением файла на диске и URL, с которого он доставляется пользователю, нет ничего общего. Так что в случае Apache мы можем использовать mod_rewrite, создав простое правило для редиректа определенных URL к нужным файлам:
RewriteEngine on
RewriteRule ^/(.*.)v[0-9.]+.(css|js|gif|png|jpg)$ /$1$2 [L]
Это правило обрабатывает все URL с указанными расширениями, которые также содержат суффикс версии. В процессе обработки правило переписывает URL так, чтобы он указывал на путь к нужному файлу (исключая при этом суффикс). Например:
foo.v2.gif foo.gif
/css/main.v1.27.css css/main.css
/javascript/md5.v6.js /javascript/md5.js
Когда это правило работает, мы можем менять URL (добавляя к нему суффикс версии), не меняя расположения файла на диске. Обнаружив, что URL изменился, браузер считает, что ему нужно обратиться к новому ресурсу.