Как бороться с кешированием браузеров

В web-разработке более логичным и правильным считается вынесение javascript-логики и CSS-стилей в отдельные файлы. Кроме более логичной структуры такая организация файлов снижает нагрузку на сервер и уменьшает траффик. То-есть мы снижаем количество http-запросов и .js и .css файлы отдаются с заголовками, обеспечивающими надежное кеширование этих файлов браузерами. Но когда в процессе развития сайта приходится менять эти файлы, у пользователей в кеше остается старый вариант и пока кеш не устарел, будут поступать жалобы на сломанную интеграцию серверной и клиентской частей.

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

Простое кеширование ETag

Самый простой способ кеширования статических ресурсов - использование ETag.

Достаточно включить соответствующую настройку сервера (для Apache включена по умолчанию) - и к каждому файлу в заголовках будет даваться ETag - хеш, который зависит от времени обновления, размера файла и (на inode-based файловых системах) inode.

Браузер кеширует такой файл и при последующих запросах указывет заголовок If-None-Match с ETag кешированного документа. Получив такой заголовок, сервер может ответить кодом 304 - и тогда документ будет взят из кеша.

Выглядит это так:

1) Первый запрос к серверу (кеш чистый)

GET /misc/pack.js HTTP/1.1
Host: lifeinweb.biz

Вообще, браузер обычно добавляет еще пачку заголовоков типа User-Agent, Accept и т.п. Для краткости они порезаны.

2) Ответ сервера (Сервер посылает в ответ документ c кодом 200 и ETag):

HTTP/1.x 200 OK
Content-Encoding: gzip
Content-Type: text/javascript; charset=utf-8
Etag: "3272221997"
Accept-Ranges: bytes
Content-Length: 23321
Date: Fri, 02 May 2008 17:22:46 GMT
Server: lighttpd

3) Следующий запрос браузера (При следующем запросе браузер добавляет If-None-Match: (кешированный ETag)):

GET /misc/pack.js HTTP/1.1
Host: umi.lifeinweb.biz
If-None-Match: "453700005"

4) Ответ сервера (Сервер смотрит - ага, документ не изменился. Значит можно выдать код 304 и не посылать документ заново):

HTTP/1.x 304 Not Modified
Content-Encoding: gzip
Etag: "453700005"
Content-Type: text/javascript; charset=utf-8
Accept-Ranges: bytes
Date: Tue, 15 Apr 2008 10:17:11 GMT

Альтернативный вариант - если документ изменился, тогда сервер просто посылает 200 с новым ETag.

Аналогичным образом работает связка Last-Modified + If-Modified-Since:

1) сервер посылает дату последней модификации в заголовке Last-Modified (вместо ETag)

2) браузер кеширует документ, и при следующем запросе того же документа посылает дату закешированной версии в заголовке If-Modified-Since(вместо If-None-Match)

3) сервер сверяет даты, и если документ не изменился - высылает только код 304, без содержимого.

Эти способы работают стабильно и хорошо, но браузеру в любом случае приходится делать по запросу для каждого скрипта или стиля.

Умное кеширование - Версионность

Общий подход для версионности:

1) К каждому файлу скрипта или стиля добавляется версия (или дата модификации). Например, //www.webmancer.org/bizmy.js превратится в //www.webmancer.org/bizmy.v1.2.js

2) Все скрипты жестко кешируются браузером

3) При обновлении скрипта версия меняется на новую: //www.webmancer.org/bizmy.v2.0.js

4) Адрес изменился, поэтому браузер запросит и закеширует файл заново

5) Старая версия 1.2 постепенно выпадет из кеша

Дальше мы разберем, как сделать этот процесс автоматическим и прозрачным.

Жесткое кеширование

Жесткое кеширование - своего рода кувалда которая полностью прибивает запросы к серверу для кешированных документов.

Для этого достаточно добавить заголовки Expires и Cache-Control: max-age.

Например, чтобы закешировать на 365 дней в PHP:

header("Expires: ".gmdate("D, d M Y H:i:s", time()+86400*365)." GMT");
header("Cache-Control: max-age="+86400*365);

Или можно закешировать контент надолго, используя mod_header в Apache:

Header add "Expires" "Mon, 28 Jul 2014 23:30:00 GMT"
Header add "Cache-Control" "max-age=315360000"

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

