Статьи

Автор: Алексей Хоменко
Опубликована: 17 октября 2008

Настройка nginx на использование псевдодиректорий

Грамотные люди из Yahoo подсказали хорошую идею, ставить заголовок Expires для статики в далекое-предалекое будущее, и в случае изменения оной, указывать новую версию в имени скриптов/стилей, для того чтобы пробить пользовательский кеш. Однако попытавшись реализовать аналогичную схему для своего подопытного проекта, наткнулся на неприятность - этот метод плохо подходит для использования сторонних скриптов и библиотек. Дело в том, что внутри библиотек при каждом обновлении тоже приходится править пути к файлам, а это большая бессмысленная работа чреватая ошибками.

Тогда решил так, вставлять номер версии (в моем случае — SVN Revision) не в имя файла а в путь к нему.

В итоге получилось так:

  • JavaScript-файлы, общие для всех проектов, лежат в папке /scripts/
  • CSS-файлы и скрипты уровня проекта в папке /skin/
  • Графика необходимая данному проекту /pic/

Пример: SVN Revision = 1001, следовательно, сайт показывает пути так: /scripts_1001/, /skin_1001/, /pic_1001/.

Так, со стилями все. Однако есть еще одна разновидность изображений которые нужно отслеживать превьюшки для картинок встроенных в документы. Здесь пришлось генерировать ссылки на них вида /img/12_tb.jpeg где цифры после ? это unix timestamp последнего изменения документа. Теоретически было бы точнее смотреть дату последней модификации превьюшки, но это как раз и есть лишние обращения к диску, которых хотелось бы избежать, пусть и ценой некоторого увеличения трафика.

Минус такой схемы пока обнаружился только один — при изменении номера ревизии, посетителям придется скачивать заново все используемые ими скрипты, стили и графику скина (но не картинки, закачанные ими же на сайт). Да, несколько неоптимально. Но большинство браузеров получат с сайта ответ 304 и реальные задержки на скачивание будут не столь велики. Кроме того, можно указывать номер ревизии отдельно для скина и для общих скриптов.

На практике

А теперь опишу, как это реализовано на практике.

Со стороны бэкенда (я использую PHP-FCGI) все довольно скучно. Внутри PHP-скриптов остается только вывести правильные пути к файлам.

В процессе отладки сайта:

  • при каждой загрузке (как пример) главной страницы (либо отдельным скриптом, что менее удобно) парсим css файлы, меняем в них пути к картинкам бэкграунда, слегка уминаем css-компрессором (пяток регекспов) и записываем под другим (постоянным) именем в тот же каталог. Обращаемся только к уже обработанным таким образам файлам. Несжатые выполняют роль черновиков.
  • после отладки копируем все на отдельный локальный веб-сервер и там запускаем такую вот ужасную команду:
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; \

Что она делает:

  • сжимает все JPEG-файлы без потери качества
  • сжимает все PNG-файлы опять же без потери качества наивыгоднейшим способом из пары сотен имеющихся
  • сжимает и немного оптимизирует все JS- и CSS-файлы, записывая также их архивированные gzip копии (для чего это нужно объясню чуть ниже)
  • удаляет все комментарии и прочий хлам из PHP-скриптов.

Внутри 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+.

Все комментарии (habrahabr.ru)