Javascript.RU

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

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

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

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

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

Самый простой способ кеширования статических ресурсов - использование 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 - без разницы.

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

Например, при использовании даты модификации в качестве версии и шаблонизатора 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

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

.ня


Автор: Илья Кантор, дата: 16 января, 2011 - 18:36
#permalink

Современная ситуация такова что прокси хуже кешируют вопросики. С браузерами сейчас все в порядке.


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

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

.ня


Автор: Илья Кантор, дата: 3 мая, 2008 - 15:00
#permalink

Сейчас очень мало ресурсов, где жестко кешируется все HTML-страница.

Но даже если она кешируется по ETag/Last-Modified - то метод замечательно работает.
Конечно, эти ETag/Last-Modified должны обновляться при изменении версии ресурса.


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

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

.ня


Автор: Илья Кантор, дата: 3 мая, 2008 - 15:08
#permalink

Метод, описанный в этой статье, направлен на то, чтобы полностью избежать лишних запросов с клиента.

В результате страницы грузятся быстрее, т.к меньше запросов.

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


Автор: tenshi, дата: 3 мая, 2008 - 16:25
#permalink

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

так вот, что быстрее?
1) ревалидировать и загрузить страницу, загрузить ресурс. на каждую загрузку уходит куча хттп-пакетов
2) ревалидировать страницу, ревалидировать все ресурсы, загрузить ресурс. на каждую ревалидацию уходит всего один хттп-пакет

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

ps: ситуацию с сотнями ресурсов я естественно не рассматриваю...
pps: напомню, что все браузеры поддерживают постоянные хттп-соединения...

.ня


Автор: Илья Кантор, дата: 10 мая, 2008 - 08:11
#permalink

Совершенно с тобой согласен. Это разные методы и у них разные характеристики.

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

Спасибо за комментарий.


Автор: 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 кб. в несколько раз больше чем вся страница. можно ли как то на долго кешировать именно этот скрипт, а остальную страницу не кешировать?


Автор: atxquadro, дата: 2 марта, 2010 - 00:50
#permalink

Можно пропиши в .htaccess в хэдерах для этого prototype.js Expired большой.
Читай статью.


Автор: 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 должно быть всего три параметра: действие, заголовок и содержание заголовка. Исполнять не хочет.
У нас получается четыре параметра.
И что делать?


Автор: Илья Кантор, дата: 9 августа, 2008 - 00:48
#permalink

Проверьте версию apache..

Выдержка из http://httpd.apache.org/docs/2.2/mod/mod_headers.html

Syntax: Header [condition] set|append|merge|add|unset|echo|edit header [value] [early|env=[!]variable]

P.S В 2.0 такая запись тоже вроде пашет.


Автор: 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 но тем не менее зачем то запросы шлет.


Автор: Илья Кантор, дата: 18 октября, 2009 - 09:12
#permalink

Да, так работает релоад в Firefox. А еще можно нажать Ctrl-F5, и он вообще все запросит на 200. Только это ведь релоады. А при хождении по страницам все работает ок.


Автор: 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?


Автор: kf1day (не зарегистрирован), дата: 30 октября, 2012 - 10:50
#permalink

Можно переименовать .js в .php и добавить в начало скрипта php-код с соответствующими заголовками:

<?php

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

?>

В примере ошибка, нужно использовать именно

max-age=".(86400*365)

вместо

max-age="+86400*365


Автор: 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), что на странице?

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


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

Учебник javascript

Основные элементы языка

Сундучок с инструментами

Интерфейсы

Все об AJAX

Оптимизация

Разное

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

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