Большинство браузеров (Opera, Internet Explorer 6+, Safari) НЕ кешируют документы, если в адресе есть вопросительный знак, т.к считают их динамическими.Именно поэтому мы добавляем версию в имя файла. Конечно, с такими адресами приходится использовать решение типа mod_rewrite, мы это рассмотрим дальше в статье.

P.S: А вот Firefox кеширует адреса с вопросительными знаками.

Автоматическое преобразование имен

Разберем, как автоматически и прозрачно менять версии, не переименовывая при этом сами файлы.

Имя с версией -> Файл

Самое простое - это превратить имя с версией в оригинальное имя файла.

На уровне Apache это можно сделать mod_rewrite:

RewriteEngine on
RewriteRule ^/(.*\.)v[0-9.]+\.(css|js|gif|png|jpg)$ /$1$2   [L]

Такое правило обрабатывает все css/js/gif/png/jpg-файлы, вырезая из имени версию.

Например:

/images/logo.v2.gif -> /images/logo.gif
/css/style.v1.27.css -> /css/style.css
/javascript/script.v6.js -> /javascript/script.js

Но кроме вырезания версии - надо еще добавлять заголовки жесткого кеширования к файлам. Для этого используются директивы mod_header:

Header add "Expires" "Mon, 28 Jul 2014 23:30:00 GMT"
Header add "Cache-Control" "max-age=315360000"

А все вместе реализует вот такой апачевый конфиг:

RewriteEngine on
# убирает версию, и заодно ставит переменную что файл версионный
RewriteRule ^/(.*\.)v[0-9.]+\.(css|js|gif|png|jpg)$ /$1$2 [L,E=VERSIONED_FILE:1]# жестко кешируем версионные файлы
Header add "Expires" "Mon, 28 Jul 2014 23:30:00 GMT" env=VERSIONED_FILE
Header add "Cache-Control" "max-age=315360000" env=VERSIONED_FILE

Из-за порядка работы модуля mod_rewrite, RewriteRule нужно поставить в основной конфигурационный файл httpd.conf или в подключаемые к нему(include) файлы, но ни в коем случае не в .htaccess, иначе команды Header будут запущены первыми, до того, как установлена переменная VERSIONED_FILE. Директивы Header могут быть где угодно, даже в .htaccess - без разницы.

Автоматическое добавление версии в имя файла на HTML-странице

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

Например, при использовании даты модификации в качестве версии и шаблонизатора Smarty - ссылки можно ставить так:

Функция version добавляет версию:

function smarty_version($args){$stat = stat($GLOBALS['config']['site_root'].$args['src']);
 $version = $stat['mtime'];echo preg_replace('!\.([a-z]+?)$!', ".v$version.\$1", $args['src']);
}

Результат на странице:

Оптимизация

Чтобы избежать лишних вызовов stat, можно хранить массив со списком текущих версий в отдельной переменной

$versions['css'] = array(
 'group.css' => '1.1',
 'other.css' => '3.0',
}

В этом случае в HTML просто подставляется текущая версия из массива.

Можно скрестить оба подхода, и выдавать во время разработки версию по дате модификации - для актуальности, а в продакшн - версию из массива, для производительности.

Применимость

Такой способ кеширования работает везде, включая javascript, CSS, изображения, flash-ролики и т.п.

Он полезен всегда, когда документ изменяется, но в браузере всегда должна быть текущая актуальная версия.

Крайний случай

Если хотите, чтобы картинки и файлы не кешировались совсем, то можете добавить к ним параметр GET, например:

echo '';
Информация
Автор webmancer Нравится 0
Рейтинг 1 Не нравится 0
Голосов 1 Прочитали 1
Дата 2012-02-20 09:00:00 В избранном 0
Ваша реакция

Только авторизованные пользователи могут участвовать в рейтингах, делать заметки и добавлять в избранное.

Зарегистрироваться

Авторизоваться

На рекомендательном сервисе WEBmancer.Org только зарегистрированные пользователи могут комментировать и оставлять рецензии. Авторизованный пользователь так же может ставить отметки книгам, фильмам и другим постам. Вести учет прочитанных книг и просмотренных фильмов. Добавлять посты в избранное и иметь к ним быстрый доступ.