nginx web

HTTP-сервер Nginx

Dmitry Vasiliev 23:19, 2012 2 6 15:34, 2008 12 29

Впервые было опубликовано в журнале "Системный администратор" #10 за 2008 год

Уже долгое время Apache является наиболее популярным HTTP-сервером с открытым исходным кодом. Но он может быть слишком громоздким для некоторых применений. И здесь на сцену выходят новые игроки — легковесные, асинхронные HTTP-сервера, одним из которых является Nginx.

Вот уже более восьми лет я использую HTTP-сервер Apache, и в большинстве случаев он меня во всем устраивает. Но недавно, установив его на один из небольших серверов с Ubuntu Linux, я подумал, что Apache не очень целесообразно использует ресурсы данного сервера. И с этой мыслью решил попробовать какой-нибудь альтернативный вариант. Я уже давно слышал о высокопроизводительных асинхронных серверах Lighttpd (произносится lighty, http://www.lighttpd.net/) и Nginx (произносится engine x, http://sysoev.ru/nginx/), но до этого момента не использовал их на практике. Надо также заметить, что по статистике Netcraft на Октябрь 2008-го года Lighttpd и Nginx занимают, соответственно, 5 и 6 места: http://survey.netcraft.com/Reports/200810/.

Теперь, когда у меня было два варианта, оставалось самое сложное — выбрать наиболее подходящий для моего случая. Это на самом деле очень сложный выбор, т.к. оба имеют практически одинаковые возможности (хотя Nginx также может использоваться как почтовый прокси-сервер я не учитывал это в сравнении). Оба используются на высоко-посещаемых сайтах, например YouTube использует Lighttpd, а Rambler — Nginx. Оба имеют достаточно хорошую документацию, в том числе Nginx имеет документацию на русском. В итоге для себя я остановился на Nginx по следующим причинам:

  • Многие источники (например, http://hostingfu.com/article/nginx-vs-lighttpd-for-a-small-vps) отмечают, что Lighttpd имеет проблемы со стабильностью и утечки памяти. Даже если эти проблемы остались в прошлом похоже, что автор не уделял им достаточно внимания.
  • Nginx, в отличие от Lighttpd, имеет две официальные ветки разработки (стабильную и экспериментальную), что, на мой взгляд, говорит о большей заботе автора о конечных пользователях и качестве проекта в целом.

Мне хотелось иметь возможность использовать сервер с языком Python (http://www.python.org) с помощью стандартного интерфейса WSGI (http://ru.wikipedia.org/wiki/WSGI). При этом для Nginx существует дополнительный модуль mod_wsgi, а в Lighttpd это можно делать только через шлюз FastCGI > WSGI.

Принципы работы

Прежде чем обратиться к рассмотрению непосредственно сервера Nginx рассмотрим чем отличается асинхронный (событийный) подход от параллельной обработки соединений в отдельных процессах, или потоках.

В простейшем случае при параллельной обработке соединений основной процесс занимается ожиданием входящих соединений и после этого отдает всю работу по обработке нового соединения дочернему процессу, или отдельному потоку. У данного подхода есть следующие достоинства:

  • Это достаточно простая модель для программирования сервера и подключаемых модулей;
  • Каждое соединение обрабатывается независимо и таким образом возможная долгая обработка данных выполняемая в одном процессе (потоке) не влияет на другие;

Недостатки:

  • Такой подход плохо масштабируется. Например, 1000 одновременно открытых соединений может быть вполне нормальным числом, но 1000 одновременно работающих процессов, или потоков могут быть проблемой;

При асинхронной обработке в случае одного процесса с одним потоком используется специальный системный вызов осуществляющих диспетчеризацию открытых сокетов и дескрипторов файлов. Этот системный вызов возвращает только сокеты и дескрипторы готовые для открытия соединения, чтения, или записи. Так как основное время обычно тратится на ожидание ввода-вывода и скорость работы процессора с памятью во много раз выше скорости ввода-вывода, можно успевать выполнять необходимую обработку данных в то время как система ожидает их новую порцию. Достоинства данного похода:

  • Для многих применений только один процесс с одним потоком может обрабатывать больше соединений чем в случае параллельной обработки;
  • Так как один процесс с одним потоком может обрабатывать сразу несколько соединений для обработки большего количества соединений используется меньше ресурсов;

Недостатки:

  • Программирование асинхронных приложений может быть сложнее чем приложений с параллельной обработкой;
  • В случае использования только одного процесса с одним потоком он может плохо масштабироватся, хотя и лучше чем при параллельной обработке;
  • В простейшем случае, асинхронный подход не может использоваться если необходима долгая обработка данных;

Как можно заметить оба подхода имеют свои достоинства и недостатки. Что бы объединить достоинства и уменьшить недостатки из обоих подходов, в настоящее время для асинхронных сетевых приложений наиболее популярен гибридный подход, при котором несколько асинхронных приложений работают параллельно в отдельных процессах, или потоках. Теперь рассмотрим как это работает в Nginx.

Особенности Nginx

Nginx разрабатывается Игорем Сысоевым (http://sysoev.ru/) с весны 2002-го года. Первая публичная версия вышла осенью 2004-го. На данный момент сервер находится в активной разработке с точки зрения функциональности, но нет никаких нареканий с точки зрения стабильности. Последняя стабильная версия на данный момент — 0.6.32 (http://sysoev.ru/nginx/nginx-0.6.32.tar.gz).

Архитектурно Nginx — это асинхронный сервер, который использует один главный процесс для приема соединений и несколько рабочих процессов для их обработки. Рабочие процессы выполняются от непривилегированного пользователя. Асинхронная диспетчеризация может осуществляться как старыми вызовами select() и poll(), так и с использованием современных подходов специфичных для различных операционных систем: kqueue (для FreeBSD начиная с версии 4.1), epoll (для Linux начиная с версии 2.6), rt signals (для Linux начиная с версии 2.2.19), /dev/poll (для Solaris начиная с версии 7) и event ports (для Solaris начиная с версии 10). Так же для оптимизации производительности используются исключающие лишнее копирование данных системные вызовы sendfile(), sendfile64(), или sendfilev() и сведены к минимуму операции копирования данных внутри сервера.

Далее рассмотрим, как собрать и установить сервер и подробнее остановимся на конфигурировании некоторых сценариев работы.

Сборка и установка

Хотя Nginx уже доступен в пакетах для последних версий Ubuntu Linux я решил собирать его из исходников потому что хотел использовать более свежую стабильную версию и модуль mod_wsgi для поддержки WSGI протокола Python. Если вам не нужно собирать Nginx с mod_wsgi вы можете пропустить все места где он упоминается. Итак, для сборки нам понадобится исходный код Nginx, исходный код библиотеки совместимых с Perl регулярных выражений (PCRE) и исходный код модуля mod_wsgi. Здесь мы используем самые последние, на данный момент, версии mod_wsgi и библиотеки PCRE. При использовании более старых версий PCRE версия не должна быть ниже 4.4:

$ wget http://sysoev.ru/nginx/nginx-0.6.32.tar.gz
$ tar -xzf nginx-0.6.32.tar.gz
$ wget ftp://ftp.csx.cam.ac.uk/pub/software/programming/pcre/pcre-7.8.tar.bz2
$ tar -xjf pcre-7.8.tar.bz2
$ mv pcre-7.8 pcre
$ wget -O mod_wsgi.tar.gz http://hg.mperillo.ath.cx/nginx/mod_wsgi/archive/tip.tar.gz
$ tar -xzf mod_wsgi.tar.gz
$ mv mod_wsgi-8994b058d2db mod_wsgi

Здесь я также переименовал директории для PCRE и mod_wsgi что бы в дальнейшем использовать для конфигурации один скрипт, не изменяя его. Автор mod_wsgi выбрал подход с применением патчей что бы успевать за изменениями в Nginx и поэтому мы должны приложить патч для нашей версии Nginx. Запишем его как mod_wsgi_nginx-0.6.32.patch:

--- src/ngx_http_wsgi_handler.c 2008-03-26 22:35:15.000000000 +0300
+++ src/ngx_http_wsgi_handler.c 2008-10-06 16:55:07.000000000 +0400
@@ -71,7 +71,7 @@

   if (r->method == NGX_HTTP_GET || r->method == NGX_HTTP_HEAD) {
     /* XXX not sure */
-    rc = ngx_http_discard_body(r);
+    rc = ngx_http_discard_request_body(r);

     if (rc != NGX_OK && rc != NGX_AGAIN) {
       return rc;

И затем приложим:

$ patch -d mod_wsgi -p0 < mod_wsgi_nginx_0.6.32.patch

Для сборки Nginx с SSL также необходимо установить библиотеку OpenSSL и для использования модуля ngx_http_gzip_module библиотеку zlib. Для сборки mod_wsgi понадобится установить пакет python-dev:

$ sudo apt-get install libssl-dev
$ sudo apt-get install zlib1g-dev
$ sudo apt-get install python-dev
$ touch nginx.sh
$ chmod a+x nginx.sh
$ vi nginx.sh

Для редактирования скрипта конфигурации я использую текстовый редактор Vi, но вы можете использовать любой другой удобный для вас редактор. Для сборки я написал следующий скрипт:

#! /bin/sh
./configure \
    --prefix=/var/lib/nginx \
    --sbin-path=/usr/sbin/nginx \
    --conf-path=/etc/nginx/nginx.conf \
    --pid-path=/var/run/nginx.pid \
    --error-log-path=/var/log/nginx/error.log \
    --http-log-path=/var/log/nginx/access.log \
    --lock-path=/var/lock/nginx \
    --http-client-body-temp-path=/var/lib/nginx/client_body \
    --http-proxy-temp-path=/var/lib/nginx/proxy \
    --http-fastcgi-temp-path=/var/lib/nginx/fastcgi \
    --user=www-data \
    --group=www-data \
    --without-select_module \
    --without-poll_module \
    --without-http_ssi_module \
    --without-http_geo_module \
    --without-http_referer_module \
    --without-http_memcached_module \
    --without-http_limit_zone_module \
    --without-http_empty_gif_module \
    --without-http_browser_module \
    --without-http_upstream_ip_hash_module \
    --with-http_ssl_module \
    --with-http_gzip_static_module \
    --with-pcre=../pcre \
    --add-module=../mod_wsgi

По умолчанию Nginx устанавливается в директорию /usr/local/nginx и использует поддиректории для файлов конфигурации и различных временных файлов, но я привык придерживаться стандарта иерархии файловой системы (http://www.pathname.com/fhs/) и поэтому в начале скрипта идут опции переназначения различных директорий. Затем идут опции установки пользователя и группы под которыми будут работать рабочие процессы. В случае если данные пользователь и группа не созданы в вашей системе их надо будет создать вручную. После этого находятся опции включающие и отключающие различные модули, в том числе отключение поддержки системных вызовов select() и poll() т.к. в моем случае, на Linux 2.6, будет использоваться вызов epoll(). Если вы не знаете какой системный вызов может использоваться в вашей системе, оставьте это решение скрипту конфигурации, который выберет наиболее оптимальный вариант. Соответственно если вам нужны другие пути к файлам и директориям, другой пользователь и группа, или другие модули, вы можете внести изменения в эти секции. После того как все готово со скриптом конфигурации можно начать конфигурацию:

$ cd nginx-0.6.32
$ ../nginx.sh

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

Configuration summary
  + using PCRE library: ../pcre
  + using system OpenSSL library
  + md5 library is not used
  + sha1 library is not used
  + using system zlib library

  nginx path prefix: "/var/lib/nginx"
  nginx binary file: "/usr/sbin/nginx"
  nginx configuration prefix: "/etc/nginx"
  nginx configuration file: "/etc/nginx/nginx.conf"
  nginx pid file: "/var/run/nginx.pid"
  nginx error log file: "/var/log/nginx/error.log"
  nginx http access log file: "/var/log/nginx/access.log"
  nginx http client request body temporary files: "/var/lib/nginx/client_body"
  nginx http proxy temporary files: "/var/lib/nginx/proxy"
  nginx http fastcgi temporary files: "/var/lib/nginx/fastcgi"

Теперь можно начать компиляцию:

$ make

Если компиляция прошла успешно, устанавливаем Nginx:

$ sudo make install

При установке, для конфигурации выше, будет создана директория /var/lib/nginx, бинарный файл сервера nginx будет установлен в директорию /usr/sbin (в случае присутствия старого файла он переименовывается в nginx.old), будет создана директория /etc/nginx в которую будет перенесена конфигурация по-умолчанию и будут созданы директории /var/log/nginx и /var/lib/nginx с нужными правами доступа.

Запуск и управление

В простейшем случае мы можем запустить Nginx следующим образом:

$ sudo /usr/sbin/nginx

После первого запуска можно зайти по адресу http://localhost/ и увидеть страницу приветствия. Для остановки сервера можно выполнить:

$ sudo killall nginx

Конечно такой способ управления лучше всего использовать только для первого тестирования, а для обычного использования и, в последующем, автоматического запуска, лучше написать скрипт управления и положить его в директорий /etc/init.d под именем nginx, не забывая дать права на исполнение:

#! /bin/sh
#
# /etc/init.d/nginx: start and stop nginx http server
#

PID_FILE=/var/run/nginx.pid
CAT=/bin/cat
NGINX=/usr/sbin/nginx
START_STOP_DAEMON=/sbin/start-stop-daemon

. /lib/lsb/init-functions

do_start()
{
    $START_STOP_DAEMON --start --quiet --pidfile $PID_FILE --exec $NGINX
    RETVAL=$?

    if [ $RETVAL = 0 ]; then
        log_success_msg "Server started at pid" $($CAT $PID_FILE)
    elif [ $RETVAL = 1 ]; then
        log_failure_msg "Server already running at pid" $($CAT $PID_FILE)
    else
        log_failure_msg "Error starting server"
    fi

    return $RETVAL
}

do_stop()
{
    $START_STOP_DAEMON --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PID_FILE --exec $NGINX
    RETVAL=$?

    if [ $RETVAL = 0 ]; then
        log_success_msg "Server stopped"
    elif [ $RETVAL = 1 ]; then
        log_failure_msg "Server already stopped"
    else
        log_failure_msg "Error stopping server"
    fi

    return $RETVAL
}

is_running()
{
    if [ -f $PID_FILE ]; then
        kill -n 0 $($CAT $PID_FILE)
        if [ $? = 0 ]; then
            return 1
        else
            return 0
        fi
    fi
    return 0
}

case "$1" in
  start)
    log_begin_msg
    do_start
    log_end_msg $?
    ;;

  stop)
    log_begin_msg
    do_stop
    log_end_msg $?
    ;;

  status)
    log_begin_msg
    is_running
    if [ $? = 0 ]; then
        log_success_msg "Server not running"
        log_end_msg 1
    else
        log_success_msg "Server running at pid" $($CAT $PID_FILE)
        log_end_msg 0
    fi
    ;;

  restart)
    log_begin_msg
    do_stop
    if [ $? = 0 ] || [ $? = 1 ]; then
        do_start
    fi
    log_end_msg $?
    ;;

  reload)
    log_begin_msg
    is_running
    if [ $? = 0 ]; then
        log_failure_msg "Server not running"
        log_end_msg 1
    else
        $NGINX -t
        if [ $? = 0 ]; then
            kill -HUP $($CAT $PID_FILE)
            if [ $? = 0 ]; then
                log_success_msg "Server configuration reloaded"
                log_end_msg 0
            else
                log_failure_msg "Error while reloading configuration"
                log_end_msg 1
            fi
        else
            log_failure_msg "Configuration errors found"
            log_end_msg 1
        fi
    fi
    ;;

  *)
    log_success_msg "Usage: /etc/init.d/nginx {start|stop|status|restart|reload}"
    exit 3
    ;;
esac

exit $?

Дадим скрипту нужные права:

$ sudo chmod a+x /etc/init.d/nginx

И теперь можно управлять Nginx через наш скрипт, используя команды start, stop, status, restart и reload. Что бы Nginx запускался при старте системы можно воспользоваться следующей командой:

$ sudo update-rc.d nginx defaults

Эта команда создаст ссылки на скрипт /etc/init.d/nginx в каталогах соответствующих различным уровням запуска: /etc/rc0.d - /etc/rc6.d.

Настраиваем статический сайт

Когда мы разобрались с управлением сервером, рассмотрим детали настройки статического сайта. Вот пример основного файла конфигурации для статических сайтов:

user  www-data www-data;

worker_processes  4;

error_log  /var/log/nginx/error.log  info;
pid        /var/run/nginx.pid;

events {
    worker_connections  1024;
}

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    charset utf-8;
    server_tokens off;
    sendfile on;

    gzip on;
    gzip_http_version 1.0;
    gzip_comp_level 6;
    gzip_proxied any;
    gzip_disable "MSIE [1-6]\.(?!.*SV1)";
    gzip_buffers 16 8k;
    gzip_types text/plain text/html text/css
        application/x-javascript text/xml application/xml
        application/javascript text/js
        application/xml+rss text/javascript application/atom+xml;

    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;

    include /etc/nginx/example.ru.conf
}

