Perl Hackers Hub

第1回PSGI/Plack―フレームワークとサーバをつなぐエンジン (3)

PSGIミドルウェアを利用する

PSGIミドルウェアはアプリケーションと同様にコードリファレンスとして実装されますが、いちいち.psgiファイルに記述するのはあまり効率が良くありませんし、再利用性もありません。そこでPlackにはミドルウェアをモジュールとして利用するためのベースクラスPlack::Middlewareや、それを利用して実装された各種ミドルウェアが同梱されています[2]⁠。

ここでは、これらのミドルウェアを利用して先ほどのTatsumakiアプリケーションを拡張してみましょう。

Auth::Basic

PSGIアプリケーションに簡単なHTTP認証機能を付けるミドルウェアがAuth::Basicです。容易に拡張できるコールバックインタフェースを持っているため、データベースやLDAPLightweight Directory Access Protocolなどとの連携もできます。

まずは、先ほどのhello.psgiを開き、ミドルウェアを利用するコードを追加します。リスト5のように、Plack::Middleware::Auth::Basicをロードし、wrapメソッドを使ってPSGIアプリケーションのコードリファレンス$app->psgi_appに認証機能を追加します(2)⁠。authenticatorはユーザ名、パスワードを受け取り、それが正しいかどうかを返すコールバック関数になります。また、Helloハンドラ内でも、認証されたユーザを$self->request->userから取得して画面に表示(1)するようにしてみます。

リスト5 Auth::Basicの追加
use strict;
package Hello;
use parent 'Tatsumaki::Handler';

sub get {
    my $self = shift;
    $self->write("Hello ", $self->request->user); ……(1)
}

use Tatsumaki::Application;
my $app = Tatsumaki::Application->new([
    '/hello' => 'Hello',
]);

use Plack::Middleware::Auth::Basic;                 
$app = Plack::Middleware::Auth::Basic->wrap(        
    $app->psgi_app,                                 
    authenticator => sub {                          │(2)
        my($user, $pass) = @_;                      
        return $user eq 'admin' && $pass eq 'gihyo';
    },
);

ブラウザでhttp://localhost:5000/helloをもう一度開いてみましょう。図6のように認証ダイアログが表示されるはずです。ここではコードに記述したように、ユーザ名「admin⁠⁠、パスワード「gihyo」だけが正しい入力ペアとなっています。それ以外の値を適当に入力しても、再度ダイアログが表示されるだけでしょう。正しいパスワードを入力すれば、⁠Hello admin」と認証によって取得したユーザ名が出力されます。

Auth::BasicミドルウェアはCPANモジュールのAuthen::Simpleのインタフェースにも対応しているため、LDAPや.htpasswdファイルなどによる認証と簡単に連携できます。詳しくはオンラインドキュメントを参照してください。

図6 Auth::Basicによる認証機能の追加
図6 Auth::Basicによる認証機能の追加

Plack::Builder

先ほどの例では、ミドルウェアを通常のPerlモジュールのようにuseし、wrapメソッドを呼ぶことでミドルウェアを追加しました。この方法では利用するミドルウェアを増やしていく場合などに記述が長くなりがちで、ミドルウェアの実行順序などが直感的でないという問題があります。そこで用意されているのがPlack::BuilderモジュールによるDSLDomain Specific Languageドメイン特化言語)です。

Auth::Basicを利用したミドルウェアの例は、Plack::Builderを利用するとリスト6のように記述できます。ラップされるアプリケーションをbuilder { }で囲み、その上にenableミドルウェア名,オプション...;のようにスタックしていくイメージです。ミドルウェア名の「Plack::Middleware::」部分は省略できますし、モジュールはBuilderによって自動的にロード(require)されますので、記述を大幅に簡潔化できます。

リスト6 Builderによるミドルウェアの記述
# $appまではリスト5と同様

use Plack::Builder;

builder {
    enable "Auth::Basic", authenticator => sub { ... };
     $app->psgi_app;
};

