Джино: хостинг и веб-сервисы

Система Orphus
Russian version
Добавить на Del.icio.us
English version
Добавить на Digg.com

 dkLab | Конструктор | HTTP_StaticMerger: автоматическая "склейка" CSS- и JS-файлов для быстрой загрузки 

Карта сайта :: Форум «Лаборатории» :: Проект «Денвер»
Проект «Orphus» :: Куроводство: наблы :: Конструктор


2008-11-23
Обсудить на форуме

Принять участие в разработке библиотеки/утилиты можно на GitHub.

Библиотека HTTP_StaticMerger 2.x позволяет "на лету" сливать набор статических файлов (CSS и JS) и, таким образом, ускорять загрузку страницы, экономя HTTP-запросы. Ближайший аналог — minify, имеющий более расширенный функционал, но зато большую сложность и размер.

Естественно, библиотеку рекомендуется применять совместно с кэширующим reverse proxy (например, nginx) для минимизации времени ответа. Возможности:

  • Обработка директив @import и удаление комментариев из кода, что экономит трафик.
  • "Слитый" URL зависит от времени модификации входящих в него файлов.
  • Поддержка заголовков HTTP If-Modified-Since, If-None-Match, Etag.
  • Работа под очень высокой нагрузкой (при использовании reverse-proxy сервера с модулем кэширования, например, nginx).
  • В версии 2.x поддерживается цифровое подписывание URL, защищающее от "зафлуживания" файловой системы злоумышленником в случае использования nginx.
  • Компрессия слитого URL для сокращения его длины.

Вы можете сразу посмотреть примеры использования.

Чайник 

Владельцы нагруженных проектов! Прочтите, пожалуйста, раздел Nginx: gzip-сжатие и кэширование в нижней части страницы, чтобы узнать, как правильно использовать HTTP_StaticMerger под нагрузкой.

Суть проблемы

В сложных сайтах количество подключаемых CSS- и JS-файлов может быть достаточно большим, из-за чего скорость загрузки страницы сильно страдает:

Листинг 1: подгрузка нескольких статических файлов
<head>
    <script type="text/javascript" src="/js/jquery.js"></script>
    <script type="text/javascript" src="/js/jquery-dimensions.js"></script>
    <script type="text/javascript" src="/js/left-menu.js"></script>
    ...
</head>

Загрузка каждого файла порождает отдельный HTTP-запрос, и браузер не может показать содержимое страницы до тех пор, пока не загрузятся все до единого CSS- и JS-файлы. Естественно, это замедляет отображение.

Библиотека HTTP_StaticMerger позволяет использовать единый URL для загрузки сразу нескольких CSS- или JS-файлов:

Листинг 2: примеры запросов на слияние
<head>
    <script src="/merge.php/!!a2de6253!!12345678!!js/jquery.js!!js/jquery-dimensions.js!!..."></script>
    <link rel="stylesheet" type="text/css" href="/merge.php/!!3ad3b1f8!!12345678!!css/common.css!!css/menu.css!!..."></script>
</head>

Как пользоваться: простейший вариант

Для начала положите в DOCUMENT_ROOT файл merge.php примерно следующего содержания:

Листинг 3: /merge.php — обработчик для слияния
<?php
require_once "../lib/HTTP/StaticMerger.php";
$merger = new HTTP_StaticMerger('a-secret-and-constant-string');
$merger->main('windows-1251'); // Желательно указать кодировку файлов.

Затем в коде, формирующем HTML-страницу, перечислите CSS- или JS-файлы, которые вы планируете "слить" вместе:

Листинг 4: вставка URL для слияния файлов
<head>
  <?
  $merger = new HTTP_StaticMerger('a-secret-and-constant-string');
  echo $merger->getHtml("/merge.php/", array("js/jquery.js", "js/jquery-dimensions.js", ...));
  ?>
</head>

В целях отладки иногда бывает удобно временно отключить слияние файлов. Для этого передайте третий параметр в метод getHtml(), равный true. Тогда каждый файл будет представлен отдельным тэгом <script> или <link>:

Листинг 5: третий параметр true — слияния не происходит
echo $merger->getHtml("/merge.php/", array("js/jquery.js", ...), !empty($_GET['no_merge']));

В общем-то, это и есть необходимый минимум.

Nginx: gzip-сжатие и кэширование

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

Чтобы разгрузить сервер, используйте кэширующий reverse proxy перед веб-сервером (например, nginx). Пусть он кэширует "навечно" все запросы к merge-скрипту:

Модификация обработчика слияний