Рассмотрим все опции по порядку:

  1. Опция user описывает от имени какого пользователя и группы будут выполняться рабочие процессы.
  2. Опция worker_processes определяет количество рабочих процессов. Количество рабочих процессов — параметр который влияет на производительности и, в основном, зависит от ожидаемой нагрузки на сервер и требуемой функциональности сайтов. В простейшем случае может быть только один рабочий процесс, но если используется компрессия данных, или SSL, то их количество может быть равно количеству процессоров, или в два раза большему числу. В данном случае я выбрал 4 рабочих процесса, т.к. пример со статическим сайтом использует и компрессию и SSL и запускается на двухпроцессорной машине. В случае если используются, не совсем свойственные Nginx, синхронные модули, как например, mod_wsgi нужно выбрать количество рабочих процессов в зависимости от ожидаемого количества одновременных запросов и скорости их обработки.
  3. Опции error_log и pid описывают соответственно путь к файлу журнала ошибок, вместе с уровнем логгинга и путь к файлу в котором будет хранится идентификатор главного процесса сервера.
  4. Секция events описывает опции связанные с обработкой событий. Опция worker_connections описывает ограничение на количество одновременно обрабатываемых рабочими процессами соединений. Таким образом, в случае статического сайта, максимальное количество одновременных соединений с сервером можно рассчитать по формуле: worker_processes * worker_connections. Также в этой секции можно выбрать метод обработки событий, например, use epoll; и другие настройки связанные с обработкой событий.
  5. Секция http описывает конфигурацию HTTP сервера и всех сайтов. Опции include и default_type описывают каталог MIME-типов и тип по умолчанию соответственно. Опция charset определяет кодировку которая будет добавлена в заголовок ответа Content-Type. Если ее не указывать браузеры будут использовать собственные кодировки по-умолчанию как кодировку контента. Опция server_tokens управляет выводом номера версии сервера в ответах. Опция sendfile включает использование системного вызова оптимизирующего посылку файлов. Набор опций начинающихся на gzip описывает параметры сжатия ответов. Набор опций начинающихся на ssl описывает настройки SSL сессий. Опция include подключает другой файл конфигурации, в данном случае непосредственно конфигурацию сайта.

