Кеширование ресурсов (картинок / js / css ...) в браузере
Зависимости
- Нет зависимостей
Базовое описание компонента
-
Компонент выполняет две основных функции:
- Осуществляет корректный вывод файла в выходной поток, вместе с соответствующим формату файла хидером.
- Через хидеры организовывает кеширование ресурсов (картинки, js, css, zip и пр.) в браузере пользователя.
Отдача файла (с одновременным кешированием в браузере пользователя), происходит следующим образом:
BrowserDataCache::OutFile(BASEPATH . 'i/image/def_logo.png');
Чтобы организовать автоматическо кеширование обычных ресурсов сайта (находящихся в папках i, tmp, upl), нужно в файле core/init.php заменить строчку GetQuery() на BrowserDataCacheFinder::TryFind(GetQuery()).
При этом нужно настроить сервер, чтобы происходило перенаправление вызова так, чтобы вместо www.example.com/i/image/sample.jpg, вызывался www.example.com/index.php?q=i/image/sample.jpg
Для веб-сервера Apache правило для перенаправления задается через модуль mod_rewrite, в файле .htaccess в корне сайта:
RewriteEngine on
RewriteBase /
RewriteCond $1 !^(index\.php|robots\.txt|favicon\.ico|sitemap\.xml)
RewriteRule ^(.*)$ /index.php?q=$1 [L,QSA]
Полное описание принципа работы
Обычно мы обращаемся к ресурсам сайта (находящимся в папках i, tmp, upl) напрямую, через url следующего вида:
www.example.com/i/image/sample.jpg
Чтобы осущестить выдачу и кеширование таких файлов через BrowserDataCache, нам нужно организовать обращение к ним через php скрипт.
Первый способ: можно поменять url на:
www.example.com/index.php?q=i/image/sample.jpg
Это будет легко сделать, если для формирования ссылок на файлы вы используете функцию Root("i/image/sample.jpg").
После соответствующей модификации она будет выглядеть следующим образом:
function Root($uri = '')
{
$dir = SITE_IN_DIR ? (SITE_IN_DIR . '/') : '';
return SITE_ROOT . "{$dir}?q={$uri}";
}
Данный способ имеет право на жизнь, но менее предпочтителен.
Если есть возможность, то следует придерживаться подходов, которые позволяют сохранить обратную совместимость с обычным обращением к файлу.
То есть в идеале нам желательно оставить простой и понятный url к файлам вида www.example.com/i/image/sample.jpg не тронутым.
Второй способ: можно настроить сервер так, чтобы происходило перенаправление запроса на другой url.
То есть при обращении к www.example.com/i/image/sample.jpg, должен на самом деле вызываться www.example.com/index.php?q=i/image/sample.jpg.
Для веб-сервера Apache правило для перенаправления задается через модуль mod_rewrite, в файле .htaccess в корне сайта.
Пример:
# Запретить получение списка файлов
Options -Indexes
# При запросе к директории, будет вызван файл index.php
DirectoryIndex index.php
# Включить механизм преобразований
RewriteEngine on
# RewriteBase отрабатывает после всех RewriteRule.
# Если после всех преобразований в RewriteRule, путь останется относительным (без адреса сайта и / в начале), то RewriteBase допишет себя слева.
RewriteBase /
# От пути "example.com/i/image/sample.jpg" в RewriteRule и RewriteCond будет передано только "i/image/sample.jpg"
# Определяет выполнять в итоге преобразование в RewriteRule или нет. То есть для указанных файлов преобразование не будет выполнено.
RewriteCond $1 !^(index\.php|robots\.txt|favicon\.ico|sitemap\.xml)
# Строка будет отдана в index.php через GET переменную q
RewriteRule ^(.*)$ /index.php?q=$1 [L,QSA]
Есть ещё третий способ: через 404 ошибку на сервере, однако это очень спецефичный метод и он не является темой данной статьи.
Использование и настройка
Теперь, обращение к файлу происходит через php скрипт и мы можем использовать BrowserDataCache.
-
Для чего может понадобиться выдача файла через php скрипт:
- Когда файл генерируется на ходу (с возможным складированием в кеш). Это может быть уменьшеное preview картинки или картинка для каптчи;
- Когда нужно выполнить определенный код перед выдачей ресурса. Допустим организовать сбор статистики или проверить права доступа;
- Если нужно выполнить кеширование файла в браузере пользователя (с помощью отправки соответствующих заголовочников);
- Нет возможности отдать ресурс другим методом.
Суть работы BrowserDataCache в том, что мы просто вызываем BrowserDataCache::OutFile() для нужного файла:
BrowserDataCache::OutFile(BASEPATH . 'i/image/file.png');
Дальше скрипт выполняться не будет, а выходной поток будет передан файл с соответствующими хидерами (которые автоматом определятся сами по типу файла).
Помните, что нам не обязательно обращаться к существующему файлу. Мы, к примеру, можем создать скрипт src/get_def_logo.php, в котором будет лишь одна строчка:
BrowserDataCache::OutFile(BASEPATH . 'i/image/def_logo.png');
То есть обращаясь по адресу www.site.com/get_def_logo мы будем видеть содержимое файла def_logo.png
Чтобы сработало обычное кеширование для существующих на сайте файлов/ресурсов, по сути нужно взять лишь текущую строку запроса (функция GetQuery()), проверить соответствует ли она существующему ресурсу и если да, то отдать этот реусурс.
Для решения этой задачи существует BrowserDataCacheFinder, который устанавливается вместе с BrowserDataCache.
Для того чтобы он заработал, нужно в файле core/init.php заменить строчку GetQuery() на
BrowserDataCacheFinder::TryFind(GetQuery()).
Если ресурс не будет найден, то BrowserDataCacheFinder не будет ничего делать, а просто передаст управление дальше.
Настройки библиотеки BrowserDataCache хранятся в файле core/config/browserdatacache.php.
Одним из важнейших параметров конфига является $g_config['browserdatacache_allow_dirs'], который указывает из каких папок
разрешено запрашивать кешируемые ресурсы. Здесь перечисляются только необходимые директории, создавая таким образом дополнительный уровень защиты.
Сроки кеширования и некоторые другие параметы настраиваются через константы в файле lib/BrowserDataCache.php
Другие способы кеширования
Важно помнить, что кеширование файла совершенно не обязательно делать через php скрипт и BrowserDataCache. В этом случае вызывается php скрипт, а это увеличивает нагрузку на сервер. Если есть возможность, то желательно делать кеширование средставами сервера. Для сервера Apache это можно сделать через файл .htaccess и существует несколько вариантов того, как это можно сделать.
Вариант через модуль mod_expires:
# Установить кеширеование на некоторые типы файлов
<IfModule mod_expires.c>
ExpiresActive On
ExpiresDefault "access 7 days"
ExpiresByType application/javascript "access plus 1 year"
ExpiresByType text/javascript "access plus 1 year"
ExpiresByType text/css "access plus 1 year"
ExpiresByType text/html "access plus 7 day"
ExpiresByType text/x-javascript "access 1 year"
ExpiresByType image/gif "access plus 1 year"
ExpiresByType image/jpeg "access plus 1 year"
ExpiresByType image/png "access plus 1 year"
ExpiresByType image/jpg "access plus 1 year"
ExpiresByType image/x-icon "access 1 year"
ExpiresByType application/x-shockwave-flash "access 1 year"
</IfModule>
Вариант через модуль mod_headers:
<IfModule mod_headers.c>
# 30 дней
<filesMatch "\.(ico|pdf|flv|jpg|jpeg|png|gif|swf|css|js|xml|txt)$">
Header set Cache-Control "max-age=2592000, public"
</filesMatch>
</IfModule>
Вариант модуль mod_expires с использованием ETags:
# Установить ETags на файлы сервера с указанным расширением
FileETag MTime Size
<IfModule mod_expires.c>
<filesmatch "\.(jpg|gif|png|css|js)$">
ExpiresActive on
# Когда проверить ETags для этого файла в следующий раз (дата доступа к файлу +1 год)
ExpiresDefault "access plus 1 year"
</filesmatch>
</IfModule>
# Запрет отдачи HTTP-заголовков некоторым браузерам
<IfModule mod_setenvif.c>
BrowserMatch "MSIE" force-no-vary
BrowserMatch "Mozilla/4.[0-9]{2}" force-no-vary
</IfModule>
Если сервер поддерживает gzip сжатие, то можно добавить следующий код:
# Включить gzip сжатие для text, html, javascript, css, xml
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE application/xml
AddOutputFilterByType DEFLATE application/xhtml+xml
AddOutputFilterByType DEFLATE application/rss+xml
AddOutputFilterByType DEFLATE application/javascript
AddOutputFilterByType DEFLATE application/x-javascript
AddOutputFilterByType DEFLATE text/plain
AddOutputFilterByType DEFLATE text/html
AddOutputFilterByType DEFLATE text/xml
AddOutputFilterByType DEFLATE text/css
SetOutputFilter DEFLATE
</IfModule>