Листинг 6: /merge.php — обработчик для слияния с gzip-сжатием
<?php
require_once "../lib/HTTP/StaticMerger.php";
ob_start();
$merger = new HTTP_StaticMerger('a-secret-and-constant-string');
$merger->main('windows-1251');
$c = ob_get_clean();
if ($c && empty($_SERVER['HTTP_NOGZIP'])) ob_start(array('ob_gzhandler', 9));
echo $c;

Чайник 

Обратите внимание: мы включаем gzip-сжатие только в случае, если nginx это не запретил (переменная HTTP_NOGZIP устанавливается в конфигурации nginx в зависимости от браузера, см. ниже). Также запрещаем gzip-сжатие для ответов с "пустым" телом (например, "304 Not Modified"), иначе возникают проблемы как минимум в Google Chrome.

Создание кэш-директории

Листинг 7: Для начала создайте директорию кэша.
mkdir -p /var/cache/nginx
chown nginx:nginx /var/cache/nginx

Конфигурация для nginx+apache+mod_php

В случае, если вы работаете с apache+mod_php, а nginx применяется в качестве фронтенд-сервера, используйте следующие настройки:

Листинг 8: Конфигурация для nginx+apache+mod_php
# Включаем кэширование для сжатых файлов.
proxy_cache_path /var/cache/nginx levels= keys_zone=merge:10m;
location /merge.php {
    set $no_gzip 0;
    if ($http_user_agent ~ "MSIE [4-6]\.|Safari|Konqueror") {
        # В этих браузерах имеются баги при обработке gzip-сжатых файлов с ДЛИННЫМИ URL-ами.
        set $no_gzip 1;
    }
    if ($http_accept_encoding !~ "gzip") {
        set $no_gzip 1;
    }
    proxy_pass   http://127.0.0.1:80;
    proxy_set_header Host $host;
    
    # Nginx caching.
    proxy_cache merge;
    proxy_cache_valid 200 304 404 240h;
    proxy_cache_key "$no_gzip|$request_method|$http_if_modified_since|$http_if_none_match|$host|$request_uri";
    proxy_set_header NOGZIP $no_gzip;
}

Чайник 

Внимание! В MS Internet Explorer 6.0 и младше отмечаются проблемы с кэшированием gzip-сжатых данных, если длина URI CSS- или JS-файла превышает 215 символов. Соответственно, мы выключаем в этих браузерах сжатие.

Или - конфигурация для FastCGI PHP

А эти настройки — для поклонников FastCGI-версии PHP:

Листинг 9: Конфигурация для FastCGI-версии PHP
# Включаем кэширование для сжатых файлов.
fastcgi_cache_path /var/cache/nginx levels= keys_zone=merge:10m;
location /merge.php {
    set $no_gzip 0;
    if ($http_user_agent ~ "MSIE [4-6]\.|Safari|Konqueror") {
        # В этих браузерах имеются баги при обработке gzip-сжатых файлов с длинными URL-ами.
        set $no_gzip 1;
    }
    if ($http_accept_encoding !~ "gzip") {
        set $no_gzip 1;
    }
    fastcgi_pass   127.0.0.1:9000;
    fastcgi_param  SCRIPT_FILENAME $document_root$fastcgi_script_name;
    include fastcgi_params;
    
    # Nginx caching.
    fastcgi_cache merge;
    fastcgi_cache_valid 200 304 404 240h;
    fastcgi_cache_key "$no_gzip|$request_method|$http_if_modified_since|$http_if_none_match|$host|$request_uri";
    fastcgi_param HTTP_NOGZIP $no_gzip;
}

Чайник 

Подробности можно прочитать в
документации nginx.

Использование совместно с mod_rewrite

Конечно, можно при помощи Apache mod_rewrite или nginx rewrite делать URL сливаемых файлов более аккуратными, вот такими:

/merge.php?some=param&other=!!a2de6253!!12345678!!js/jquery.js!!...
/merge/!!a2de6253!!12345678!!js/jquery.js!!...
/m/!!a2de6253!!12345678!!js/jquery.js!!...

Последний пример продразумевает, что вы использовали директиву mod_rewrite:

Листинг 10: улучшение вида URL слияния при помощи mod_rewrite
RewriteRule ^m/(.*) merge.php/$1

Если так не сработает, попробуйте вариант с "?" в итоговом URL:

Листинг 11: улучшение вида URL слияния при помощи mod_rewrite
RewriteRule ^m/(.*) merge.php?$1

Чайник 

Старайтесь, однако, избегать части "?..." в URL слияния, т.к. некоторые старые браузеры отключают клиентское кэширование для таких адресов либо работают неоптимально.







Дмитрий Котеров, Лаборатория dk. ©1999-2016
GZip
Добавить на Del.icio.us   Добавить на Digg.com   Добавить на reddit.com