Конфигурация сайта может выглядеть следующим образом:

server {
    listen       80;
    server_name  example.ru *.example.ru;

    error_log   /var/log/nginx/example.ru/error.log  info;
    access_log  /var/log/nginx/example.ru/access.log;

    location / {
        root   /home/www/example.ru/data/;
        index  index.html;

        if ($host != "example.ru") {
            rewrite ^(.*) http://example.ru$1 permanent;
        }
    }
}

server {
    listen       443;
    server_name  example.ru;

    error_log   /var/log/nginx/example.ru/error.log  info;
    access_log  /var/log/nginx/example.ru/access.log;

    ssl on;
    ssl_certificate /etc/nginx/cert/example.ru.pem;
    ssl_certificate_key /etc/nginx/cert/example.ru.pem;

    auth_basic "Example admin place";
    auth_basic_user_file /home/www/example.ru/.htpasswd;

    location / {
        root /home/www/example.ru/data/admin/;
        index  index.html;
    }
}

В данном случае файл /etc/nginx/example.ru.conf описывает две версии одного сайта — обычную HTTP версию и HTTPS версию защищенную паролем. Секции server описывают конфигурацию виртуальных серверов. Рассмотрим опции подробнее:

  1. Опция listen определяет адрес и порт на котором сервер будет принимать запросы. Здесь также можно задать дополнительные опции управляющие приемом соединений. В том числе с помощью этой опции можно объединить конфигурацию HTTP и HTTPS серверов в одной секции, хотя в примере этого не сделано.
  2. Опция server_name задает имя и псевдонимы сайта.
  3. Опции error_log и access_log определяют путь к файлу журнала ошибок с уровнем логгирования и журналу запросов соответственно.
  4. Секция location объединяет конфигурационные параметры в зависимости от URI запроса.
  5. Опции root и index описывают корневой директорий сайта и имя индексного файла по умолчанию.
  6. Опции if и rewrite используются для изменения URI запроса. В данном примере все запросы для других доменов перенаправляются на example.ru.
  7. Опции начинающиеся с ssl_ описывают путь к SSL сертификату и приватному ключу, соответственно.
  8. Опции начинающиеся с auth_basic описывают текст комментария при вводе пароля и путь к файлу со списком паролей соответственно, в случае использования простой HTTP авторизации. Файл со списком паролей может быть создан утилитой htpasswd из пакета сервера Apache. На данный момент Nginx не поддерживает других методов авторизации кроме простой HTTP авторизации.

