Грамотные люди из Yahoo подсказали хорошую идею, ставить заголовок Expires для статики в далекое-предалекое будущее, и в случае изменения оной, указывать новую версию в имени скриптов/стилей, для того чтобы пробить пользовательский кеш. Однако попытавшись реализовать аналогичную схему для своего подопытного проекта, наткнулся на неприятность - этот метод плохо подходит для использования сторонних скриптов и библиотек. Дело в том, что внутри библиотек при каждом обновлении тоже приходится править пути к файлам, а это большая бессмысленная работа чреватая ошибками.
Тогда решил так, вставлять номер версии (в моем случае — SVN Revision) не в имя файла а в путь к нему.
В итоге получилось так:
/scripts//skin//pic/Пример: SVN Revision = 1001, следовательно, сайт показывает пути так: /scripts_1001/, /skin_1001/, /pic_1001/.
Так, со стилями все. Однако есть еще одна разновидность изображений которые нужно отслеживать превьюшки для картинок встроенных в документы. Здесь пришлось генерировать ссылки на них вида /img/12_tb.jpeg?221212123, где цифры после ? это unix timestamp последнего изменения документа. Теоретически было бы точнее смотреть дату последней модификации превьюшки, но это как раз и есть лишние обращения к диску, которых хотелось бы избежать, пусть и ценой некоторого увеличения трафика.
Минус такой схемы пока обнаружился только один — при изменении номера ревизии, посетителям придется скачивать заново все используемые ими скрипты, стили и графику скина (но не картинки, закачанные ими же на сайт). Да, несколько неоптимально. Но большинство браузеров получат с сайта ответ 304 и реальные задержки на скачивание будут не столь велики. Кроме того, можно указывать номер ревизии отдельно для скина и для общих скриптов.
А теперь опишу, как это реализовано на практике.
Со стороны бэкенда (я использую PHP-FCGI) все довольно скучно. Внутри PHP-скриптов остается только вывести правильные пути к файлам.
В процессе отладки сайта:
for i in `find ./* -type f -name '*.jpeg'`; do jpegtran -copy none -optimize -perfect -outfile $i $i; done; \ for i in `find ./* -type f -name '*.png'`; do pngcrush -rem alla -reduce -brute $i temp.png; mv temp.png $i; done; \ for i in `find ./* -type f -name '*.js'`; do echo $i; java -jar ~/yuicompressor.jar -o temp.js $i; mv temp.js $i ; gzip -c -9 $i > $i.gz; done; \ for i in `find ./* -type f -name '*.css'`; do echo $i; java -jar ~/yuicompressor.jar -o temp.css $i; mv temp.css $i; gzip -c -9 $i > $i.gz; done; \ for i in `find ./* -type f -name '*.php'`; do echo $i; php -w $i > temp.php; mv temp.php $i; done; \
Что она делает:
Внутри PHP-скриптов остается только вывести правильные пути к файлам.
Теперь вся эта упакованная и ужатая начинка сайта тестируется на предмет возможных глюков, и проверку работы фронтенда с боевыми настройками кеширования и отдачи Expires.
Со стороны фронтенда все немного хитрее. Я использую Nginx, а его настройка сильно отличается от традиционного Апача.
Вот пример конфига одного виртуального сервера с комментариями:
server {
#слушаем порт 80
listen 80;
#перечисляем через пробел имена этого сервера
server_name core.freewheel.ru:
#путь к корню сервера
root /my/path/to/core.freewheel.ru;
#пути к логам
access_log /my/path/to/core-access.log combined;
error_log /my/path/to/core-access.log info;
#реальный путь к favicon - пришлось так сделать
#т.к. у меня несколько разных сайтов тестируются в одной папке
set $favicon "/my/path/to/favicon.ico";
#подключаем шаблон настроек сервера, в нем самое интересное
include _servers_template;
#разрешаем себе посмотреть статус сервера
location = /nginx_status {
stub_status on;
access_log off;
allow 127.0.0.1;
deny all;
}
#включаем сжатие для тех браузеров, которые его понимают
gzip on;
#определяем минимальную версию протокола HTTP, для которой отдаем архивы
gzip_http_version 1.0;
#устанавливаем максимальный уровень сжатия
gzip_comp_level 9;
#разрешаем проксировать сжатые файлы
gzip_proxied any;
#и определяем типы файлов (все, которые хорошо сжимаются)
gzip_types text/plain text/css application/x-javascript text/xml application/xml application/xml+rss text/javascript image/x-icon;
}А теперь собственно основной файл настроек, использующийся для всех виртуальных серверов в неизменном виде:
index index.php index.html;
#вот здесь преобразуем URI вида /skin_1001/ в /skin/
rewrite ^/scripts_\d+/(.*)$ /scripts/$1 break;
#и делаем внутреннюю переадресацию
rewrite ^/skin_\d+/(.*)$ /skin/$1 break;
#посетитель думает что URI не изменился
rewrite ^(/pic/[^_/]+)_\d+(/.*)$ $1$2 break;
#показываем, откуда брать favicon.ico
location = /favicon.ico {
rewrite ^(.+)$ $favicon break;
expires 10m;
}
location / {
#стили, скрипты и XML-файлы
location ~* ^.+\.(css|js|xml)$ {
#вот для этого и делались заранее архивированные .gz версии
#css и js файлов. Nginx не будет тратить время и сжимать их каждый раз
#заново, а просто отдаст уже готовые архивы, если браузер клиента может
#их принять
gzip_static on;
expires 1y;
}
#это понадобилось для редактора FCK
location ~* ^/scripts.+\.html\?*.*$ {
expires 1y;
}
#несуществующие файлы html и папки отправляем на бэкенд
if (!-e $request_filename ) {
rewrite ^/(.*)$ /index.php ;
}
#проксируем все запросы к PHP-файлам на FCGI бэкенд
location ~* \.php$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
include _fastcgi_params;
}
#картинки
location ~* ^.+\.(bmp|gif|jpg|jpeg|ico|png|swf|tiff)$ {
expires 1y;
}
#файлы
location ~* ^.+\.(bz2|dmg|gz|gzip|rar|tar|zip)$ {
expires 1y;
}
#другие статические файлы
location ~* ^.+\.(pdf|txt)$ {
expires 1y;
}
}Обычно nginx собирается без модуля статического сжатия, поэтому при его сборке надо указать опцию --with-http_gzip_static_module — без этого gzip_static не заработает, и серверу придется сжимать файлы каждый раз заново. Также надо иметь в виду, что указанная конфигурация приведена для версии 0.7+.