FastCGI интерфейс

Модуль: mod_fastcgi

Автор: Jan Kneschke
Дата: 2004-11-03
Версия: 1.3

Суть

Интерфейс FastCGI это быстрейший и наиболее безопастный способ обработки запросов внешними программами, тикими как Perl, PHP а также вашими самописными приложениями.

Содержание

Описание

lighttp предостовляет интерфейс ко внешним программам которые поддерживают FastCGI интерфейс. FastCGI Интерфейс определён http://www.fastcgi.com/ и это платформо-независимый и серверо-независимый интерфейс между web-приложением и web-сервером.

Это подразумевает что FastCGI программы запущенные с Apache web-сервером также запустятся с lighttpd и наоборот.

FastCGI

FastCGI ликвидирует множество ограничений CGI программ. Проблема CGI программ в том что они должны быть перезапущенны web-сервером при каждом запросе, что приводит к понижению производительности.

FastCGI убирает это ограничение сохраняя процесс запущенным и передавая запросы этому постоянно запущенному процессу. Это позволяет не тратить время на создание (fork()) новых процессов.

Пока CGI программы соединены с сервером через pipe'ы, FastCGI процессы используют Unix-Domain-Sockets или TCP/IP для связи с сервером. Это даёт следующее преимущество над обычными CGI программами: FastCGI программы могут быть запущенны не только на этом же сервере, но и где угодно в сети

lighttpd включает в себя внутренный FastCGI распределитель нагрузки который может использоваться для распределения сразу на несколько FastCGI серверов. В отличие от иных решений только FastCGI процесс должен находиться в кластере, а не целый web-сервер. Это позволяет использовать FastCGI процессу больше резурсов чем, например, load-balancer+apache+mod_php.

Если вы сравните FastCGI с apache+mod_php вы должны обратить внимание на то, что FastCGI обеспечивает дополнительную безопастность, как запуст FastCGI процесса под пользователем отличным от пользователя web-сервера, а также может находиться в chroot'е отличным от chroot'а web-сервера.

Опции

Поддержка FastCGI в lighttod предоставляется через модуль fastcgi (mod_fastcgi) который имеет 2 опции в конфигурационном файле:

fastcgi.debug
значение от 0 до 65535 для выставления уровня отладки в FastCGI модуле. На данный момент используются только 0 или 1. Выставите 1 чтобы включить отладку, и 0 чтобы выключить.
fastcgi.server

сообщает модулю куда надо отправлять FastCGI вызовы. каждое расширение файла может отправлять своему собственному FastCGI серверу. Балансировка нагрузки реализуется указанием нескольких FastCGI сервером для одного расширения.

структура fastcgi.server секции:

( <extension> => 
  ( <handle> => 
    ( "host" => <string> ,
      "port" => <integer> ,
      "socket" => <string>,       # socket
                                  # или пара host+port
      "bin-path" => <string>,     # ОПЦИОНАЛЬНО 
      "bin-environment" => <array>, # ОПЦИОНАЛЬНО 
      "bin-copy-environment" => <array>, # ОПЦИОНАЛЬНО 
      "mode" => <string>,         # ОПЦИОНАЛЬНО
      "docroot" => <string> ,     # ОПЦИОНАЛЬНО если "mode" 
                                  # не "authorizer"
      "check-local" => <string>,  # ОПЦИОНАЛЬНО
      "min-procs" => <integer>,   # ОПЦИОНАЛЬНО
      "max-procs" => <integer>,   # ОПЦИОНАЛЬНО
      "max-load-per-proc" => <integer>, # ОПЦИОНАЛЬНО
      "idle-timeout" => <integer> # ОПЦИОНАЛЬНО
    )
  ), 
  ( <handle> => ... 
  )
)
<extension>:расширение файла или префикс (если начинается с "/")
<handle>:уникальное имя для этого handle
"host":имя хоста/ip данного FastCGI процесса
"port":tcp-порт на "host" используемый FastCGI процессом
"bin-path":путь к локальному исполняемому файлу FastCGI который должен быть запущен если не используется отдельный FastCGI процесс
"socket":путь к unix-domain socket
"mode":режим FastCGI протокола. По умолчанию это "responder", также есть режим "authorizer" .
"docroot": это опционально, и это docroot (корневая директория для документов) на удалённом хосте для режима "responder" mode. Для режима "authorizer" это ОБЯЗАТЕЛЬНО и указывает на docroot для авторизованных запросов. По причинам безопастности рекомендуется держать этот docroot вне дерева каталога server.document-root.
"check-local":опционально и может быть включено "enable" (по умолчанию) или "disable". Если включено то сервер сначала проверяет файл в локальном server.document-root каталоге и возвращает 404 (Not Found) если такого файла нет. Если выключено, то сервер перенаправляет запрос к FastCGI интерфейсу без этой проверки.

