Умное Кеширование и Версионность в Javascript/CSS

Форум

Учебник Node.JS скринкаст Стандарт языка

Справочник

Discord чат

 
Статьи Тест знаний Аналоги функций PHP  

Умное Кеширование и Версионность в Javascript/CSS

Подключая внешние CSS и Javascript, мы хотим снизить до минимума лишние HTTP-запросы.

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

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

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

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

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

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

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

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

Первый запрос к серверу (кеш чистый)
GET /misc/pack.js HTTP/1.1
Host: javascript.ru

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

Ответ сервера
Сервер посылает в ответ документ 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
Следующий запрос браузера
При следующем запросе браузер добавляет If-None-Match: (кешированный ETag):
GET /misc/pack.js HTTP/1.1
Host: javascript.ru
If-None-Match: "453700005"
Ответ сервера
Сервер смотрит - ага, документ не изменился. Значит можно выдать код 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. Во все скрипты добавляется версия (или дата модификации). Например, http://javascript.ru/my.js превратится в http://javascript.ru/my.v1.2.js
  2. Все скрипты жестко кешируются браузером
  3. При обновлении скрипта версия меняется на новую: http://javascript.ru/my.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 - ссылки можно ставить так:

<link href="{version src='/css/group.css'}" rel="stylesheet" type="text/css" />

Функция 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']);
}

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

<link href="/css/group.v1234567890.css" rel="stylesheet" type="text/css" />

Оптимизация

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

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

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

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

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

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

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


Автор: tenshi, дата: 3 мая, 2008 - 10:59

#permalink

браузеры не кэшируют вопросики только если не переданы заголовки регулирующие кэширование.

.ня


Автор: tenshi, дата: 3 мая, 2008 - 11:06

#permalink

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

.ня


Автор: tenshi, дата: 3 мая, 2008 - 11:14

#permalink

более оптимальное кэширование:
устанавливаем время кэширования в несколько секунд и прячемся за проксёй, которая, соответственно, будет стучаться к нам не чаще чем раз в несколько секунд, а клиенту быстро отдавать 304 или новый контент.
и не надо изобретать велосипед...

.ня



Автор: dark57, дата: 19 мая, 2008 - 12:35

#permalink

На медленном инете (wifi или gprs) или на просто глючном инете (например, p2p сеть задействовала тысящи запросов и плюс канал забит по максимуму) бывает так, что какой-нть форум загрузится, а css не может, хотя он 100% сидит в кеше. В результате форум отрисован в Times New Roman . Иногда css таки подгружается через 5-10 секунд, а иногда так и остается.

Способ, описанный в статье, как раз избавляет от этого недуга.


Автор: oleg12 (не зарегистрирован), дата: 3 июня, 2008 - 19:39

#permalink

У меня вопрос. я создаю страницу которая периодически обновляется, поэтому кэширование не желательно. но в сранице используется скрипт prototype.js который весит почти 100 кб. в несколько раз больше чем вся страница. можно ли как то на долго кешировать именно этот скрипт, а остальную страницу не кешировать?


Автор: tenshi, дата: 4 июня, 2008 - 21:13

#permalink

можно. копай в сторону http headers и .htaccess,

.ня


Автор: des (не зарегистрирован), дата: 9 августа, 2008 - 00:05

#permalink

Прописал этот код на сервере:
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

Сервер ругается, что у Header должно быть всего три параметра: действие, заголовок и содержание заголовка. Исполнять не хочет.
У нас получается четыре параметра.
И что делать?


Автор: des (не зарегистрирован), дата: 10 августа, 2008 - 00:04

#permalink

Да вот в том-то и хрень, наверное... У хостера облазил всё - нет намёка на версию...
Только по переданным заголовкам увидел, что Apache 1.3.37...
У него, я так понял, не работает эта штука...


Автор: TaunT (не зарегистрирован), дата: 19 января, 2009 - 22:57

#permalink

оо... сколько ништяков!
в смысле полезной информации


Автор: tty01 (не зарегистрирован), дата: 7 февраля, 2009 - 19:06

