Статьи

Автор: Мациевский Николай aka sunnybear
Опубликована: 6 августа 2008

nginx как reverse proxy

Несколько читателей блога webo.in просили меня выложить конфигурацию связки nginx + Apache, на которой работает сервер. Хотя это и не относится напрямую к теме клиентской оптимизации. Однако, большинству специалистов, занимающихся клиентской оптимизацией, будет интересно узнать о настройке нескольких хостов для выдачи статики и пара других трюков, связанных с балансировкой запросов.

Также я подробно комментирую все настройки конкретно Apache, которые так или иначе относятся к самой оптимизации времени загрузки страниц.

Конфигурация Apache 2

Итак, Apache установлен на localhost на порт 8080, далее я буду приводить части его конфигурационного файла с комментариями, что они означают, и как их можно поменять.

Gzip для HTML/XML/ICO

С самого начала включаем gzip для текстовых файлов и favicon:

AddEncoding gzip .gz
AddOutputFilterByType DEFLATE text/html
AddOutputFilterByType DEFLATE text/xml
AddOutputFilterByType DEFLATE image/x-icon

Далее я устанавливаю максимальную степень сжатия (9) для таких файлов и максимальный размер окна (15). Если сервер не такой мощный, то уровень сжатия можно выставить в 1, размер файлов при этом увеличивается примерно на 20%.

DeflateCompressionLevel 9
DeflateWindowSize 15

Мы отключаем сжатие для тех браузеров, у которых проблемы с его распознаванием (такие случаи уже достаточно хорошо описаны и задокументированы):

BrowserMatch ^Mozilla/4 gzip-only-text/html
BrowserMatch ^Mozilla/4\.0[678] no-gzip
BrowserMatch \bMSIE !no-gzip !gzip-only-text/html

Gzip для CSS/JS

Теперь мы заботимся о сжатии для CSS/JS-файлов (эта техника хорошо описана в соответствующей статье). Для всех файлов (.css|.js|.xml)

RewriteEngine ON
RewriteCond %{HTTP:Accept-encoding} !gzip [OR]
RewriteCond %{HTTP_USER_AGENT} Safari [OR]
RewriteCond %{HTTP_USER_AGENT} Konqueror
RewriteRule ^(.*)\.(css|js|xml)\.gz$ $1.$2 [QSA,L]

Здесь мы выставляем соответствующий MIME-тип для сжатых стилей и скриптов:

<FilesMatch .*\.js\.gz$>
    ForceType text/javascript
    Header set Content-Encoding: gzip
</FilesMatch>
<FilesMatch .*\.css\.gz$>
    ForceType text/css
    Header set Content-Encoding: gzip
</FilesMatch>

Если серверных ресурсов не так жалко, то можно архивировать CSS/JS файлы и стандартным для Apache образом примерно так:

<FilesMatch .*\.(js|css)$>
    SetOutputFilter DEFLATE
    BrowserMatch ^Safari no-gzip
    BrowserMatch ^Konqueror no-gzip
</FilesMatch>

Далее пара небольших фиксов для улучшения работы с различными локальными прокси-серверами: мы указываем на необходимость передачи заголовка User-Agent (потому что по нему принимается решение о сжатии) и заголовка Cache-Control (чтобы кеширование не осуществлялось на прокси-сервере, а файл передавался полностью пользователю). Цитата с w3.org:

[Этот заголовок] указывает на то, что все ответное сообщение полностью или его часть предназначена для отдельного пользователя и не должна быть сохранена в каком-то промежуточном кеше

Ну и сам сами правила, собственно:

Header append Vary User-Agent
Header append Cache-Control private

Кеширование

Включаем кеширование для всех файлов сроком на 10 лет:

ExpiresActive On
ExpiresDefault "access plus 10 years"

Если вы хотите конкретно для HTML-файлов его выключить, то можно поступить следующим образом:

<FilesMatch .*\.(html|php)$>
    ExpiresActive Off
</FilesMatch>

Для корневой директории выставляем ETag и корректируем кеширование в Apache для работы с SSI (через XBitHack):

<Directory "RootDir/">
    FileETag MTime Size
    XBitHack full
</Directory>

Для Личного кабинета кеширование у меня отключено:

<Directory "RootDir/my/">
    ExpiresActive Off
    XBitHack off
</Directory>

В принципе, это все, что нам нужно от Apache, но у меня была еще одна небольшая задача/проблема на сервере.

«Мягкое» серверное кеширование