Если указана bin-path:

"min-procs":выставляет минимальное количество процессов стартующих при запуске
"max-procs":верхний лимит запущенных процессов
"max-load-per-proc":
 максимальное количество ожидающих процессов до того как запуститься новый процесс
"idle-timeout":количество секунд после прошествия которых неиспользуемый процесс будет уничтожен
"bin-environment":
 помещает пременную окружения для запускаемого процесса
"bin-copy-environement":
 очищает переменные окружения и копирует только указанные переменные в новое окружение создаваемого процесса

Примеры

Множественные расширения для того же хоста

fastcgi.server = ( ".php" =>
                   ( "grisu" => 
                     ( 
                       "host" => "127.0.0.1",
                       "port" => 1026,
                       "bin-path" => "/usr/local/bin/php"
                     )
                   ),
                  ".php4" =>
                   ( "grisu" => 
                     ( 
                       "host" => "127.0.0.1",
                       "port" => 1026
                     )
                   )
                 )

Пример с префиксом:

fastcgi.server = ( "/remote_scripts" =>
                   ( "fcg" =>
                     (
                       "host" => "192.168.0.3",
                       "port" => 9000,
                     "check-local" => "disable",
                     "docroot" => "/" # удалённый сервер может использовать
                                        # свой docroot
                     )
                   )
                 )

Запрос http://my.host.com/remote_scripts/test.cgi будет перенаправлен на fastcgi сервер на 192.168.0.3 и значение "/remote_scripts/test.cgi" будет использовано для переменной SCRIPT_NAME. Удалённый сервер может обработать его со своим собственным document root. Обработка index файлов в данном случае также ложиться на удалённый сервер.

Пример для режима "authorizer":

fastcgi.server = ( "/remote_scripts" =>
                   ( "auth" =>
                     (
                       "host" => "10.0.0.2",
                       "port" => 9000,
                       "docroot" => "/path_to_private_docs",
                       "mode" => "authorizer"
                     )
                   )
                 )

Заметьте что если "docroot" определена, тогда её значение будет использовано в переменных DOCUMENT_ROOT и SCRIPT_FILENAME FastCGI сервера.

Распределение нагрузки

FastCGI plugin предоставляет автоматическое распределение нагрузки между несколькими FastCGI серверами.

fastcgi.server = ( ".php" => 
                   ( "server1" => 
                     ( "host" => "10.0.0.3",
                       "port" => 1030 ),
                     "server2" =>
                     ( "host" => "10.0.0.3",
                       "port" => 1030 )
                   )
                 )

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

proc: 127.0.0.1 1031  1 1 1 31454
proc: 127.0.0.1 1028  1 1 1 31442
proc: 127.0.0.1 1030  1 1 1 31449
proc: 127.0.0.1 1029  1 1 2 31447
proc: 127.0.0.1 1026  1 1 2 31438
got proc: 34 31454
release proc: 40 31438
proc: 127.0.0.1 1026  1 1 1 31438
proc: 127.0.0.1 1028  1 1 1 31442
proc: 127.0.0.1 1030  1 1 1 31449
proc: 127.0.0.1 1031  1 1 2 31454
proc: 127.0.0.1 1029  1 1 2 31447

Данный вывод показывает множество породений FastCGI на локальной машине. Следующее объяснение верно также и для удалённых соединений.

Вывод показывает:

Как вы можете видеть список всё время упорядочен по полю загрузуки

При запросе нового соеденения, выбирается первый указатель на FastCGI процесс (один с наименьшей нагрузкой), значение загрузки увеличивается на 1 (got proc: ...) и список сортируется вновь.

Если FastCGI запрос заканчивается или соединение обрывается, загрузка FastCGI proc уменьшается на 1 и список опять сортируется (release proc: ...)

Такое поведение занимает мало кода, весьма эффективно, и позволяет использовать fastcgi-сервера равнозагруженно, даже если они имеют разные CPU.

Adaptive Process Spawning

Начиная с 1.3.8 lighttpd может создавать просцессы по запросу если определена bin-path, и FastCGI процессы запускаются локально.

Если вы хотите иметь запущенным по крайней мере один FastCGI процесс и больше при запросах, вы можете использовать min-procs и max-procs.

Новый процесс запускается как только среднее количество запросов ожидающих обработку одним процессом превысит max-load-per-proc.

Параметр idle-timeout определяет как долго fastcgi-процесс должен ожидать новый запрос прежде чем завешит свою работу

Пример