#permalink

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

Браво, я бы сказал это самое краткое и понятное описание из того, что читал в сети.


Автор: JB (не зарегистрирован), дата: 10 мая, 2009 - 08:33

#permalink

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


Автор: demimurych (не зарегистрирован), дата: 17 октября, 2009 - 15:27

#permalink

Все вроде бы так да не так.

Например нажав в firefoxe кнопку reload firefox все равно шлет НА ВСЕ ЭЛЕМЕНТЫ ЗАПРОС забивая на кеширование. В ответ конечно получает 306 но тем не менее зачем то запросы шлет.


Автор: demimurych (не зарегистрирован), дата: 21 октября, 2009 - 20:56

#permalink

но и это еще не все.

достаточно закрыть тот же фаерфокс хотя бы на 20 минут. И он опять по новой выгребает ВСЕ.


Автор: mycoding, дата: 14 мая, 2011 - 20:05

#permalink

У меня Жесткое кеширование не работает.
Каждый раз опять загружаются файлы.

Пишу в начале кода

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

Автор: neonshark (не зарегистрирован), дата: 18 августа, 2011 - 10:27

#permalink

Небольшое исправление в правило:

RewriteRule ^/(.*\.)v[0-9\.]+\.(css|js|gif|png|jpg)$ /$1$2 [L,E=VERSIONED_FILE:1]

(добавлена косая черта '\' перед '.'). Позволит правильно обработать запросы типа my-js.v1.1.js, my-js.v1.1.1.js и т.п.


Автор: neonshark (не зарегистрирован), дата: 19 августа, 2011 - 08:35

#permalink

А статья суперская, все получилось и работает (я на VDS под Linux).


Автор: Paul_onclick, дата: 30 октября, 2011 - 12:06

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

А куда такие header'ы вписывать, если нужно, предположим, js-файл закэшировать? В сам файл с JS?


Автор: Mixa (не зарегистрирован), дата: 7 декабря, 2011 - 20:21

#permalink

Доброго времени суток!
Хочу сначала выразить огромную благодарность за проделаную работу на пути к просвитлению тьмы!

Вопрос (возможно я чет не до понял, а скорее всего так и есть):
мне нужно чтобы браузер не кешировал мое изображение в анимации, как это правильно прописать?


Автор: Хыиуду, дата: 5 мая, 2012 - 12:00

#permalink

А можно еще
href=css/style.css?ver=1.0
При изменении файла css в подключающем файле просто меняется номер версии, и браузер его обновляет без вопросов


Автор: Раед, дата: 12 мая, 2012 - 22:51

#permalink

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

А нельзя поменять порядок с помощью order?
Не всегда есть возможность использовать главные конфиг-файлы


Автор: blackswanny, дата: 1 ноября, 2012 - 11:40

#permalink

У меня стоит следующая задача:
Есть статические js-файлы в хедере страницы. Развернуто веб-приложение из этой одной страницы (html), которое является лишь частью всего большого проекта на ASP.NET.
Необходимо, чтобы работало кеширование без запроса на сервер даже с проверкой модификации. Т.е. чтобы обновить приложение можно было, если почистить кеш браузера. При этом сервер IIS 7, который конфигурировать его нельзя. В ASP.NET тоже вклиниваться нежелательно.
Решение пока не нашел.


Автор: CoddX, дата: 17 декабря, 2012 - 12:17

#permalink

народ, может кто подскажет как в ие 8 с помощью javascript чистить кеш?
или может у кого завалялась ссылочка на эту тему ), буду очень признателен


Автор: Горбунов (не зарегистрирован), дата: 24 сентября, 2014 - 19:27

#permalink

Здравствуйте,
Вы не могли бы подсказать, возможно ли с помощю подобной функции
(может как либо измененной):

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']);
}

дополнить версиями картинки (jpeg, pnp), что на странице?

Или если есть какая то другая?
Буду ОЧЕНЬ благодарен!


 
Текущий раздел
Поиск по сайту
Содержание

Дерево всех статей

Последние темы на форуме
Forum