Perl: пример простого веб-сервиса на Plack

08 Dec 2013


...

В этой статья я покажу, как написать и запустить работать простейшее PSGI-приложение.

Что такое Plack и PSGI

Plack – это библиотека для Perl, которая позволяет разрабатывать веб-приложения (в том числе веб-фреймворки, если хочется), абстрагируясь от конкретного веб-сервера, в котором будет работать приложение (или фреймворк).

PSGI – это спецификация, определяющая абстрактный интерфейс веб-сервера, то есть PSGI – это идея, реализацией которой является Plack. А вообще для начала можно не зацикливаться на терминологической точности, и считать, что Plack и PSGI – примерно одно и то же ^_^

Зачем нужен Plack и PSGI

Если у вас уже есть какой-нибудь Perl-код, работающий под mod_perl в Apache (и вы еще не знаете про Plack), вы наверняка используете класс Apache2::Request для получения параметров запроса, чтения заголовков и т.п. И Apache2::Response для управления отдаваемым ответом. Если ваш проект существует достаточно долго, велики шансы, что начинали вы с Apache 1.x, и для переезда на 2.x вам пришлось немножко переписать код. А если бы вы захотели иметь возможность запускать свое веб-приложение и через CGI, и через FastCGI, и через mod_perl, вам пришлось бы постараться.

С Plack все это осталось в прошлом. Вы пишете прикладной код, который полагается на Plack для получения параметров url, чтения заголовков запроса, установки заголовков ответа и т.п., и после этого можете включать приложение в веб-сервер, используя CGI, FastCGI, mod_perl под Apache 1.3 и 2.x, отдельные веб-серверы Starman, Corona, Twiggy и т.п. – код не надо менять, чтобы приспособить к очередному интерфейсу веб-сервера. Удобно!

Предварительно требуется

Предварительно требуется пакет Plack.

Ubuntu:

> sudo apt-get install libplack-perl

FreeBSD:

> sudo pkg install p5-Plack-<доступная версия>

Простейшее приложение под Plack

Создаем файл first.psgi:

use strict;
use warnings;

my $app = sub {
    my $env = shift;

    my $content = "current time: ".localtime();
   
    return [
        '200',
        [ 'Content-Type' => 'text/plain' ],
        [ $content ],
    ];
};

$app;

Добавив чуть-чуть логики в вычисление переменной $content, можно реализовать, например, alive-проверку, которя будет рассказывать о состоянии сервиса (доступность БД, свободное место на диске, память и т.п.)

Включаем приложение в Apache 2.x

Кладем файл first.psgi там, где его сможет читать apache, и добавляем в httpd.conf такие настройки:

PerlRequire /full/path/to.../first.psgi
<Location /time>
    SetHandler perl-script
    PerlResponseHandler Plack::Handler::Apache2
    PerlSetVar psgi_app /full/path/to.../first.psgi
</Location>

Обратите внимание, надо дважды подставить полный путь к psgi-файлу.

Готово: на сайте, который обслуживает этот apache, появился локейшен /time, показывающий текущее время.

Запускаем приложение из командной строки через plackup

> plackup --port 8080 --host 127.0.0.1 first.psgi
> wget -qO - http://127.0.0.1:8080/
current time: Sun Dec  1 22:51:48 2013

Добавляем обработку параметров

Добавим в наше веб-приложение параметр: формат, в котором хотим получить дату.

Модифицированный first.psgi:

use strict;
use warnings;

use Plack::Request;
use POSIX;

my $app = sub {
    my $env = shift;
    
    my $r = Plack::Request->new($env);
    # $multiform -- объект Hash::MultiValue
    my $multiform = $r->parameters;
    
    my $time = strftime($multiform->{format} || "%Y-%m-%d %H:%M:%S", localtime);
    
    my $content = "current time: $time";
    
    return [
        '200',
        [ 'Content-Type' => 'text/plain' ],
        [ $content ],
    ];  
};

$app;

Запускаем:

> plackup --port 8080 --host 127.0.0.1 first.psgi

Смотрим на результат:

> wget -qO - 'http://127.0.0.1:8080?format=%H:%M'   
current time: 00:31

Кириллический контент

Чтобы отдавать через Plack utf-ные данные, надо самостоятельно их закодировать. Цитата из документации по PSGI:

The body MUST be encoded into appropriate encodings and MUST NOT contain wide characters (> 255).

use strict;
use warnings;

use Plack::Request;
use POSIX;
use Encode;

use utf8;

my $app = sub {
    my $env = shift;

    my $r = Plack::Request->new($env);
    # $multiform -- объект Hash::MultiValue
    my $multiform = $r->parameters;

    my $time = strftime($multiform->{format} || "%Y-%m-%d %H:%M:%S", localtime);

    my $content = "Точное время: $time";
    # без этого encode_utf8 ничего не работает
    $content = Encode::encode_utf8($content);

    return [
        '200',
        [ 'Content-Type' => 'text/plain' ],
        [ $content ],
    ];
};

$app;

Ну вот и все. Удачи!

Дополнительно читаем