Кеширование ресурсов (картинок / js / css ...) в браузере

Зависимости

  • Нет зависимостей

Базовое описание компонента

Отдача файла (с одновременным кешированием в браузере пользователя), происходит следующим образом:

        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.

Суть работы 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>