Настраиваем HTTP-прокси

Часто HTTP сервер является лишь посредником (прокси), стоящим перед другим HTTP сервером. Например, такая конфигурация нужна если один из сайтов сделан с применением технологии которая уже имеет свой HTTP сервер, или посредник используется как балансировщик нагрузки для нескольких стоящих за ним серверов. В Nginx эта функциональность поддерживается модулем ngx_http_proxy_module. Также с помощью модуля ngx_http_memcached_module Nginx можно использовать совместно с Memcached (http://www.danga.com/memcached/) для кэширования контента. Рассмотрим пример простого сайта который перенаправляет запросы на сервер сделанный с использованием технологии Zope 3 (http://www.zope.org/). Сначала добавим следующие опции в секцию http файла /etc/nginx/nginx.conf:

proxy_set_header  Host            $host;
proxy_set_header  X-Real-IP       $remote_addr;
proxy_set_header  X-Forwarded-For $proxy_add_x_forwarded_for;

include /etc/nginx/zope3.example.ru.conf

Эти опции устанавливают дополнительные заголовки которые будут переданы вспомогательному серверу и подключают файл конфигурации сайта. Конфигурация сайта может быть такой:

server {
    listen       80;
    server_name  zope3.example.ru;

    error_log   /var/log/nginx/zope3.example.ru/error.log  info;
    access_log  /var/log/nginx/zope3.example.zope3.ru/access.log;

    error_page 502 503 504  /maintenance.html;
    location /maintenance.html {
        alias /home/www/zope3.example.ru/data/index.html;
    }

    location / {
        proxy_pass  http://127.0.0.1:8080/++skin++example/sites/example/++vh++http:zope3.example.ru:80/++/;
    }
}

Рассмотрим не описанные выше опции:

  1. Опция error_page задает URI который будет показываться для заданных кодов ошибок. В данном случае это ошибки связанные с недоступностью вспомогательного сервера, или невозможностью обработать запрос.
  2. Опция alias задает замену для указанного пути. Таким образом для страницы сайта /maintenance.html будет возвращено содержимое файла /home/www/zope3.example.ru/data/index.html, который может содержать страницу с описанием почему сервер в данный момент не доступен.
  3. Опция proxy_pass задает адрес и путь на вспомогательном сервере на который будет отображаться путь на сайте. Таким образом в случае доступа к http://example.zope3.ru/page.html пользователю будет возвращен результат обработки запроса вспомогательным сервером по адресу http://127.0.0.1:8080/++skin++example/sites/example/++vh++http:zope3.example.ru:80/++/page.html.

Настраиваем PHP

На данный момент наиболее оптимальным способом связать Nginx и PHP является протокол FastCGI. При этом PHP должен работать в отдельном процессе, что не является особой проблемой так как PHP уже имеет собственный FastCGI демон. Для управления демоном PHP можно использовать следующий скрипт, который, в свою очередь, можно сохранить как /etc/init.d/php-fastcgi и дать ему права на исполнение:

#! /bin/sh

EXEC_AS_USER=www-data

FCGI_HOST=127.0.0.1
FCGI_PORT=9000

PHP_FCGI_CHILDREN=5
PHP_FCGI_MAX_REQUESTS=1000

DAEMON=/usr/bin/php-cgi
PIDFILE=/var/run/php-fastcgi.pid
PHP_CONFIG_FILE=/etc/php5/cgi/php.ini
START_STOP_DAEMON=/sbin/start-stop-daemon

export PHP_FCGI_CHILDREN PHP_FCGI_MAX_REQUESTS
DAEMON_ARGS="-q -b $FCGI_HOST:$FCGI_PORT -c $PHP_CONFIG_FILE"

. /lib/lsb/init-functions

do_start()
{
    $START_STOP_DAEMON --start --quiet --pidfile $PIDFILE --exec $DAEMON --test \
        || return 1
    $START_STOP_DAEMON --start --quiet --pidfile $PIDFILE --exec $DAEMON \
        --background --make-pidfile --chuid $EXEC_AS_USER --startas $DAEMON -- \
        $DAEMON_ARGS \
        || return 2
}

do_stop()
{
    $START_STOP_DAEMON --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE # --name $DAEMON
    RETVAL="$?"
    [ "$RETVAL" = 2 ] && return 2
    $START_STOP_DAEMON --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON
    [ "$?" = 2 ] && return 2
    rm -f $PIDFILE
    return "$RETVAL"
}

case "$1" in
  start)
        log_begin_msg
        do_start
        log_end_msg $?
        ;;
  stop)
        log_begin_msg
        do_stop
        log_end_msg $?
        ;;
  restart)
        log_begin_msg
        do_stop
        if [ $? = 0 ] || [ $? = 1 ]; then
            do_start
        fi
        log_end_msg $?
        ;;
  *)
        echo "Usage: php-fastcgi {start|stop|restart}"
        exit 3
        ;;
