Платформы для запуска Perl веб-приложений

Версия от 21:24, 10 августа 2009; VitaliyFilippov (обсуждение | вклад) (Заключение)

Версия от 21:24, 10 августа 2009; VitaliyFilippov (обсуждение | вклад) (Заключение)

В статье рассматриваются платформы/библиотеки для запуска веб-приложений на языке Perl. Рассматриваются они именно как «серверы приложений» — в случае со standalone серверами не предъявляется требований гибкости обычного HTTP-сервера вроде nginx или Apache, безопасности, или производительности в обслуживании статических файлов, так как предполагается, что Perl-сервер будет всё равно «спрятан» за фронтенд наподобие nginx. Задача платформы — предоставить удобный программный интерфейс, такой, чтобы приложение могло получить запрос, предназначающийся ему, и сформировать на него адекватный ответ.

Все они делятся на две категории — библиотеки, дающие возможность запуска Standalone HTTP-сервера, и интерфейсы, требующие для работы внешнего HTTP-сервера.

Фреймворки для построения веб-приложений — всевозможные Mason, Catalyst и т. п. — не рассматриваются.

Do you want to try some new features? By joining the beta, you will get access to experimental features, at the risk of encountering bugs and issues.

Ок Нет, спасибо

Требующие внешнего HTTP-сервера

Сюда относятся, во-первых, все реализации, предназначенные для встраивания Perl-интерпретатора в тот или иной веб-сервер, а во-вторых, дополнительные протоколы взаимодействия HTTP-сервера с Perl-программами или демонами, такие, как CGI.

CGI

Старый добрый Common Gateway Interface (CGI — статья в русскоязычной Википедии), впервые реализованный где-то в районе 1993 года в первом HTTP-сервере NCSA HTTPd 1.0. Идея — максимально простой интерфейс взаимодействия с программой — через переменные окружения, а также стандартные ввод и вывод (STDIN и STDOUT). Веб-сервер при каждом запросе, который нужно обработать CGI приложением, запускает новый процесс (это самое приложение), передавая некоторые заголовки и данные в переменных окружения, а адрес запроса и содержимое (например, загружаемого на сервер файла) через стандартный ввод, после чего читает ответ (заголовки и содержимое) со стандартного вывода приложения.

Плюсы:

  1. Универсальность — скрипт, написанный с использованием CGI.pm, будет работать на любой платформе, так как CGI поддерживают все (почти все?).
  2. Простые вещи реализовать на CGI просто. Чем-то эта идеология похожа на идеологию Perl в целом — простые задачи он делает простыми, а сложные — возможными.
  3. Большое количество наработок, то есть, модулей для работы в CGI среде. В частности, к счастью разработчиков, есть модуль PCGI.pm, являющий собой вменяемую PHP-подобную реализацию CGI-протокола.
  4. Запуск нового интерпретатора, помимо минуса (1), означает и плюс — свободу от свойственных долгоживущим Perl демонам утечек памяти.

А вот универсальность CGI в смысле возможности использования произвольного языка программирования для нас ни плюсом, ни минусом, очевидно, не является, так как рассматривается именно Perl.

Минусы:

  1. Самый очевидный и повторяемый всеми аки мантра минус — слабая производительность из-за необходимости запуска нового процесса интерпретатора при каждом запросе. Причин две — во-первых, это постоянно создаваемые процессы, которые могут быть совсем не лёгкими — раз, и вообще ограничивать производительность ОС большим числом переключений при большой нагрузке; во-вторых, это постоянные компиляции и инициализации модулей, занимающие приличное время.
  2. Кривой и устаревший код многих модулей для работы с разными функциями CGI. Примеры:
    • CGI.pm написан без использования use strict. Это не страшно, но показатель раздолбайства авторов.
    • В районе года 2000-ного авторам CGI.pm вдруг взбрело в голову, что век амперсанда (&) в качестве разделителя параметров в строках запроса подошёл к концу, и что теперь все будут использовать вместо него точку с запятой (;). Соответственно поведение и разбора, и генерации URL изменилось — причём, если в случае с генерацией всё легко возвращается на свои места заданием опции -oldstyle_urls, то разбор URL неизменно разбивает их и по «&», и по «;», что влечёт за собой различные неприятные эффекты.
    • Многие модули CGI::xx исповедуют генерацию HTML кода без использования шаблонизаторов, обычными print()'ами, на содержимое которых повлиять без влезания в сами модули невозможно. XXI век на дворе, пора бы прекратить хотя бы это — ан нет, и в 2004, и даже в 2006 годах такие модули появлялись.
  3. Идеология CGI хоть чем-то и похожа на Перл, но фактически CGI больше рассчитан на простые задачи, нежели чем на сложные.
  4. Слабые возможности взаимодействия с HTTP-сервером — например, обычно в CGI даже не передаются все заголовки запроса. Кроме того, вывод ответа на STDOUT неудобен, если различные параметры задаются в различных местах программы.

