Грамотные люди из Yahoo подсказали хорошую идею, ставить заголовок Expires
для статики в далекое-предалекое будущее, и в случае изменения оной, указывать новую версию в имени скриптов/стилей, для того чтобы пробить пользовательский кеш. Однако попытавшись реализовать аналогичную схему для своего подопытного проекта, наткнулся на неприятность - этот метод плохо подходит для использования сторонних скриптов и библиотек. Дело в том, что внутри библиотек при каждом обновлении тоже приходится править пути к файлам, а это большая бессмысленная работа чреватая ошибками.
Тогда решил так, вставлять номер версии (в моем случае — SVN Revision) не в имя файла а в путь к нему.
В итоге получилось так:
/scripts/
/skin/
/pic/
Пример: SVN Revision = 1001
, следовательно, сайт показывает пути так: /scripts_1001/
, /skin_1001/
, /pic_1001/
.
Так, со стилями все. Однако есть еще одна разновидность изображений которые нужно отслеживать превьюшки для картинок встроенных в документы. Здесь пришлось генерировать ссылки на них вида /img/12_tb.jpeg где цифры после ? это 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+.