esac

После старта демона он начнет принимать запросы по адресу 127.0.0.1:9000 и для сайта можно использовать следующий файл конфигурации:

server {
    listen       80;
    server_name  php.example.ru;

    error_log   /var/log/nginx/php.example.ru/error.log  info;
    access_log  /var/log/nginx/php.example.ru/access.log;

    location / {
        root   /home/www/php.example.ru/data/;
        index  index.html;
    }

    location ~ \.php$ {
        fastcgi_pass  127.0.0.1:9000;
        fastcgi_index index.php;
        include /etc/nginx/fastcgi_params;
        fastcgi_param SCRIPT_FILENAME /home/www/php.example.ru/data$fastcgi_script_name;
    }
}

Кроме уже известных опций здесь есть секция которая определяет действия для всех путей оканчивающихся на .php:

  1. Опция fastcgi_pass определяет адрес FastCGI сервера. В нашем случае это адрес где запущен демон PHP.
  2. Опция fastcgi_index задает имя индексного файла для FastCGI.
  3. Опцией include мы подключаем файл с параметрами которые будут передаваться в FastCGI запросах. К сожалению мы не можем один раз подключить эти параметры на более верхнем уровне, т.к. следующая опция fastcgi_param, которая в свою очередь задает еще один дополнительный параметр, отменяет все определения параметров более высокого уровня.

