読者です 読者をやめる 読者になる 読者になる

Plack::Middlewareの書き方

前回,Plack::Middleware書いたので,Plack::Middlewareの書き方をメモしておく.

Plack::Middlewareモジュールに最低限必要なのは基本的に2つしかない. Plack::Middlewareを継承することcallメソッドを実装しておくだけ.

callメソッドの返り値はPSGI形式のレスポンスである.

リクエストがとんでくると,builderブロック内でenableした順番にそれぞれのcallが発行される.

package Plack::Middleware::MyMy;
use strict;
use warnings;

use parent 'Plack::Middleware';

sub call {
    my ($self, $env) = @_;

    return $self->app->($env); # 次にenableしたMiddlewareのcallを呼び出す
}

$self->appは次のenableしたMiddlewareオブジェクトである.
$self->app->($env)で次のMiddlewareのcallを実行する.
$envがどんどん次のMiddlewareに引き渡されていくイメージ.

call以外にprepare_appというメソッドにより,サーバ起動時に一度だけ実行される前処理を書ける. リクエストのたびに実行させたくないような処理はここで実行させて結果を$selfに突っ込んでおくとcallが呼ばれたときに使いまわせる.

package Plack::Middleware::MyMy;
use strict;
use warnings;

use parent 'Plack::Middleware';

sub prepare_app {
    my $self = shift;
    $self->{foo} = Foo->new;
}

sub call {
    my ($self, $env) = @_;
    $self->{foo}; #=> "Fooオブジェクト"
    return $self->app->($env); # 次にenableした
}

.psgiファイル内でenableするときに第2引数以降にMiddlewareへ渡すパラメータを指定できる. 以下ではstateとstoreキーにそれぞれオブジェクトをセットしている.

    enable "Plack::Middleware::Session",
        state => Plack::Session::State::Cookie->new(
            session_key => 's',
            expires => undef,
        ),
        store => Plack::Session::Store::File->new(
            dir          => config->root->subdir('session'),
            serializer   => sub { $MessagePack->pack(+shift) },
            deserializer => sub { eval { $MessagePack->unpack(+shift) } || +{} },
        );

これらのパラメータはそのままMiddlewareオブジェクトのメンバとなる. Middlewareモジュール内でアクセッサを定義し,アクセッサ経由でパラメータを参照してることが多い.

package Plack::Middleware::Session;
use strict;
use warnings;

use parent 'Plack::Middleware';

use Plack::Util::Accessor qw(state store);

sub call {
    my ($self, $env) = @_;
    $self->state;
}
...

基本的にcallだけ書けばよい感じなので,わかりやすいけど,返り値を何にすればよいとか$self->appとは何かを気にしだすとソースを読まなければわからない感じだった. Middlewareの仕組み自体はそんなに難しくないと思うけど,subrefをネストして生成して,遅延実行していく感じのコードなのでちゃんと実行経路を辿らないと??って感じだった.

$self->app->($env)が一番謎で,次にenableされたMiddlewareオブジェクトのcallを実行するということがわかったのが収穫だった.

※ ソース読んでいっただけなので,何か間違いがあるかもしれない