Суть проблемы в том, что на webo.in есть уникальная страница для каждого проверенного сайта. Находится она по адресу https://webo.in/urls/{адрес_сайта}/. Однако, в некоторых случаях сама страница может физически не существовать (фактически, это статичный HTML). Поэтому нужно при отсутствии страницы перенаправлять запросы на динамический адрес, чтобы он выбрал данные из базы и создал сам HTML-файл.

Более того, именно такие адреса прописаны в sitemap.xml, поэтому поисковый трафик идет именно на них. В связи с чем нам жизненно необходимо, чтобы они работали (и работали максимально быстро). Для этого в конфигурации Apache проверяется, существует ли физический файл, если нет и он запрашивается из нужной нам директории, то такой запрос нужно адресовать динамическому серверному скрипту:

RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} ^/urls/(.*)/
RewriteCond %{REQUEST_FILENAME} !index.s?html
RewriteRule (.*) https://webo.in/check/?url=%1 [QSA,L]

Таким образом, каждая страница, которой физически нет на сервере, но она заявлена в каких-то внешних источниках, появляется при первом запросе к ней.

Формат логов

Поскольку реальный IP пользователя у нас будет в другой переменной окружения (из-за nginx), нам нужно изменить формат логов. У меня это сделано следующим образом:

LogFormat "%{X-Real-IP}i %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"" combined

Откуда берется переменная X-Real-IP речь пойдет чуть ниже.

Конфигурация nginx

Reverse proxy

Для начала пробросим все запросы к nginx на Apache:

server {
    listen  webo.in;
    server_name webo.in www.webo.in;

    location / {
	proxy_pass http://127.0.0.1:8080/;
	proxy_set_header   X-Real-IP $remote_addr;
	proxy_set_header   Host $http_host;
	proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
    }																							    
}

Apache у нас поднят на порту 8080, туда мы направляем все запросы к хосту webo.in, которые принял nginx. Дополнительно мы выставляем переменные X-Real-IP (для нормального ведения логов Apache, иначе у нас будут одни записи типа 127.0.0.1 вместо IP пользователей) и Host (так как Apache занимается не одним хостом, то знать, на какой хост пришли запросы ему тоже нужно).

теперь более интересная часть. Нам нужно сделать несколько (у меня это 1) хостов для выдачи статики (картинок и др.) прямо с nginx (чтобы лишний раз не пробрасывать ее на Apache).

server {
    listen i.webo.in;
# здесь можно вписать любое число виртуальных хостов, которые
# мы хотим подключить для выдачи физически одинаковой статики
    server_name i.webo.in;
# далее добавляются кеширующие заголовки для всех картинок,
# лежащих прямо в корне i.webo.in
    location ~* /[^/]*\.(jpg|jpeg|gif|png)$ {
	access_log   off;
	expires  10y;
	add_header  Last-Modified: $date_gmt;
    }
# здесь делаем то же самое и для папки urls, которая
# обслуживает запросы типа i.webo.in/urls/webo.in.png
# Естественный вопрос: почему нельзя сразу для всех файлов
# прописать такие правила? Ответ: для ограничения возможных
# адресов, которые обслуживает nginx (т.е. в такой
# конфигурации он отвечает только за корень и urls)
    location ~* /urls/[^/]*\.(jpg|jpeg|gif|png)$ {
	root DocRoot/i/urls;
	access_log  off;
	expires 10y;
	add_header  Last-Modified: $date_gmt;
    }
# далее раздел, занимающийся опять-таки "мягким" серверным
# кешированием: если картинки для сайта нет, то запрос
# направляется серверному скрипту
    if (!-f $request_filename) {
	rewrite ^/urls/(.*)\.png$ https://webo.in/i/image.php?url=$1 break;
    }
# ну и дальше пара строк для обработки 404/403-ошибок
    error_page  404  https://webo.in/404.shtml;
    error_page  403  https://webo.in/404.shtml;
}

Комментарий от посмотреть профиль sharifulin:

Опыт работы с nginx подсказывает

expires 10y

заменить на

expires max

Иногда (но все же бывает) сервер может спросить установку даты и времени и тогда отдача контента с заголовком Expires на 10 лет не поможет, на помощь приходит параметр max, который задаёт время 31 декабря 2037 23:55:55 GMT для строки Expires и 10 лет для строки Cache-Control.

В общем, это все. На выходе мы получаем 2 хоста: один чисто под nginx, второй на связке Apache + nginx. Дополнительно осуществляем набор действий по клиентской оптимизации и некоторому серверному кешированию.

Читать дальше

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