Протокол WSGI

До определенного момента для связи WEB-приложения написанного на Python и HTTP-сервера использовалось множество подходов. Например, это могли быть протоколы CGI и FastCGI, или модуль mod_python для Apache. Также многие приложения, например Zope, использовали, жестко интегрированные, HTTP-сервера. Стандартный протокол WSGI (http://www.python.org/dev/peps/pep-0333/) был впервые предложен в декабре 2003-го года и на данный момент поддерживается подавляющим большинством серверов и WEB-приложений написанных на Python. Протокол подробно описывает порядок передачи параметров приложению, передачи ответа серверу и обработку ошибок. Кроме того учитываются расширения специфичные для различных серверов. Простейшее приложение с использованием WSGI может выглядеть так:

def application(environ, start_response):
    status = '200 OK'
    response_headers = [('Content-type','text/plain')]
    start_response(status, response_headers)
    return ['Hello world!\n']

Таким образом протокол позволяет, например, начать отлаживать WEB-приложение используя стандартный пакет wsgiref (появился в Python 2.5) и затем перейти на mod_wsgi (с Nginx, или Apache), или HTTP-сервер из асинхронного каркаса Twisted.

Настраиваем Python с WSGI

WSGI является протоколом который связывает HTTP сервер и приложение на Python. На данный момент уже есть большое количество серверов и приложений работающих по этому протоколу, что позволяет использовать их как взаимозаменяемые компоненты. В примере я подключу к Nginx известный трекер ошибок Trac (http://trac.edgewall.org/). Прежде всего нам нужно создать файл с описанием параметров передаваемых по WSGI. Файл можно записать как /etc/nginx/wsgi_params по аналогии с fastcgi_params:

wsgi_var  REQUEST_METHOD      $request_method;
wsgi_var  QUERY_STRING        $query_string;

wsgi_var  CONTENT_TYPE        $content_type;
wsgi_var  CONTENT_LENGTH      $content_length;

wsgi_var  SERVER_NAME         $server_name;
wsgi_var  SERVER_PORT         $server_port;

wsgi_var  SERVER_PROTOCOL     $server_protocol;


wsgi_var  REQUEST_URI         $request_uri;
wsgi_var  DOCUMENT_URI        $document_uri;
wsgi_var  DOCUMENT_ROOT       $document_root;

wsgi_var  SERVER_SOFTWARE     $nginx_version;

wsgi_var  REMOTE_ADDR         $remote_addr;
wsgi_var  REMOTE_PORT         $remote_port;
wsgi_var  SERVER_ADDR         $server_addr;

wsgi_var  REMOTE_USER         $remote_user;

После этого необходимо добавить в /etc/nginx/nginx.conf в секцию http следующие опции:

wsgi_temp_path /var/lib/nginx/wsgi;
include /etc/nginx/wsgi_params;

Теперь сделаем WSGI скрипт для подключения Trac и положим его как /home/www/wsgi.example.ru/wsgi.py не забывая сделать исполняемым:

#! /usr/bin/env python2.4

import os

os.environ['TRAC_ENV'] = "/home/www/wsgi.example.ru/trac"
os.environ['PYTHON_EGG_CACHE'] = "/var/tmp"

import trac.web.main

application = trac.web.main.dispatch_request
И последний момент — конфигурация сайта:
server {
    listen       80;
    server_name  wsgi.example.ru;

    error_log   /var/log/nginx/wsgi.example.ru/error.log  info;
    access_log  /var/log/nginx/wsgi.example.ru/access.log;

    location / {
        wsgi_pass /home/www/wsgi.example.ru/wsgi.py;
    }
}

Здесь опция wsgi_pass определяет имя скрипта который будет выполнятся в рамках рабочего процесса. Соответственно, так как скрипты выполняются синхронно в рамках рабочих процессов, для оптимизации производительности сайта с WSGI возможно придется увеличить значение опции worker_processes в nginx.conf.

Заключение

В статье я постарался охватить широкий спектр вопросов связанных с Nginx. Конечно многие детали пока остались за кадром, но о них можно прочитать на следующих сайтах: http://sysoev.ru/nginx/ и http://wiki.nginx.org/. Надеюсь статья помогла вам расширить кругозор и поможет выбрать правильный HTTP сервер подходящий для ваших задач.