たとえば、ここでは詳細を省きますが、スタティックファイルを配信するミドルウェアStaticやアプリケーションの実行時間をX-Runtimeヘッダに挿入するRuntimeミドルウェアと組み合わせるとリスト7のように書くことができます。図4で見たようにアプリケーションが真ん中にあるとすれば、builderの中で下の行にありアプリケーションに近いほうが、真ん中に近い位置にあることになります。よって、この.psgiファイルを実行すると、Auth::Basic⇒Static⇒Runtime⇒Tatsumakiアプリケーション⇒Runtime⇒Static⇒Auth::Basicの順番で処理が実行されます。ミドルウェアごとにアプリケーションの前に実行されるもの(認証など⁠⁠、後に実行されるもの(出力ヘッダの挿入など)に種類が分かれていますので、ドキュメントなどを参照して、どの順番が最適かを検討してください。

リスト7 ミドルウェアの追加
# $appまではリスト5と同様

use Plack::Builder;

builder {
    enable "Auth::Basic", authenticator => sub { ... };
    enable "Static", path => qr/^\/images/, root => "/images";
    enable "Runtime";
    $app->psgi_app;
};

そのほかのミドルウェア

紙幅の都合ですべては紹介できませんが、PlackコアやCPANには便利な各種ミドルウェアがアップロードされています。

Session

Sessionミドルウェアはフレームワークにセッション管理機能を追加します。多くのフレームワークが自前でセッション管理を実装している場合がほとんどのため、執筆時点ではあまりメリットがありませんが、フレームワークからこのPlack::Middleware::Sessionを利用するためのアダプタを記述すれば、各種フレームワークでセッションを共有する、といったことが容易にできるようになります。SessionミドルウェアはCookieによりID管理を行い、ストレージには各種ファイルやmemcachedといったキャッシュバックエンドが利用可能です。

Debug

PythonのDjangoプラグインであるdjango-debugtoolbarやRackのミドルウェアrack-bugにインスパイアされて作成したミドルウェアがDebugです。このミドルウェアを有効にすると、HTMLページの右側にパネル図7が表示され、リクエストヘッダやレスポンス、各種フレームワークのログなどをクリックして見ることができます。開発時にはとても有用なミドルウェアでしょう。

図7 Debugパネル
図7 Debugパネル

PSGI非対応Webアプリケーションを移行する

利用しているWebフレームワークがPSGIに対応している場合、PSGIへ移行してPlackやミドルウェアを利用することは簡単でした。基本的にはコードの変更は必要なく、.psgiファイルなどを作成して起動方法を変更するだけです。

では、使っているフレームワークがPSGIに対応していない場合や、自前のフレームワークを作成している場合はどうでしょうか。

CGI::PSGI

先述したように、PSGIの環境はCGIに似せた仕様となっており、CGI環境で動くアプリケーションであればPSGIへの移行が簡単に行えるようにデザインされています。

お使いのフレームワークでCGI.pmを利用している場合、CPANモジュールのCGI::PSGIを利用することでPSGIへの対応が容易になるかもしれません。実装の詳細は省略しますが、リスト8のようにCGI.pmをリクエストのパラメータやパスなどの処理のみに使用しているフレームワークであれば、リスト9のようにPSGI環境変数を取得し(1)⁠、CGI::PSGIを利用するように書き換え、あとはヘッダを出力しレスポンスを返す部分を配列リファレンスに変更する(2)だけでよいでしょう。

リスト8 CGI.pmを利用したフレームワークのコード
package MyFramework;
use CGI;

sub new {
    my $class = shift;
    bless { cgi => CGI->new }, $class;
}

sub dispatch {
    my $self = shift;

    my $path = $self->{cgi}->path_info;

    # $pathからディスパッチするアクションを決定
    my $action = $self->get_action($path);

    my($headers, $body) = $action->dispatch($self);

    # $headers is HTTP::Headers
    print $headers->as_string, $body;
}

1;
リスト9 CGI::PSGIの利用
package MyFramework;
use CGI::PSGI;

sub new {
    my $class = shift;
    my $env = shift; ……(1)
    bless { cgi => CGI::PSGI->new($env) }, $class;
}

sub dispatch {
    my $self = shift;

    my $path = $self->{cgi}->path_info;

    # $pathからディスパッチするアクションを決定
    my $action = $self->get_action($path);

    my($headers, $body) = $action->dispatch($self);

    # $headers is HTTP::Headers
    my @headers;
    $headers->scan(sub { push @headers, @_ });

    return [ 200, \@headers, [ $body ] ]; ……(2)
}
1;

CGI::Emulate::PSGI+CGI::Compile

前項では簡単に言いましたが、言うは易く行うは難し。CGI環境でしか利用できない%ENVを利用していたり、CGI.pmの関数をメソッドでない利用方法で呼んでいたりと、うまくPSGIで動かない例もあるかもしれません。

その際に使えるのがCGI::Emulate::PSGIとCGI::Compileで、どちらもCPANから入手できます。CGI::Compileは、CGIスクリプトを何度も呼べる形のサブルーチンリファレンスにコンパイルします。CGI::Emulate::PSGIは、それをPSGIアプリケーションとして実行できるよう、環境変数%ENVや標準入出力をラップしてCGI環境をエミュレートします。CGI::PSGIを利用する場合に比較してエミュレートする分のオーバーヘッドはありますが、各種PSGIサーバで実行でき、Plackミドルウェアが利用できるなど、手に入るメリットはとても大きいはずです。

Plack::Request

すでに自前のRequestクラスなどを持っている既存のフレームワークや、mod_perlのApache::Requestを利用したコードをPlack対応させるには、Plack::Requestが便利です。Plack::Requestは執筆時点で鋭意開発中のPlackバージョン1.0からコアモジュールとなり、CatalystやApacheに似たリクエスト/レスポンスAPIを提供します。単純にパラメータを処理するだけならば、CGI.pmの置き換えとして利用することも可能でしょう。

PSGIサーバ

ここでは、どのようなWebサーバ環境でPSGIがサポートされているかを簡単に紹介します。

CGI、FastCGI

いわゆる一般的なWebサーバであるApache、lighttpdやnginxなどからPSGIを実行するには、CGIやFastCGIなどを利用するのが一般的になります。Plackにはこれらの環境で実行できるアダプタが含まれています。

mod_perl

Perlアプリケーションを永続環境で実行するスタンダードな方法としてmod_perlがあります。Plackにはmod_perl 1.xと2.xに対応したハンドラが含まれていますので、どのようなPSGIアプリケーションでもmod_perl上で実行できます。

HTTP::Server::PSGI

先述したように、Plackに標準でバンドルされているスタンドアロンのサーバがHTTP::Server::PSGIです。Pure Perlで書かれており、Cモジュールなどへの依存もありませんので、Windowsなどを含むどのような環境でもインストールが可能です。また、プロセス数を複数指定した場合には、preforkの形で起動させて同時リクエストを並列処理することもできます。

Starman

筆者も開発に関わっているUNIXベースのWebサーバがStarmanです。StarmanはNet::Server::PreForkをベースにしたHTTPサーバで、preforkによる並列動作やHTTP 1.1対応、UNIXソケットへのバインド、Server::Starterなどのホットデプロイツールに自動対応するなど、プロダクション環境での実行に最適化することを念頭において開発されています。

AnyEvent、POE、Coro

Perlの非同期フレームワークやイベントループライブラリとしてよく利用されているAnyEvent、POEやCoroを利用したPSGIサーバも、それぞれCPANに登録されています。Plackからこれらのサーバを利用するアダプタがPlack::Handler::AnyEven(t POE、Coro)の名前で登録されていますので、これらをキーにして検索してみてください。

まとめ

Plackを利用することで、PSGIに対応したどのようなアプリケーション、フレームワークも、さまざまなWebサーバ環境で動かすことが可能になります。また、ミドルウェアを利用してさまざまな拡張機能を実装し、その実装をフレームワーク間で共有することが可能になります。今回は紹介しきれませんでしたが、Plackを利用してWebのテストを完全にサーバから切り離すためのツール(Plack::TestやTest::WWW::Mechanize::PSGI)なども出てきています。各種ミドルウェアの充実もあり、PSGIに対応するメリットは日々増えています。ぜひ、Plackで新しいWebアプリケーション開発を体験してください。

そのほかの最新情報などについては、PlackのWebサイトやCPANのドキュメントなどを参照してください。

次回は、本文最後でも紹介した非同期のイベントループを簡潔に記述するためのライブラリAnyEventについて、牧大輔さんが担当します。

では、Happy Hacking!

おすすめ記事

記事・ニュース一覧