fastcgi.server = ( ".php" => ( "localhost" => 
                   ( "socket" => "/tmp/php.socket",
                     "bin-path" => "/usr/local/bin/php",
                     "min-procs" => 1,
                     "max-procs" => 32,
                     "max-load-per-proc" => 4,
                     "idle-timeout" => 20 ) 
                   ) )

Отключение Adaptive Spawning

Adaptive Spawning всё ещё новая возможность, и может вести себя нестабильно. Здесь указаны несколько возможностей как контролировать создание новых процессов:

  1. "max-load-per-proc" => 1 если это работает у вас то всё хорошо.

  2. Если не выставлено min-procs == max-procs.

  3. Для PHP вы также можете использовать:

    $ PHP_FCGI_CHILDREN=384 ./lighttpd -f ./lighttpd.conf
    
    fastcgi.server = ( ".php" => ( "localhost" =>
                       ( "socket" => "/tmp/php.socket",
                         "bin-path" => "/usr/local/bin/php",
                         "min-procs" => 1,
                         "max-procs" => 1,
                         "max-load-per-proc" => 4,
                         "idle-timeout" => 20 )
                     ) )
    

    Это создаст один socket и позволит PHP самому создать 384 процесса.

  4. Если вы не хотите позволять lighttpd управлять fastcgi процессами, уберите bin-path и используйте spawn-fcgi чтобы FastCGI создавал процессы сам

FastCGI и Языки Программирования

Подготовка PHP как FastCGI программы

Одно из наиболее важных приложений которое имеет FastCGI интерфейс это php который может быть скачан с http://www.php.net/ . Вы должны перекомпилировать php из исходников чтобы активировать FastCGI интерфейс, т.к. он не включен по умолчанию.

Если у вас уже имеется работающий PHP на web-сервере, выполните короткий скрипт который просто содержит

<?php phpinfo(); ?>

и посмотрите на строки содержащие вызов configure. Вы можете использовать их как основы для компиляции.

Вы должны удалить опции --with-apxs, --with-apxs2 и те которые используются для компилирования с поддержкой Apache. Добавьте следующие три опции для компиляции PHP с поддержкой FastCGI:

$ ./configure \
  --enable-fastcgi \
  --enable-discard-path \
  --enable-force-cgi-redirect \
  ...

После компиляции и инстраляции проверьте что ваш PHP поддерживает FastCGI, выполнив:

$ php -v
PHP 4.3.3RC2-dev (cgi-fcgi) (built: Oct 19 2003 23:19:17)

Обратите внимание на (cgi-fcgi).

Настройка PHP

Важно чтобы php.ini содержал:

cgi.fix_pathinfo = 1

В противном случае PHP_SELF не примет правильное значение.

Запуск FastCGI-PHP

Начиная с версии 1.3.6 lighttpd может сам создавать FastCGI процессы если необходимо:

fastcgi.server = ( ".php" =>
                   ( "localhost" =>
                     ( "socket" => "/tmp/php-fastcgi.socket",
                       "bin-path" => "/usr/local/bin/php"
                     )
                   )
                 )

PHP предоставляет 2 специальные переменные окружения которые контролируют число рабочих запущенных процессов под контроллем одного наблюдательного процесса (PHP_FCGI_CHILDREN) и число запросов которые один рабочий процесс обработает до завершения.

fastcgi.server = ( ".php" =>
                   ( "localhost" =>
                     ( "socket" => "/tmp/php-fastcgi.socket",
                       "bin-path" => "/usr/local/bin/php",
                       "bin-environment" => ( 
                         "PHP_FCGI_CHILDREN" => "16",
                         "PHP_FCGI_MAX_REQUESTS" => "10000"
                       )
                     )
                   )
                 )

Чтобы улучшить безопастность запущенных процессов вы только должны передать необходимые переменные окружения FastCGI процессу.

fastcgi.server = ( ".php" =>
                   ( "localhost" =>
                     ( "socket" => "/tmp/php-fastcgi.socket",
                       "bin-path" => "/usr/local/bin/php",
                       "bin-environment" => ( 
                         "PHP_FCGI_CHILDREN" => "16",
                         "PHP_FCGI_MAX_REQUESTS" => "10000"
                       ),
                       "bin-copy-environment" => (
                         "PATH", "SHELL", "USER"
                       )
                     )
                   )
                 )

Внешний Spawning

Создание процесса FastCGI прямо в web-сервере имеет следующие недостатки

  • процесс FastCGI может быть запущен только локально
  • имеет тебе права что и web-сервер
  • имеет туже base-dir что и web-сервер

Как только вы начнёте использовать отдельный FastCGI сервер чтобы снять нагрузку с web-сервера, вы сможете контролировать процесс FastCGI внешними программами, такими как spawn-fcgi.

