Показаны сообщения с ярлыком web. Показать все сообщения
Показаны сообщения с ярлыком web. Показать все сообщения

среда, 24 декабря 2014 г.

HTTP content encoding

Решил прикрутить к AnyEvent::HTTP Accept-Encoding и к LWP в response_data handler, но перед этим выяснить какой процент серверов понимает gzip и deflate.

deflate кодирование реализовано в серверах двумя способами и поэтому авторы nginx отказались от его реализации и используют только gzip
(http://sysoev.ru/mod_deflate/readme.html#mehtods).

Под рукой оказался файл с 53587 доменами со следующим распределением по зонам:
  29244 com    1000 de      627 it
   3102 org     935 info    553 ca
   2786 uk      861 nl      504 si
   2734 net     853 au      467 fr
   1315 ru      682 br      368 ua

Для каждого домена запрашивал HTTP содержимое с указанием "Accept-Encoding" в трех различных вариантах: "gzip, deflate" (приоритет gzip), "deflate, gzip" (приоретет deflate) и "deflate". Результаты как закодировал ответ сервер представлены в нижеприведенной таблице (прочерк означает, что сервер вернул не закодированное содержимое):
             | "gzip, deflate" | "deflate, gzip" | "deflate"
 ------------|-----------------|-----------------|----------
 -           |     22888       |     22764       |  50462
 gzip        |     30612       |     30442       |    145
 deflate     |        67       |       361       |   2959
 iso-8859-1  |         1       |         1       |      1
 none        |        16       |        16       |     16
 none;       |         1       |         1       |      1
 UTF-8       |         2       |         2       |      2

Как видим, можно ограничиться поддержкой лишь одного gzip.

Заодно узнал популярность серверов:
                Все зоны | ru зона | ua зона
 ------------------------|---------|--------
 Apache            27327 |     978 |    443 
 nginx              6924 |     922 |    332
 Microsoft-IIS      6886 |     323 |    148
 -                  6116 |     212 |    103

четверг, 10 сентября 2009 г.

Url, обработчики и диспетчера

Url и обработчики

Первоначально web сайты целиком состояли из статических HTML страниц. Затем появился CGI, и сайты стали обзаводиться формами отправки данных и прочими динамическими станицами. Постепенно процент динамических страниц все увеличивался и увеличивался. Но url вида http://www.server.com/cgi-bin/foo.cgi не индексировались поисковиками, а также страдала эстетика, поэтому динамические страницы стали маскировать под обычные html: http://www.server.com/foo.html.

В то же время шло усложнение CGI скриптов, и повторяющийся код стали выносить в общие модули. Но не весь такой код можно было вынести в модули, в скриптах оставался код простой инициализации. Проще иметь одну точку входа, в которой посредством диспетчера вызывались обработчики конкретных команд, url.

Кстати, одну точку входа имели изначально те, что прятал статику за динамикой при помощи назначения обработчиков в Apache, а не при помощи mod_rewrite. Для тех, кто не прятал динамику за статикой, единая точка входа приводила к не слишком красивым url: http://www.server.com/index.php?action=foo, хотя при помощи mod_rewrite этого можно избежать.

Далее в статье web сайты являются лишь частным случаем.

Обработчики и диспетчера

В perl есть несколько возможностей сообщить диспетчеру какой код необходимо выполнять для каждой конкретной команды (url).

Можно просто вручную прописать таблицу диспетчеризации. Но при создании нового обработчика, можно забыть его зарегистрировать в таблице диспетчеризации, поэтому желательно чтобы эта регистрации происходила автоматически.

Автоматизации можно добиться при помощи задания необходимого атрибута подпрограммам-обработчикам и определения подпрограммы MODIFY_CODE_ATTRIBUTES. Например, модуль с обработчиками может иметь следующий вид:

package Foo::C::Too;
use base qw(Foo::C);
sub foo : Handler(foo) { '...' }
sub too { '...' }
sub baz : Handler(abc) { '...' }

Где подпрограммы foo и baz являются обработчиками команд foo и abc или обработчиками url http://www.server.com/foo.html и http://www.server.com/abc.html, а подпрограмма too не является обработчиком.

Модуль Foo::C::Too унаследован от Foo::C, в котором определена MODIFY_CODE_ATTRIBUTES:

package Foo::C;
sub MODIFY_CODE_ATTRIBUTES {
my ($package, $sub, @attr) = @_;
foreach (@attr) {
if (m/^Handler\((.+)\)$/) {
# Регистрация в таблице диспетчеризации
# подпрограммы $sub для команды $1.
# ...
last;
}
}
return ();
}

При запуске ядро просматривает каталог Foo/C/ и загружает все модули Foo::C::*, а perl уже сам, вызывает Foo::C::MODIFY_CODE_ATTRIBUTES для каждой подпрограммы модуля, унаследованного от Foo::C.

Подробности работы с атрибутами смотрите в perldoc attributes.

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

no strict "refs";
foreach (keys %INC) {
if (m/(Foo\/C\/.+)\.pm$/) {
my $p = $1;
$p =~ s/\//::/g;
while (my ($key, $val) = each(%{*{"$p\::"}})) {
if ($key =~ m/^h_(.+)/ and my $sub = *$val{CODE}) {
# Регистрация в таблице диспетчеризации
# подпрограммы $sub для команды $1.
# ...
}
}
}
}

Соответственно в модуле Foo::C::Too, обработчиками являются h_foo и h_baz:

package Foo::C::Too;
sub h_foo { '...' }
sub too { '...' }
sub h_baz { '...' }

На практике вместо префикса "h_" лучше использовать набор префиксов, например: "u_" - пользовательские команды, "a_" - команды администраторов, "o_" - команды доступные всем.

Дополнительные свойства обработчиков можно помещать в our переменные с именами, идентичными именам подпрограмм-обработчиков.

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

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

А если вы занимаетесь просто созданием сайтов, то посмотрите готовые фреймворки.

пятница, 31 июля 2009 г.

Причины популярности PHP на фоне Perl Web Frameworks

Я не согласен с автором заметки Причины стремительного успеха PHP.

Истоки PHP находятся в Perl. Он один из многих. Но он "застолбил" себе расширение файлов php, в то время, как другие использовали расширение cgi в каталоге /cgi-bin/, или "цепляли" свой Perl обработчик на страницы с расширением htm или html.
PHP выделился, он стал узнаваемый - а это 80% успеха. Остальные же оставались безликим множеством.

Следующим по значению фактом является то, что в те далекие времена поисковые машины не индексировали динамику, не индексировали ничего, что находилось в каталоге /cgi-bin/. Поэтому сайты на PHP были целиком известны поисковикам, а на Perl не всегда. Ведь большинство в то время не могли использовать Perl скрипты вне каталога /cgi-bin/ или не знали о такой возможности.

Мой первый хостер для статики предоставлял url следующего вида: http://hoster/~user/, а для cgi - http://hoster/cgi-bin/user.
PHP в то время обычно также ставился в каталог cgi-bin, но Apache был настроен на обработку расширения php.
Вот и третья причина: url http://server/foo.php выглядит более красивым, чем http://server/cgi-bin/project.cgi?foo.
Кажется пустяк, но психология так не считает.

Я уверен, что если бы Perl сообществу в те далекие времена удалось бы "протолкнуть" в Apache настройки поумолчанию привязку расширения, например, plp к виртуальному обработчику /cgi-bin/plp.cgi, а с Perl поставлялся бы простой обработчик plp.cgi (обработчик-каркас), то сегодня расстановка сил была бы иной.

Но история не имеет сослагательного наклонения.

пятница, 24 июля 2009 г.

Невидимый Perl

Недавно один знакомый удивленно спросил: "А разве проект N не был статическим?". "Нет", - ответил я: "он изначально был полностью написан на Perl".

На момент создания проекта его основной целью было SEO исследование, объектом которого являлся Google.
В те далекие времена поисковики не индексировали динамику, поэтому проект извне виделся как набор статических HTML страниц.
Проект выполнил свою SEO задачу на отлично и был передан дочерней структуре.

Дальнейшая судьба проект протекала без моего участия. У проекта появились новые цели, сменился дизайн и одновременно проект был почти полностью переписан на PHP. Именно смена расширения HTML страниц вызвала вопрос с которого начата эта заметка.

Зачем я рассказал эту историю? Даже не знаю. Наверно чтобы сказать, что вокруг существует множество проектов на Perl, но не видно как и на чем они сделаны. Вот такой невидимый Perl.

четверг, 6 декабря 2007 г.

FastCGI, mod_perl и прочие

С января 2001 года сфера моей профессиональной деятельности тесно связна с созданием web-сервисов, в основном аналитических.

Одной из технологий, используемых в работе, является mod_perl. FastCGI не использовался ни разу. Почему? Как часто отвечают: "по историческим причинам". А вот сейчас решил посмотреть в сторону FastCGI подробней. Специфика нового проекта подразумевает наличие frontend'да, например, nignx. "Нет, проблем", - говорю сам себе: "frontend'ом будет nignx, а backend'ом - Apache". Но не так все просто.

В рассылке по nignx, до того как было все разложено по полочкам в различных документациях и заметках, часто задавались вопросы по использованию PHP в режиме FastCGI. То есть люди массово избавлялись от Apache mod_php, переходя не FastCGI. При этом, что в тот момент, не знаю как сейчас, патч php-fpm (http://php-fpm.anight.org/) не был включен в официальные исходники, так что многие пользовалось spawn-fcgi от lighttpd.

Что это? Дань моде или нечто большее? Тем не-менее этот процесс подтолкнул меня посмотреть подробней, какие есть у FastCGI преимущества. Да, я знаю, mod_perl и mod_php координально отличаются в области загрузки кода, но будем считать, что php акселераторы работают хорошо и это не является причиной перехода с mod_php на FastCGI.

Известно, что процессы Apache с mod_perl очень прожорливы на память. Может с FastCGI ее требуется меньше? Теоретически разница не должна быть слишком большой: ведь под mod_perl можно все используемые модули загрузить до начала создания дочерних процессов.

Стоп! Если мне не изменяет память, то mod_php так не может! Наверно это и есть причина перехода с mod_php на FastCGI. Хм, но ведь и spawn-fcgi в этом вопросе не помощник...

Тем не-менее с FastCGI решил разобраться до конца. Прописал в конфиге nignx какой порт слушать и занялся perl. Для perl существует три основных модуля FCGI, FCGI::ProcManager и FCGI::Spawn. (о FCGI::Async и других разговор не ведем). Первый является основным, второй - это менеджер процессов. Третий является надстройкой над первым двумя и предназначен для запуска скриптов через require, то есть является в некотором смысле аналогом Apache::PerlRun, поэтому мы его не рассматриваем.

Модули FCGI, FCGI::ProcManager являются очень "зрелыми" и все приведенные примеры предназначены для запуска FastCGI из-под "пускалок", а не как самостоятельные, поэтому все примеры слегка модифицируем. Перед основным циклом открываем сокет, который будет слушать nignx:

use FCGI;
use CGI;

my $socket = FCGI::OpenSocket(":9000", 5);
my $request = FCGI::Request(\*STDIN, \*STDOUT, \*STDERR, \%ENV, $socket);

my $count = 0;

while($request->Accept() >= 0) {
$count++;
print <<TEXT;
Content-Type: text/html

<h1>hello</h1>
<p>$count</p>
<hr>
TEXT

print "$_ = $ENV{$_}<br>\n" foreach sort keys %ENV;

print "<hr>\n";

my $query = CGI->new();
print "$_ = ", $query->param($_), "<br>\n" foreach sort $query->param();

}

FCGI::CloseSocket($socket);

Но это пример имеет большой недостаток: всего лишь один процесс FastCGI.
На помощь приходит модуль FCGI::ProcManager, который создает до основного цикла
заданное количество потомков, выполняющих всю полезную работу по обработке запроса:

use FCGI;
use FCGI::ProcManager;
use CGI;

my $proc_manager = FCGI::ProcManager->new({ n_processes => 10 });

my $socket = FCGI::OpenSocket(":9000", 5);

my $request = FCGI::Request(\*STDIN, \*STDOUT, \*STDERR, \%ENV, $socket);

$proc_manager->pm_manage();

my $count = 0;
while($request->Accept() >= 0) {
$proc_manager->pm_pre_dispatch();
$count++;
print <<TEXT;
Content-Type: text/html

<h1>hello</h1>
<p>$count</p>
<hr>
TEXT

print "$_ = $ENV{$_}<br>\n" foreach sort keys %ENV;

print "<hr>\n";

my $query = CGI->new();
print "$_ = ", $query->param($_), "<br>\n"
foreach sort $query->param();

$proc_manager->pm_post_dispatch();
}

FCGI::CloseSocket($socket);

Кстати, если кто знает ответе: в линуксе до сих про плохо с большим количеством
прослушивающих один сокет процессов и проходится перед аccept делать монопольную
блокировку файла "регулировщика"?

Вернемся к основной теме. В примерах использования FCGI::ProcManager используется модуль CGI::Fast, используя которых вышеприведенный вариант можно привести к следующему виду:

BEGIN {
$ENV{FCGI_SOCKET_PATH} = ":9000";
$ENV{FCGI_LISTEN_QUEUE} = 5;
}

use CGI::Fast;
use FCGI::ProcManager;

my $proc_manager = FCGI::ProcManager->new({ n_processes => 10 });

$proc_manager->pm_manage();

my $count = 0;

while(my $query = CGI::Fast->new()) {
$proc_manager->pm_pre_dispatch();

$count++;
print <<TEXT;
Content-Type: text/html

<h1>hello</h1>
<p>$count</p>
<hr>
TEXT

print "$_ = $ENV{$_}<br>\n" foreach sort keys %ENV;

print "<hr>\n";

print "$_ = ", $query->param($_), "<br>\n"
foreach sort $query->param();

$proc_manager->pm_post_dispatch();
}

Теперь рассмотрим фрейморки Catalyst и CGI::Application.

Для Catalyst создаем новый проект

catalyst.pl MyApp

и запускаем

myapp_fastcgi.pl -listen=localhost:9000 -nproc=10

Все просто. А вот с CGI::Application немного сложней. Модуль CGI::Application::FastCGI, как оказалось, предназначен для работы из-под "пускалки", поэтому используем не его, а непосредственно FCGI::ProcManager.

BEGIN {
$ENV{FCGI_SOCKET_PATH} = ":9000";
$ENV{FCGI_LISTEN_QUEUE} = 5;
}

use WebApp;
use CGI::Fast;
use FCGI::ProcManager;

my $proc_manager = FCGI::ProcManager->new({ n_processes => 10 });
$proc_manager->pm_manage();

while(my $query = CGI::Fast->new()) {
$proc_manager->pm_pre_dispatch();
my $app = WebApp->new(QUERY => $query);
$app->run();
$proc_manager->pm_post_dispatch();
}

Ну вот, с запуском разобрались, теперь посмотрим на использование памяти в реальных условиях. Как и ожидалось выигрыша по памяти почти нет, разумеется, когда Apache собран без излишеств.

Так что за nignx можно смело ставить либо mod_perl, либо FastCGI. Это когда один скрипт. А когда скриптов несколько и они используют множество общих модулей, то выигрыш варианта с mod_perl очевиден.

Ну вот пожалуй и все.

P.S. Если я где-то ошибся в рассуждения, просьба поправить.

20 декабря 2007, Николай.

P.P.S
Первоначально опубликовано на http://kiev.pm.org/?q=node/111.
Дату оставил как есть. :-)
В perl коде добавил вызовы pm_pre_dispatch pm_post_dispatch, которые для краткости примеров первоначально удалил.
По адресу http://kiev.pm.org/?q=node/111 смотрите ценные замечания, так же оставляйте свои.