FastCGI / SCGI

Идея FastCGI — ликвидировать недостатки CGI, сохранив интерфейс. Главный недостаток CGI — необходимость перезапуска приложения, поэтому его и ликвидировали в первую очередь: FastCGI-процесс обрабатывает не один запрос, а много — последовательно принимая их в цикле через Unix- или TCP-сокет. Таким образом, FastCGI-процессы, во-первых, могут быть запущены на другом физическом сервере, а во-вторых, становится возможно распределение запросов между несколькими процессами.

SCGI — практически это клон FastCGI, отличия лишь в формате передачи данных, который, как утверждают авторы, проще реализовать, но и возможностей у него меньше — например, STDERR не передаётся обратно HTTP-серверу. Поддерживается несколько менее широко: ngx_scgi_module, Apache, Lighttpd, Cherokee, Mathopd с неофициальным патчем.

Плюсы:

  1. Возможность запуска старых CGI-приложений в «ускоренном режиме» практически без дополнительных телодвижений. Хороший пример — awstats, представляющий из себя (внимание!) один CGI-скрипт на всё приложение, весом примерно 550 Кб. Один разбор такого скрипта занимает почти полсекунды… А под FastCGI это делается лишь однажды.
  2. Передача в скрипт любых HTTP-заголовков, какие душе угодны.
  3. Возможность самостоятельного управления пулом процессов или потоков.

Минусы:

  1. Склонность к утечкам памяти, особенно в случае использования большого числа устаревшего кода, рассчитанного на «умирание» скрипта после обработки каждого запроса в среде CGI.
  2. Взаимодействие по-прежнему ведётся через функции CGI, поэтому «интерфейсные» минусы CGI никуда не исчезают. Специальных обвязок для упрощения взаимодействия с сервером по протоколу FastCGI нет, кроме самой примитивной реализации — модуля FCGI. Идеология, видимо, такова — а зачем, раз и так есть CGI.pm и компания?

mod_perl

mod_perl — модуль HTTP-сервера Apache, предназначенный для веб-разработки внутри Apache на языке Perl. Существуют как версии для Apache 1.x, так и для 2.x. Внутреннее устройство mod_perl’а полностью повторяет C API апача.

Плюсы:

  1. Очень большая степень гибкости и возможности комбинирования с другими модулями Apache, в частности, засчёт наличия большого числа обработчиков разных стадий запроса. Гибкость означает, что можно не только просто отправлять ответ на запрос из своего Perl-модуля, но можно и осуществлять авторизацию или ещё что-нибудь. Любопытный пример использования: SVNPropCheck.
  2. Весьма богатый и достаточно удобный программный интерфейс, через который с Apache можно делать практически всё, что душа пожелает.
  3. Возможность с небольшими телодвижениями запускать CGI-приложения в среде mod_perl с помощью модуля Apache::Registry. Для примера использования доработанного модуля Registry можно посмотреть реализацию mod_perl.pl из Bugzilla 3.x.
  4. Модуль популярен. Есть множество наработок, многие из которых представляют собой весьма и весьма приятные продукты. Хороший пример — профилировщик NYTProf, разработанный именно для профилирования мод_перла.

