- PVSM.RU - https://www.pvsm.ru -
Автор: Дмитрий Шаматрин. [1]
С разрешения автора оригинальных статей цикла я публикую цикл на Хабре.
PSGI/Plack — современный способ написания web-приложений на Perl. Практически каждый фреймворк так или иначе поддерживает или использует эту технологию. В статье представлено краткое введение, которое поможет быстро сориентироваться и двигаться дальше.
Мы живем в такое время, когда технологии и подходы в области web-разработки меняются очень быстро. Сначала был CGI, потом, когда его стало недостаточно, появился FastCGI. FastCGI решал главную проблему CGI. В CGI при каждом обращении было необходимо перезапускать серверную программу, обмен данными происходил при помощи STDIN и STDOUT. В FastCGI взаимодействие с сервером происходит через TCP/IP или Unix Domain Socket. Теперь у нас есть PSGI.
PSGI, как говорит его разработчик Tatsuhiko Miyagawa, это «Перловый суперклей для веб-фреймворков и веб-серверов». Ближайшие родственники — WSGI (Python) и Rack (Ruby). Идея тут вот в чем. Разработчик очень часто тратит довольно много времени, чтобы адаптировать свое приложение под как можно большее количество движков, а PSGI предоставляет единый интерфейс для работы с различными серверами, что сильно упрощает жизнь.
Безусловно, формат статьи не позволяет описать полностью все нюансы, поэтому здесь и далее будут только ключевые моменты.
На данном этапе это все, что нужно для того, чтобы начать разбираться с кодом непосредственно.
Ниже приведен код простейшего PSGI-приложения.
my $app = sub {
my $env = shift;
# Производим необходимые манипуляции с $env
return [200, ['Content-Type' => 'text/plain'], ["hello, worldn"]];
};
Сохраняем это приложение в файле app.psgi, или любом другом с расширением psgi. Смотрим на особенности. Потом на код. Потом опять на особенности. Все сходится. Запускаем.
При запуске perl app.psgi он «молча» отрабатывает, но приложение не запущено.
Для того, чтобы запускать PSGI-приложения нам необходим PSGI-сервер. На данный момент серверов несколько.
Все эти сервера доступны на CPAN. В дальнейшем мы будем использовать Starman, затем сменим его на Twiggy, а затем на Feersum. Каждой задаче свой сервер.
Приложение абсолютно одинаково запустится на любом из этих серверов, может быть, под Corona его придется чуть видоизменить. После установки сервера, а в нашем случае это Starman, в /usr/bin или /usr/local/bin должен появиться исполняемый файл starman. Запуск производится следующей командой:
/usr/local/bin/starman app.psgi
По умолчанию PSGI-серверы используют 5000 порт. Мы можем его изменить, запустив приложение с ключом --port 8080, например. Напомним, что PSGI — спецификация. В данном случае мы использовали эту спецификацию для написания простейшего web-приложения. Очевидно, что для нормальной разработки нам необходимо реализовать и множество вспомогательных функций, от получения GET-параметров до получения данных cookie. Этого всего не было бы без необходимого функционала.
Plack — это реализация PSGI (в Perl есть стандартный модуль Pack, потому реализация получила имя Plack). Plack существенно облегчает нам жизнь, как разработчикам. Он содержит в себе огромное количество функций для работы с $env.
В базовой комплектации Plack состоит из довольно большого количества модулей. На данном этапе нас интересуют только эти:
Plack::Request и Plack::Response возвращают различные значения типа Hash::MultiValue, на которые стоит обратить внимание.
Модуль, автором которого тоже является Tatsuhiko Miyagawa, представляет собой хеш, но с одним нюансом. Он может хранить несколько значений по одному ключу. Например: $hash->get('key') вернет value, если же значений по ключу несколько, то оно вернет последнее, а если нужны все значения, то можно воспользоваться функцией $hash->get_all('key'), тогда результат будет ('value1','value2'). Hash::MultiValue также учитывает контекст вызова, так что будьте внимательны.
Модуль, который содержит функции для работы с запросами клиента. Методов содержит много, всегда можно ознакомиться на CPAN. В рамках этой статьи, дальше, мы будем использовать следующие методы:
Рассматривать методы не будем, отметим только, что это весьма гибкий маршрутизатор. Например, он позволяет устанавливать обработчик (PSGI- приложение) на локальный адрес:
my $app = builder {
mount "/" => builder { $my_cool_app; };
};
Результат — обращения по адресу / будут перенаправлены в соответствующее PSGI-приложение. В данном случае это $my_cool_app.
Маршруты могут быть вложенными, например:
my $app = builder {
mount "/" => builder {
mount "/another" => builder { $my_another_cool_app; };
mount "/" => builder { $my_cool_app; };
};
};
И эти маршруты могут быть вложенными. В этом примере, все, что не попадает в /another отправляется в /.
Базовый класс для создания middleware-приложений. Middleware это «промежуточное программное обеспечение». Используется тогда, когда нужно модифицировать PSGI-запрос или готовый PSGI-ответ, а также предоставить специфические условия для запуска определенной части приложения.
use strict;
use Plack;
use Plack::Request;
my $app = sub {
my $env = shift;
my $req = Plack::Request->new($env);
my $res = $req->new_response(200);
$res->body('Hello World!');
return $res->finalize();
};
Это простейшее приложение, использующее Plack. Оно совершенно наглядно демонстрирует принцип его работы.
На что надо обратить внимание. $app — ссылка на функцию. Очень часто, когда идет быстрое написание нечто подобного, забывается символ; после окончания ссылки на функцию или создание Plack::Request без передачи $env. Стоит быть внимательным.
Для проверки синтаксиса можно использовать perl -c app.psgi.
Вот еще один важный момент касательно написания PSGI-приложений: при формировании тела ответа стоит убедиться, что там находятся байты, а не символы (например, UTF-8). Обнаруживается такая ошибка весьма сложно. Ее наличие приводит к пустому ответу сервера с ошибкой в psgi.error:
«Wide character at syswrite»
Запускается наше приложение аналогично предыдущему.
Да, Hello world это конечно неплохо, но мало функционально. Сейчас, используя весь инструментарий, попробуем написать простейшее приложение (но оно будет гораздо полезнее, правда).
Напишем API, реализующее три функции:
В результате написания кода у нас должно получиться нечто, умеющее следующие вещи:
Для переворачивания строки будем использовать следующую конструкцию:
$string = scalar reverse $string;
Для определения, является ли строка палиндромом, будем использовать следующую функцию:
sub palindrome {
my $string = shift;
$string = lc $string;
$string =~ s/s//gs;
if ($string eq scalar reverse $string) {
return 1;
}
else {
return 0;
}
}
Plack::Request позволяет получать параметры при помощи метода parameters.
my $params = $req->parameters();
Доработаем приложение и приведем его к виду:
use strict;
use Plack;
use Plack::Request;
my $app = sub {
my $env = shift;
my $req = Plack::Request->new($env);
my $res = $req->new_response(200);
my $params = $req->parameters();
my $body;
if ($params->{string}) {
$body = 'string exists';
}
else {
$body = 'empty string';
}
$res->body($body);
return $res->finalize();
};
Запускаем. Первая часть готова.
Перейдя по адресу localhost [2]:8080/?string=1 мы увидим ответ, который скажет нам о том, что строка есть. Переход же по адресу localhost [2]:8080/ вернет нам ошибку.
Остальную логику можно реализовать прямо в этом же приложении, разделяя логику по path_info, которая будет содержать текущий путь. Для справки, разбор path_info может быть реализован следующим образом:
my @path = split '/', $req->path_info();
shift @path;
И теперь в $path[0] находится необходимый нам путь.
Важно: после внесения изменений в код, сервер необходимо перезапускать!
А вот теперь стоит повнимательнее посмотреть на маршрутизатор.
Он дает возможность использовать другие PSGI-приложения в качестве компонентов. Еще очень полезной будет возможность подключать middleware.
Переделаем первое приложение так, чтобы оно использовало маршрутизатор.
use strict;
use Plack;
use Plack::Request;
use Plack::Builder;
my $app = sub {
my $env = shift;
my $req = Plack::Request->new($env);
my $res = $req->new_response(200);
$res->header('Content-Type' => 'text/html', charset => 'Utf-8');
my $params = $req->parameters();
my $body;
if ($params->{string}) {
$body = 'string exists';
}
else {
$body = 'empty string';
}
$res->body($body);
return $res->finalize();
};
my $main_app = builder {
mount "/" => builder { $app; };
};
Теперь $main_app это основное PSGI-приложение. $app присоединяется к нему по адресу /. Кроме того, была добавлена функция для установки заголовков в ответ (через метод header). Стоит сделать важное замечание: в данном приложении для упрощения все функции помещены в один файл. Для более сложных приложений так делать, конечно, не рекомендуется.
Теперь подключим компонент для переворачивания строки в виде приложения, которое будет находиться по адресу localhost [2]:8080/reverse.
use strict;
use Plack;
use Plack::Request;
use Plack::Builder;
my $app = sub {
my $env = shift;
my $req = Plack::Request->new($env);
my $res = $req->new_response(200);
$res->header('Content-Type' => 'text/html', charset => 'Utf-8');
my $params = $req->parameters();
my $body;
if ($params->{string}) {
$body = 'string exists';
}
else {
$body = 'empty string';
}
$res->body($body);
return $res->finalize();
};
my $reverse_app = sub {
my $env = shift;
my $req = Plack::Request->new($env);
my $res = $req->new_response(200);
my $params = $req->parameters();
my $body;
if ($params->{string}) {
$body = scalar reverse $params->{string};
}
else {
$body = 'empty string';
}
$res->body($body);
return $res->finalize();
};
my $main_app = builder {
mount "/reverse" => builder { $reverse_app };
mount "/" => builder { $app; };
};
Адрес для проверки — localhost [2]:8080/reverse?string=test%20string.
2/3 задачи выполнено. Однако, в данном случае уж очень похожие получились $app и $reverse_app. Проведем небольшой рефакторинг. Сделаем функцию, которая будет возвращать другую функцию (иначе, функцию высшего порядка).
Теперь приложение выглядит так:
use strict;
use Plack;
use Plack::Request;
use Plack::Builder;
sub build_app {
my $param = shift;
return sub {
my $env = shift;
my $req = Plack::Request->new($env);
my $res = $req->new_response(200);
$res->header('Content-Type' => 'text/html', charset => 'Utf-8');
my $params = $req->parameters();
my $body;
if ($params->{string}) {
if ($param eq 'reverse') {
$body = scalar reverse $params->{string};
}
else {
$body = 'string exists';
}
}
else {
$body = 'empty string';
}
$res->body($body);
return $res->finalize();
};
}
my $main_app = builder {
mount "/reverse" => builder { build_app('reverse') };
mount "/" => builder { build_app() };
};
Так гораздо лучше. Теперь добавим третью и последнюю функцию в наше API и закончим, наконец, приложение. В результате всех доработок получилось приложение вида:
use strict;
use Plack;
use Plack::Request;
use Plack::Builder;
sub build_app {
my $param = shift;
return sub {
my $env = shift;
my $req = Plack::Request->new($env);
my $res = $req->new_response(200);
$res->header('Content-Type' => 'text/html', charset => 'Utf-8');
my $params = $req->parameters();
my $body;
if ($params->{string}) {
if ($param eq 'reverse') {
$body = scalar reverse $params->{string};
}
elsif ($param eq 'palindrome') {
$body =
palindrome($params->{string})
? 'Palindrome'
: 'Not a palindrome';
}
else {
$body = 'string exists';
}
}
else {
$body = 'empty string';
}
$res->body($body);
return $res->finalize();
};
}
sub palindrome {
my $string = shift;
$string = lc $string;
$string =~ s/s//gs;
if ($string eq scalar reverse $string) {
return 1;
}
else {
return 0;
}
}
my $main_app = builder {
mount "/reverse" => builder { build_app('reverse') };
mount "/palindrome" => builder { build_app('palindrome') };
mount "/" => builder { build_app() };
};
Ссылка для проверки:
localhost [2]:8080/palindrome?string=argentina%20Manit%20negra
В дальнейших статьях будут рассмотрены более углубленные темы: middleware, сессии, cookie, обзор серверов, с примерами для каждого конкретного + небольшие бенчмарки, особенности и тонкости PSGI/Plack, PSGI под нагрузкой, обзор способов разворачивания PSGI-приложений, PSGI-фреймворки, профилирование, Starman + Nginx, запуск CGI-скриптов в PSGI-режиме или «У меня CGI приложение, но я хочу PSGI» и так далее.
Автор: inquisitor_ua
Источник [3]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/web-razrabotka/79151
Ссылки в тексте:
[1] Дмитрий Шаматрин.: http://pragmaticperl.com/authors/6
[2] localhost: http://localhost
[3] Источник: http://habrahabr.ru/post/247545/
Нажмите здесь для печати.