spawn-fcgi используется чтобы запустить FastCGI процесс в своём окружении, выставить ему user-id, group-id и сменить корневую директорию (chroot).

Для большего удобства должен быть использван wrapper скрипт берущий на себя заботу обо всех опциях. Такой скрипт включён в состав lighttpd,- spawn-php.sh.

Скрипт использует набор конфигурационных переменных на которые вы должны обратить внимание:

## АБСОЛЮТНЫЙ путь к исполняемому файлу spawn-fcgi
SPAWNFCGI="/usr/local/sbin/spawn-fcgi"

## АБСОЛЮТНЫЙ путь к исполняемому файлу PHP
FCGIPROGRAM="/usr/local/bin/php"

## bind к tcp-порту на localhost
FCGIPORT="1026"

## bind к unix domain socket
# FCGISOCKET="/tmp/php.sock"

## число запускаемых PHP потомков
PHP_FCGI_CHILDREN=10

## число запросов которые будет обрабатывать один php-процесс пока
## он не перезапуститься
PHP_FCGI_MAX_REQUESTS=1000

## IP адресса с которых PHP позволяет соединяться
FCGI_WEB_SERVER_ADDRS="127.0.0.1,192.168.0.1"

# разрешённые переменные окружения разделямые пробелами
ALLOWED_ENV="ORACLE_HOME PATH USER"

## если скрипт запущен root'ом, сменить на следующего владельца
USERID=wwwrun
GROUPID=wwwrun

Как только вы указали необходимые вам значения, вы можете запустить spawn-php.sh:

$ spawn-php.sh
spawn-fcgi.c.136: child spawned successfully: PID: 6925

Если вы видите "child spawned successfully: PID:" значит php процесс запущен успешно. Вы должны увидеть их в своём списке процессов:

$ ps ax | grep php
6925 ?        S      0:00 /usr/local/bin/php
6928 ?        S      0:00 /usr/local/bin/php
...

Число процессов должно быть PHP_FCGI_CHILDREN + 1. В данном случае процесс 6925 это master slave'ов работающих параллельно. Число рабочих процессов указывается в PHP_FCGI_CHILDREN. Рабочий процесс автоматически завершает свою работу после обработки PHP_FCGI_MAX_REQUESTS запросов, т.к. в PHP могут возникнуть утечки памяти.

Если вы запустите скрипт как пользователь root, php процессы будут работать с пользовательским USERID и GROUPID группы. В ином случае php процессы запустятся с правами того пользователя которые запустил скрипт.

Так как скрипт может быть запущен с неопределённого уровня запуска или даже напрямую с коммандной строки, он очищает переменные окружения прежде чем запустить процессы. ALLOWED_ENV содержит все внешние переменные окружения которые должны быть доступны php-процессам.

Perl

Для Perl вы должны установить FCGI модуль с CPAN.

TCL

Для TCL ...

Шаблон для удалённого авторизатора

Базовая функциональнось авторизатора (см. http://www.fastcgi.com/devkit/doc/fcgi-spec.html, 6.3 для подробностей).

#include <fcgi_stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main () {
  char* p;

  while (FCGI_Accept() >= 0) {   
    /* wait for fastcgi authorizer request */

    printf("Content-type: text/html\r\n");

    if ((p = getenv("QUERY_STRING")) == NULL) ||
         <QUERY_STRING is unauthorized>)
         printf("Status: 403 Forbidden\r\n\r\n");

    else printf("\r\n");  
      /* default Status is 200 - allow access */
  }

  return 0;
}

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

Проблемы

fastcgi.debug должен быть включен для решения проблем.

Если вы получаете:

(fcgi.c.274) connect delayed:  8
(fcgi.c.289) connect succeeded:  8
(fcgi.c.745) unexpected end-of-file (perhaps the fastcgi 
   process died):  8

процесс fastcgi принимает соединения но закрывает их в любом случае. Это случается если FCGI_WEB_SERVER_ADDRS не включает хост с которого происходит соединение.

Если вы получаете

(fcgi.c.274) connect delayed:  7
(fcgi.c.1107) error: unexpected close of fastcgi connection 
   for /peterp/seite1.php (no fastcgi process on host/port ?)
(fcgi.c.1015) emergency exit: fastcgi: connection-fd: 5 
   fcgi-fd: 7

процесс fastcgi не запущен на хосте/порту к которому вы соединяетесь. Проверьте вашу конфигурацию.

Если вы получаете

(fcgi.c.274) connect delayed:  7
(fcgi.c.289) connect succeeded:  7

всё нормально. Вызов connect() просто немного задержался, но соединениё установилось.