Минусы:

  1. Чрезмерная завязка на внутреннее устройство веб-сервера Apache. Фактически — когда вы разрабатываете на mod_perl’е, вы разрабатываете полноценный модуль Apache. Аналогично — приложения, написанные под mod_perl, не запустятся больше нигде.
  2. В mod_perl2 для различных функций существует по нескольку где-то конкурирующих, где-то дополняющих друг друга, а где-то сходных по функционалу, но разных по интерфейсу библиотек. Частично это диктуется совместимостью с mod_perl1. Примеры — куки: Apache2::Cookie, APR::Request::Cookie, запрос: Apache2::RequestRec, Apache2::RequestUtil, Apache2::Request, APR::Request. Пока я допёр, что для того, чтобы получить в пользование $r->dir_config(), нужно сделать use Apache2::RequestUtil Это не так страшно, но некоторую путаницу всё-таки вносит.
  3. Склонность к утечкам памяти. mod_perl течёт всегда, хоть ты его режь. Решение, правда, тоже несложное — MaxRequestsPerChild.
  4. Серьёзное увеличение размеров детёнышей процесса Apache.
  5. Проблемы с перезагрузкой модулей в процессе обслуживания без перезапуска сервера. Оговорка: это проблема Perl’а в целом, не только mod_perl’а. Но по крайней мере можно было бы предусмотреть быстрый «сброс» интерпретаторов по сигналу.
  6. В «многопользовательской» среде, точнее, в среде с множеством различных веб-приложений, мод_перл создаёт проблемы по причине отсутствия изоляции загруженного кода модулей между приложениями. Решение для этого — PerlOptions +Parent, но оно подходит только для случая с небольшим количеством приложений, так как в противном случае детёныши Apache вырастают до неприличных размеров по причине работы в них нескольких пулов Perl-интерпретаторов вместо одного.
  7. Время от времени в mod_perl всплывают совершенно неуловимые глюки, особенно в необычных режимах вроде taint, и при использовании с некоторыми модулями или движками Apache. «Потому что Perl и mod_perl — это как бэ немного разные языки» (c). Например:
    • При использовании mpm_itk, 2-го мод_перла и PerlOptions +Parent (дающей отдельный пул интерпретаторов виртхосту) глобальные переменные в пакетах (как my, так и our) перестают сохранять свои значения между запросами.
    • При включённом taint mode и тоже в отдельном интерпретаторе, в составе Bugzilla 3.x проявляется следующий мистический баг:
    На входе строки $oldstr и $newstr, обе не taint’ченные, в $newstr есть запятые, в $oldstr нет. Пишем два идентичных по семантике фрагмента кода:
    • Если написать $oldstr =~ s/[,\s]+/ /g; $newstr =~ s/[,\s]+/ /g;, то $newstr почему-то становится tainted.
    • Если же написать s/[,\s]+/ /g for $oldstr, $newstr;, то обе, как и положено, остаются не tainted.
    Баг воспроизводится только в составе Bugzilla и только под мод_перлом, из контекста выдернуть его не получается.
  8. Некоторые затрудения в автоматизированных отладке и профилировании приложений в среде mod_perl из-за неочевидности программ исполнения. Автоматизированные инструменты эта неочевидность «смущает».
  9. Отсутствие mod_perl на подавляющем большинстве веб-хостингов. Потому что для админов серверов с кучами хомячков это — головная боль, в многопользовательской среде влекущая проблемы как с безопасностью, так и с надёжностью и производительностью. Всё по причине уже описанных минусов.
    Как вы думаете, почему так широко распространился язык PHP? Именно по причине простоты обслуживания.

ngx_http_perl_module

Идея — вызов Perl-кода из nginx.

Ссылка на документацию: http://sysoev.ru/nginx/docs/http/ngx_http_perl_module.html.

Плюсы:

  • Возможность вызова Perl-кода из SSI, что делает nginx почти шаблонизатором.
  • Простота, благодаря которой количество источников мистических ошибок, а также неуловимых утечек памяти сводится почти к нулю.

Минусы:

  • Идея размещения приложения внутри nginx несколько лишает его легковесности. Также появляются некоторые проблемы с масштабируемостью в случае простоев (ожиданий ответов от СУБД и т. п.).
  • Жёсткая завязка на использование внутри nginx. Больше нигде приложения, написанные под nginx, не заработают.
  • Поразительное качество: невозможно получить список всех HTTP-заголовков, присутствующих в запросе, а можно лишь считывать их по одному. Остаётся только применять патч к src/http/modules/perl/nginx.xs.
  • Отсутствие поддержки CGI среды внутри ngx_http_perl_module, наподобие mod_perl апача.
  • Версии ngx_http_perl_module до 0.6.22 имели следующие особенности:
Значения, возвращаемые методами объекта запроса $r, во-первых, хранились в памяти, выделяемой не perl’ом, а nginx’ом из собственных пулов, что в большинстве случаев позволяло уменьшить число операций копирования, а во-вторых, не завершались нулевым байтом. В некоторых ситуациях это приводит к ошибкам, например, при попытке использования таких значений в численном контексте, или использования незавершённых нулём строк в именах файлов и тому подобном:
(FreeBSD)
nginx in realloc(): warning: pointer to wrong page
Out of memory!
Callback called exit.
(Linux)
*** glibc detected *** realloc(): invalid pointer: ... ***
Out of memory!
Callback called exit.
Обход этих особенностей простой — нужно просто скопировать возвращённое значение в скаляр. Например,
my $i = $r->variable('counter') + 1;
заменить на
my $i = $r->variable('counter'); $i++;

