Изменения

Перейти к: навигация, поиск

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

1700 байтов убрано, 14:08, 8 октября 2014
м
Нет описания правки
[[Изображение:Perl.appservers.benchmark.png|right|thumb|Тест производительности некоторых серверов]] В статье рассматриваются платформы/библиотеки для запуска веб-приложений на языке [[Perl]]. Рассматриваются они именно как «серверы приложений» — в случае со standalone серверами не предъявляется требований гибкости обычного HTTP-сервера вроде [http://nginx.ru nginx] или [http://httpd.apache.org/ Apache], безопасности, или производительности в обслуживании статических файлов, так как предполагается, что Perl-сервер будет всё равно «спрятан» за фронтенд наподобие nginx. Задача платформы — предоставить удобный программный интерфейс, такой, чтобы приложение могло получить запрос, предназначающийся ему, и сформировать на него адекватный ответ.
Все они делятся на две категории — библиотеки, дающие возможность запуска Standalone HTTP-сервера, и интерфейсы, требующие для работы внешнего HTTP-сервера.
Сюда относятся, во-первых, все реализации, предназначенные для встраивания Perl-интерпретатора в тот или иной веб-сервер, а во-вторых, дополнительные протоколы взаимодействия HTTP-сервера с Perl-программами или демонами, такие, как CGI.
=== CGI , PCGI ===
Старый добрый Common Gateway Interface ([[rupedia:CGI|CGI]] — статья в русскоязычной Википедии), впервые реализованный где-то в районе 1993 года в первом HTTP-сервере [[rupedia:NCSA_HTTPd|NCSA HTTPd]] 1.0. Идея — максимально простой интерфейс взаимодействия с программой — через переменные окружения, а также стандартные ввод и вывод (STDIN и STDOUT). Веб-сервер при каждом запросе, который нужно обработать CGI приложением, запускает новый процесс (это самое приложение), передавая некоторые заголовки и данные в переменных окружения, а адрес запроса и содержимое (например, загружаемого на сервер файла) через стандартный ввод, после чего читает ответ (заголовки и содержимое) со стандартного вывода приложения.
Плюсы:
# Универсальность — скрипт, написанный с использованием {{CPAN|запуск CGI|CGI.pm}}, будет работать на любой платформе, так как CGI поддерживают все (скриптов поддерживается почти все?).# Простые вещи реализовать на CGI просто. Чем-то эта идеология похожа на идеологию Perl в целом — простые задачи он делает простыми, а сложные — возможными.# Большое количество наработок, то есть, модулей для работы в CGI среде. В частности, к счастью разработчиков, есть модуль {{CPAN|PCGI|PCGI.pm}}, являющий собой вменяемую PHP-подобную реализацию CGI-протоколавезде.
# Запуск нового интерпретатора, помимо минуса (1), означает и плюс — свободу от свойственных долгоживущим Perl демонам утечек памяти.
А вот универсальность Универсальность CGI в смысле возможности использования произвольного языка программирования для нас ни плюсом, ни минусом, очевидно, не является, так как рассматривается именно Perl.
МинусыНа этом плюсы заканчиваются и начинаются сплошные минусы:
# Самый очевидный и повторяемый всеми аки мантра минус — слабая производительность из-за необходимости запуска нового процесса интерпретатора при каждом запросе. Причин две — во-первых, это постоянно создаваемые процессы, которые могут быть совсем не лёгкими — раз, и вообще ограничивать производительность ОС большим числом переключений при большой нагрузке; во-вторых, это постоянные компиляции и инициализации модулей, занимающие приличное время.
# Кривой Второй наиболее неприятный минус — очень неудобный API наиболее стандартного модуля {{CPAN|CGI|CGI.pm}}. Например:#* Нельзя нормально получить хеш со всеми параметрами, так как CGI->Vars работает криво: если в запросе передано несколько значений одного параметра (&a=1&a=2), то в Vars значением этого параметра будет конкатенация всех этих значений (то есть { a => '12' }). Хотя по логике вещей там должен быть массив [ '1', '2' ].#* CGI->cookie() не дружит с UTF-ными ключами.#* Следить за своевременным выводом заголовков и устаревший код многих модулей для работы текста ответа нужно самому.#* У различных функцих весьма странные интерфейсы — с разными функциями ключами, начинающимися на «-» и так далее.#* В CGIчасто даже передаются не все заголовки запроса.# Говнокод. Примеры:#* На код самого CGI.pm написан без использования страшно смотреть. В принципе, уже одно отсутствие <code>use strict</code>. Это не страшно, но показатель раздолбайства говорит о раздолбайстве авторов.#* В районе года 2000-ного авторам CGI.pm вдруг взбрело в голову, что век амперсанда (&) в качестве разделителя параметров в строках запроса подошёл к концу, и что теперь все будут использовать вместо него точку с запятой (;). Соответственно поведение и разбора, и генерации URL изменилось — причём, если в случае с генерацией всё легко возвращается на свои места заданием опции -oldstyle_urls, то разбор URL неизменно разбивает их и по «&», и по «;», что влечёт за собой различные неприятные эффекты.#* Многие модули Большинство модулей типа CGI::''xxчто-нибудь'' исповедуют  — дикая древность, исповедующая генерацию HTML кода без использования шаблонизаторов, обычными print()'ами, на содержимое которых повлиять без влезания в сами модули невозможно. ''XXI век на дворе, пора бы прекратить хотя бы это'' — ан нет, и в 2004, и даже в 2006 годах такие модули появлялись.# Идеология CGI хоть чем-то и похожа на Перл{{CPAN|PCGI}} — те же яйца, но фактически CGI больше рассчитан на простые задачитолько в профиль, нежели чем на сложные.# Слабые возможности взаимодействия чуть более аккуратно написанные и с HTTP-сервером — например, обычно в CGI даже не передаются все заголовки запросазащитой от слишком больших запросов. Кроме того, вывод ответа на STDOUT неудобен, если различные параметры задаются в различных местах программыВсе интерфейсные минусы сохраняются.
=== FastCGI / SCGI ===
Идея [[rupedia:FastCGI|FastCGI]] — ликвидировать недостатки тормоза CGI, сохранив интерфейс. Главный недостаток CGI — необходимость перезапуска приложения, поэтому его и ликвидировали в первую очередь: FastCGI-процесс обрабатывает не один запрос, а много — последовательно принимая их в цикле через Unix- или TCP-сокет. Таким образом, FastCGI-процессы, во-первых, могут быть запущены на другом физическом сервере, а во-вторых, становится возможно распределение запросов между несколькими долгоживущими процессами.
[[rupedia:SCGI|SCGI]] — практически это фактически клон FastCGI, отличия лишь в формате передачи данных, который, как утверждают авторы, проще реализовать, но и возможностей у него меньше — например, STDERR не передаётся обратно HTTP-серверу. Поддерживается несколько чуть менее широко: [http://wiki.nginx.org/NginxNgxSCGIModule ngx_scgi_module], Apache, [http://redmine.lighttpd.net/wiki/lighttpd/Docs:ModSCGI Lighttpd], [http://www.cherokee-project.com/ Cherokee], Mathopd с неофициальным [http://www.mail-archive.com/mathopd@mathopd.org/msg00369.html патчем].
Плюсы:
# Передача в скрипт любых HTTP-заголовков, какие душе угодны.
# Возможность самостоятельного управления пулом процессов или потоков.
# Возможность запуска FastCGI-скриптов на серверах, поддерживающих только CGI — в этом случае модуль {{CPAN|FCGI}} обрабатывает 1 запрос и выходит.
Минусы:
# Склонность к утечкам памяти, особенно в случае использования большого числа устаревшего кода, рассчитанного на «умирание» скрипта после обработки каждого запроса в среде CGI.
# Взаимодействие по-прежнему ведётся через функции CGI, поэтому «интерфейсные» минусы CGI никуда не исчезают. Специальных обвязок для упрощения взаимодействия с сервером по протоколу FastCGI нет, кроме самой примитивной реализации — модуля {{CPAN|FCGI}}. ''Идеология, видимо, такова — а зачем, раз и так есть CGI.pm и компания?''
=== mod_perl ===
[http://perl.apache.org/ mod_perl] — модуль HTTP-сервера [http://httpd.apache.org/ Apache], предназначенный для веб-разработки внутри модулей Apache на языке Perl. Существуют как версии для Apache 1.x«Модулей» — потому, так и для 2.x. Внутреннее устройство что API mod_perl’а почти полностью повторяет C C’шное API апача, соответственно, . Поэтому же API сильно различается в 1-ой и 2-ой версиях. Хотя какая разница, всё равно про httpd 1.x и mod_perl 1.x уже никто не помнит.
Плюсы:
# Очень большая Большая степень гибкости и возможности комбинирования с другими модулями Apache, в частности, засчёт наличия большого числа обработчиков разных стадий запроса. Гибкость означает, что можно не только просто отправлять ответ на запрос из своего Своим Perl-модуля, но модулем можно и осуществлять не всю обработку запроса, а, например, только авторизацию или ещё что-нибудь. Любопытный пример использования: [{{SVN|vitaphoto/head/lib-sway/SVNPropCheck.pm|markup}} SVNPropCheck].# Весьма богатый и достаточно довольно удобный программный интерфейс, через который с Apache можно делать практически всё, что душа пожелает.# Возможность с небольшими телодвижениями запускать CGI-приложения в среде mod_perl с помощью модуля {{CPAN|Apache::Registry}}. Для примера использования доработанного модуля Registry можно посмотреть реализацию [http://mxr.mozilla.org/bugzilla/source/mod_perl.pl mod_perl.pl] из Bugzilla 3.x.# Модуль популярен. Есть множество наработок, многие из которых представляют собой весьма и весьма приятные продукты. Хороший пример — профилировщик {{CPAN|NYTProf}}, разработанный именно для профилирования мод_перла.
Минусы:
# Чрезмерная завязка на внутреннее устройство веб-сервера [http://httpd.apache.org/ Apache]. Фактически — когда вы разрабатываете на На mod_perl’е, вы разрабатываете ''полноценный модуль Apache''. Аналогично — приложенияПриложения, написанные под mod_perl, не запустятся больше нигде.# В mod_perl2 для различных функций существует по нескольку где-то конкурирующих, где-то дополняющих друг друга, а где-то сходных по функционалу, но разных по интерфейсу библиотек. Частично это диктуется совместимостью с mod_perl1. ''Примеры — куки: Apache2::Cookie, APR::Request::Cookie, запрос: Apache2::RequestRec, Apache2::RequestUtil, Apache2::Request, APR::Request. Пока я в первый раз допёр, что для того, чтобы получить в пользование <code>$r->dir_config()</code>, нужно сделать <code>use Apache2::RequestUtil</code>…'' Это не так страшно, но некоторую путаницу всё-таки вносит.# Склонность к утечкам памяти. ''mod_perl течёт всегда, хоть ты его режь.'' Обходной манёвр, правда, несложен — Более-менее решается MaxRequestsPerChild.# Серьёзное увеличение размеров детёнышей процесса Apache.# Проблемы с перезагрузкой модулей в процессе обслуживания без перезапуска сервера. ''ОговоркаApache2:: это проблема Perl’а в целом, не только mod_perl’аReload — кривой; перезагрузку можно реализовать ручками (см. Но под mod_perl она проявляется сильнееhttps: //github.com/vitalif/bugzilla4intranet), но и такой вариант всё равно иногда при перезагрузке модулей ему прямо-таки «сносит крышу»срывает апачу крышу, он забывает про все константы и валит Internal Server Error на все запросыдо ближайшего рестарта. Ну и второе — авторы mod_perl могли бы и предусмотреть быстрый «сброс» интерпретаторов по сигналу.''# В «многопользовательской» среде, точнееБольшое потребление памяти, ибо Perl сосуществует в среде одних и тех же процессах с множеством различных веб-остальными модулями Apache.# Если нужно запустить несколько разных приложенийили несколько экземпляров одного приложения, мод_перл mod_perl создаёт проблемы по причине отсутствия изоляции загруженного кода модулей между приложениями. Решение для этого — Решается это PerlOptions +Parent (начиная с mod_perl2в mod_perl 1.x не решалось), но оно подходит только для случая с небольшим количеством приложений, так как в противном этом случае детёныши дочерние процессы Apache вырастают до совсем неприличных размеров по причине работы в них каждом нескольких пулов Perl-интерпретаторов вместо одного. Пул процессов-то общий.# Время от времени в mod_perl всплывают совершенно удивительные и неуловимые глюки, особенно в необычных режимах вроде [http://perldoc.perl.org/perlsec.html taint], и или при использовании с некоторыми модулями или движками Apache. ''«Потому что Perl и mod_perl — это как бэ немного разные языки»'' (c). Например:
#* При использовании [http://mpm-itk.sesse.net/ mpm_itk], 2-го мод_перла и PerlOptions +Parent (дающей отдельный пул интерпретаторов виртхосту) глобальные переменные в пакетах (как my, так и our) перестают сохранять свои значения между запросами.
#* При включённом taint mode и тоже в отдельном интерпретаторе, в составе [http://www.bugzilla.org/ Bugzilla] 3.x проявляется следующий мистический баг:
#: На входе строки <code>$oldstr</code> и <code>$newstr</code>, обе не taint’ченные, в <code>$newstr</code> есть запятые, в <code>$oldstr</code> нет. Пишем два идентичных по семантике фрагмента кода:
#:* Если написать <code>$oldstr =~ s/[,\s]+/ /g; $newstr =~ s/[,\s]+/ /g;</code>, то <code>$newstr</code> почему-то становится tainted.
#* Ещё пример в taint mode из Багзиллы:
#: Где-то в глубине души модифицированного модуля <code>Bugzilla::Field::Choice</code> конкатенируются <code>Bugzilla::Product->DB_COLUMNS->[0]</code> и <code>Bugzilla::Product->DB_TABLE</code>. При проверке {{CPAN|Scalar::Util}}::tainted в момент конкатенации сами по себе они оба безгрешны, однако, грех (флаг tainted) зарождается при их конкатенации. ''Вот и как это понимать? Адам и Ева сами по себе были безгрешны, но соединившись?..''
# Некоторые затрудения в ''автоматизированных'' отладке и профилировании приложений в среде mod_perl из-за неочевидности программ исполнениякоманд запуска. Автоматизированные Многие инструменты эта неочевидность «смущает».
# Отсутствие mod_perl на подавляющем большинстве веб-хостингов. Потому что для админов серверов с кучами хомячков это — головная боль, в многопользовательской среде влекущая проблемы как с безопасностью, так и с надёжностью и производительностью. Всё по причине уже описанных минусов.
#: ''Как вы думаете, почему так широко распространился язык [[rupedia:PHP|PHP]]? Именно по причине простоты обслуживания.''
=== <s>ngx_http_perl_module </s> ===
Идея — вызов Perl-кода из [http://nginx.ru/ nginx]. Скорее мёртв, чем жив.
Ссылка на документацию: [http://sysoev.ru/nginx/docs/http/ngx_http_perl_module.html http://sysoev.ru/nginx/docs/http/ngx_http_perl_module.html].
* Поразительное качество: невозможно получить список ''всех'' HTTP-заголовков, присутствующих в запросе, а можно лишь считывать их по одному. Остаётся только применять [{{SVN|vitalif/trunk/scripts/nginx.xs.diff}} патч к src/http/modules/perl/nginx.xs].
* Отсутствие поддержки CGI среды внутри ngx_http_perl_module, наподобие mod_perl апача.
* Версии ngx_http_perl_module до 0=== {{CPAN|PSGI}}/{{CPAN|Plack}} === Можно смело сказать, что {{CPAN|PSGI}}/[http://plackperl.6org Plack] — самый современный и удобный интерфейс для Perl веб-приложений.22 имели следующие особенности: PSGI — Perl-реализация питонячьего [http: Значения//wsgi.readthedocs.org/ WSGI. «Низкоуровневый» интерфейс взаимодействия тривиален:* Приложение = функция.* На входе 1 хешреф «окружения», возвращаемые методами объекта запроса $rв который включаются, во-первых, хранились в памятипараметры, выделяемой не perl’ом, а nginx’ом из собственных пулов, что аналогичные %ENV в большинстве случаев позволяло уменьшить число операций копированияCGI (REQUEST_URI, а PATH_INFO и т. п.)…* …а во-вторых, не завершались нулевым байтом. В некоторых ситуациях это приводит к ошибкамнекоторые PSGI-специфичные ключи, например, при попытке использования таких значений в численном контексте«input», или использования незавершённых нулём строк в именах файлов и тому подобном:содержащий входной поток.* На выходе массив из 3-х элементов: (FreeBSD):: <codeчисленный статус ответа, массив заголовков в виде [ Header =>nginx in realloc(): warning: pointer to wrong page</codeValue, Header =>Value, … ] и текст ответа. Однако без дополнительных обёрток такой интерфейс неудобен, поэтому есть вполне себе удобный {{CPAN|Plack}}, имеющий вполне вменяемый интерфейс — например, Plack:: <codeRequest->Out of memory!</code>parameters возвращает в виде хеша все GET и POST параметры, Plack:: <codeRequest->Callback called exitcookies — хеш кукисов и т.</code>:  п. Это — в отличие от модулей типа CGI и PCGI, в которых параметры и куки нужно читать по отдельности через param(Linux)и cookie(). Плюсы:: <code>*Грамотный интерфейс без глобальных переменных.** glibc detected *** reallocСовместимость с кучей разных серверов: можно запускать хоть под CGI/FastCGI (с помощью Plack), хоть в Apache ([http: invalid pointer//github.com/spiritloose/mod_psgi/ mod_psgi]), хоть в [https: //uwsgi-docs.readthedocs.org/ uWSGI], хоть в виде standalone демона (Starman, Twiggy, Corona и т. ***</code>п, полный список на http:: <code>Out of memory!</code>:: <code>Callback called exit.</code>plackperl.org). Причём, standalone-реализации по отзывам вполне производительны. Минусы: Обход этих особенностей простой — нужно * Разве что отсутствие совместимости со «старыми» приложениями — то есть, нельзя просто скопировать возвращённое значение обернуть CGI скрипт в скалярфункцию и превратить его таким образом в PSGI. Например, <source lang="perl">my $i = $r->variable* ('counter'Не проверено) + 1;</source> заменить на <source lang="perl">my $i = $r->variable('counter'); $i++;</source>возможно, в PSGI серверах отсутствует поддержка перезагрузки модулей.* В остальном, похоже, всё хорошо.
== Standalone ==
Альтернативой встраиванию интерпретатора в процесс HTTP-сервера является запуск собственного HTTP-сервера, рассчитанного на расположение за обратным прокси (reverse proxy) — [http://httpd.apache.org/docs/2.0/mod/mod_proxy.html mod_proxy] Апача, [http://nginx.ru/ nginx]'ом, или [http://www.squid-cache.org/ Squid]'ом — в качестве фронтенда. Так делают, например, [http://www.mortbay.org/ Jetty] (Java) и [http://www.zope.org/ Zope] (Python). Это работает точно так же, как обработка запросов внутри сервера, только запросы отправляются другому HTTP-серверу.
У такого подхода — использования HTTP вместо встраивания интерпретатора в сервер, или вариаций на тему CGI — есть несколько приятных преимуществ. Фронтенд может балансировать и распределять по нескольким серверам нагрузку одновременно с передачей запроса, причём для этого существует множество готовых качественных инструментов. Работа администраторов упрощается, потому что конфигурация фронтенда для передачи запроса разным приложениям идентична, а сами приложения могут быть запущены под любым системным пользователем, на другом сервере, в jail’е, виртуальной машине, за аппаратным firewall’ом или внутри какой-нибудь другой системы безопасности. А при При отладке разработчик может взаимодействовать напрямую со своим приложением без необходимости запускать отдельный HTTP-сервер. Потребление памяти сокращается засчёт изоляции каждого придожения в своём наборе процессов.
Кроме того, в этом случае приложение свободно от накладных расходов веб-сервера — для того же mod_perl’а генерация страниц без кэширования (но и без особых изысканий) за 1.5 мс почти «фантастика».
Плюсы:
* Интерфейс — CGI. В режиме '''nph''' — «Non-Parsed Headers» — то есть на STDOUT нужно выводить просто HTTP-ответ. Это устраняет по сути все интерфейсные ограничения часть интерфейсных ограничений CGI и сохраняет , сохраняя совместимость с CGI скриптами.
* Можно использовать стандартные модули Net::Server::'''xx''' для выбора поведения сервера — например, можно использовать как TCP, как и UNIX сокеты, можно создать prefork ({{CPAN|Net::Server::PreFork}} или {{CPAN|Net::Server::PreForkSimple}}) или мультиплексирующий однопоточный сервер ({{CPAN|Net::Server::Multiplex}}).
* Существует некоторое количество модулей для расширения данного сервера — в частности, для запуска приложений на некоторых фреймворках через HTTP::Server::Simple, например, для {{CPAN|Mason}}.
* Интерфейс CGI всё-таки требует по крайней мере выбора нужных модулей для разбора запросов и объединения всего этого функционала в своём приложении.
* Странная реализация parse_request() и parse_headers(), по меньшей мере, вплоть до версии 0.40. Лечится просто — (можно полечить переопределением соответствующей функции, но это ведь тоже лишние действия! :).
** Читает из стандартного ввода запрос и заголовки она по 1 символу функцией [http://perldoc.perl.org/functions/sysread.html sysread()], что весьма негативно сказывается на производительности.
** В качестве разделителя строк, согласно всем стандартам, при обмене данными по сети, должно выступать сочетание CR-LF в платформо-независимом варианте: «\015\012». Тем не менее, функции HTTP::Server::Simple используют просто «\n», что тоже работает, но не является идеально переносимым вариантом.
 
=== Plack ===
== Заключение ==
С точки зрения веб-разработчика, не принимающего участие в разработке основного «ядра» системы (демона и/или обработчиков), всё вышеописанное сводится к следующим различным интерфейсам: [[Изображение:Perl.appservers.benchmark.png‎|thumb|Тест производительности некоторых серверов]] * [http://perl.apache.org/docs/1.0/ mod_perl 1.x]* «Финалистами» являются [http://perl.apache.org/docs/2.0/ mod_perl 2.x]* , {{CPAN|CGI}}* {{CPAN|PCGI}}* {{CPAN|HTTP::Request}} + {{CPAN|HTTP::Response}} '''Если стремиться к наиболее «красивому» и логичному интерфейсу, то:''' Всё существующее фигня.) С моей точки зрения, по идеологии наиболее близка к идеалу идея LWP — на входе объект «запрос», на выходе объект «ответ». Но этот подход не очень популярен, поэтому, используя его, вы обрекаете себя на разработку и поддержку своей реализации — никто не говорит, что это плохо, но… не mainstream. Данный подход был выбран личной мной в платформе [[Sway Solstice]]. Исходные коды можно увидеть [http://svn.yourcmc.ru/viewvc.py/vitaphoto/branch/solstice/lib-sway/HTTP/ здесь] — они включают в себя реализацию HTTP::Request::Incoming — подкласса HTTP::Request с некоторыми удобными функциями разбора запроса, и абстракции HTTP::Request для mod_perl 1 и 2, CGI и nginx. Я использую этот подход в сочетании с {{CPAN|HTTP::Server::SimplePlack}} и считаю, что это весьма удобно, шустро и жизнеспособно'''Если хочется простоты в стиле PHP, то:''' Можно обратиться к {{CPAN|PCGI}}Остальное распространено слабо.
'''А если выбирать Победитель — однозначно {{CPAN|Plack}}. Он наиболее универсальный и «mainstream» (широко поддерживаемый) интерфейссовременный, то:'''простой и продуманный.
ЭтоCGI — всё ещё жив, конечно же, '''CGI'''но безнадёжно устарел. Что и объясняет популярность FastCGI — эквивалентного наиболее простому интерфейсуСтарые приложения, обёрнутому на нём написанные, нужно превращать либо в FastCGI, либо в «ускоритель». Если хочется — использовать HTTP::Server::Simple тоже можно — он хоть (последнее достаточно удобно и простой, но очень удобныйсделано в https://github. Отдавать с помощью него же статику я, правда, не стал… :com/vitalif/bugzilla-4intranet).
Также следует помнить об очень высокой популярности mod_perl. Ни mod_perl — тоже популярен, хотя ни универсальностью, ни надёжностью от него не пахнет, это нужно понимать, но всё-таки, во-первых, он обладает беспрецедентной гибкостью в обработке запросов — такой больше нет ни на одной платформе, а во-вторых, поддерживается он широко и «рецептов» существует множество. Но всё равно нужно помнить, что «Perl и mod_perl — это разные языки».
[[Категория:Sway]]
[[Категория:Разработка]]
[[Категория:Perl]]

Навигация