Standalone

Альтернативой встраиванию интерпретатора в процесс HTTP-сервера является запуск собственного HTTP-сервера, рассчитанного на расположение за обратным прокси (reverse proxy) — mod_proxy Апача, nginx'ом, или Squid'ом — в качестве фронтенда. Так делают, например, Jetty (Java) и Zope (Python). Это работает точно так же, как обработка запросов внутри сервера, только запросы отправляются другому HTTP-серверу.

У такого подхода — использования HTTP вместо встраивания интерпретатора в сервер, или вариаций на тему CGI — есть несколько приятных преимуществ. Фронтенд может балансировать и распределять по нескольким серверам нагрузку одновременно с передачей запроса, причём для этого существует множество готовых качественных инструментов. Работа администраторов упрощается, потому что конфигурация фронтенда для передачи запроса разным приложениям идентична, а сами приложения могут быть запущены под любым системным пользователем, на другом сервере, в jail’е, виртуальной машине, за аппаратным firewall’ом или внутри какой-нибудь другой системы безопасности. А при отладке разработчик может взаимодействовать напрямую со своим приложением без необходимости запускать отдельный HTTP-сервер.

LWP (HTTP::Daemon)

LWP (libwww-perl) — библиотека для создания как клиентов, так и серверов, полностью совместимых со спецификацией HTTP/1.1, на чистом Perl’е.

В качестве единственной готовой, пусть и исключительно простой, платформы, исповедующей идеологию получения запросов в форме HTTP::Request и ответа HTTP::Response’ами можно рассмотреть HTTP::Server::Brick, построенный на основе HTTP::Daemon.

Плюсы:

Минусы:

  • Главный минус в том, что LWP в качестве основы HTTP-сервера не использует, видимо, практически никто. Посему, сколь логичным бы ни выглядел сервер, получающий на вход HTTP::Request и отвечающий HTTP::Response’ами, реально готовых наработок для такого подхода фактически нет. Например, нет ничего похожего на удобный Apache2::Request, с готовыми функциями для доступа к запросу, разобранному на URI, параметры POST, заголовки и куки.

HTTP::Server::Simple

HTTP::Server::Simple — простая реализация HTTP-сервера на Perl.

Плюсы:

  • Существует некоторое количество модулей для расширения данного сервера — в частности, для запуска приложений на некоторых фреймворках через HTTP::Server::Simple, например, для Mason.
  • Модуль достаточно широко используется при тестировании веб-приложений, в качестве легковесного тестового сервера: Test::HTTP::Server::Simple.
  • Можно использовать стандартные модули Net::Server::xx для выбора поведения сервера — например, можно использовать как TCP, как и UNIX сокеты, можно создать prefork (Net::Server::PreFork или Net::Server::PreForkSimple) или мультиплексирующий однопоточный сервер (Net::Server::Multiplex).

Минусы:

  • Интерфейс — снова CGI с аналогичными FastCGI и SCGI интерфейсными минусами.

Заключение

С точки зрения веб-разработчика, не принимающего участие в разработке основного «ядра» системы (демона и/или обработчиков), всё вышеописанное сводится к следующим различным интерфейсам:

Если стремиться к наиболее «красивому» и логичному интерфейсу, то:

Всё, что существует — отстой. С моей точки зрения, по идеологии наиболее близок к идеалу подход LWP и HTTP::Server::Brick — на входе объект «запрос», на выходе объект «ответ». Однако этот подход совсем не популярен, поэтому используя его, вы обрекаете себя на разработку и поддержку своей реализации — никто не говорит, что это плохо, но… не mainstream.

Если хочется простоты в стиле PHP, то:

Можно обратиться к PCGI.

А если выбирать наиболее универсальный и «mainstream» (широко поддерживаемый) интерфейс, то:

Это, конечно же, CGI. Что и объясняет популярность FastCGI — эквивалентного наиболее простому интерфейсу, обёрнутому в «ускоритель». Если хочется — использовать HTTP::Server::Simple тоже можно, но только осторожно :).

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