最近Middlewareを書くことが多かったのでまとめときます。
Middlewareとは
PSGI的には、Middlewareは外から見るとPSGI Applicationsですが、別のPSGI Applicationsを動かす能力を持っているものです。平たく言えば、「$app をラップして 新たな $app として振る舞うもの」と言えます。ただしPSGIでは、この"ラップの方法"は定められていません。
ただ、現実的には Plack::Builder の enable で適用できた方が楽なので、「Plack::Middlewareを継承する」か「$appを受け取って新たな$appを返すコードリファレンス」のどちらかがいいでしょう。
入力$envを参照・変更する
一番基本的なMiddlewareのパターンです。これは簡単。コードリファレンスで実装するとこんな感じ。
my $middleware = sub { my $app = shift; return sub { my $env = shift; # ... ここで$envを変更 ... $app->( $env ); }; };
出力$resのStatusとHeadersを参照・変更する
$appから出力される$resには、array referenceとcode referenceの2種類があるのでちょっと複雑です。2010-2-11時点でまだ公開されていないgithub上のpsgi-specsでは、サーバは psgi.streaming をサポートすべき(SHOULD)となっていますので、これを期待してMiddlewareを書くと以下のような感じです。
my $middleware = sub { my $app = shift; return sub { my $env = shift; die 'not supported' unless $env->{'psgi.streaming'}; # ... $envを煮るなり焼くなり ... return sub { my $respond = shift; my $respond_wrapper = sub { my $res = shift; # ... $res->[0]と$res->[1]をお好きなように ... return $respond->( $res ); # $writerが返るかも }; my $res = $app->( $env ); ref $res eq 'CODE' ? $res->( $respond_wrapper ) : $respond_wrapper->( $res ); }; }; };
また、次の response_cb を使うと、 psgi.streaming を強制せずに $res を操作可能です。
出力$resのBodyを参照・変更する
Bodyの出力にはArray、Handle、$writerの形式等があるので、実装は大変です。素直にPlack::Middleware を継承して、 response_cb の力を借りた方がいいです*1。
package MyFramework::Middleware::SomeMiddleware; use parent qw(Plack::Middleware); sub call { my( $self, $env ) = @_; # ... $envを好きなように調理 ... my $res = $self->app->($env); $self->response_cb( $res, sub { my $res = shift; # ←$res->[0]と$res->[1]は必ずある # ... $res->[0]と$res->[1]を参照したり変更したり ... # さらにBodyを変更したい場合は、フィルタさせる関数を返すとよい my $done = 0; return sub { my $body_chunk = shift; if( $done ){ # 最後に必ずundefを返す return undef; }elsif( defined $body_chunk ){ # ... $body_chunk に好きな変更をする ... return $body_chunk; }else{ # Bodyの最後にはundefが渡ってくる $done = 1; return $body_footer; }; } ); }
ただし、response_cb のコールバックからBodyを変更するフィルタを返すと、Content-Lengthが削除されます。Bodyを変更する必要がない時は、フィルタを返さないようにしましょう。
2010-03-29 追記
response_cbのコールバック(body filter)は、最後にundefを返す必要があります。よって、以下のようなコールバックを返してはいけません。無限ループします。
## !!CAUTION!! ダメなフィルタの例。undefが永遠に返らないので無限ループする sub { defined $_[0] ? $_[0] : 'END' }
以下、IRC(#plack@irc.perl.org)でのログ。
13:29:28 miyagawa: the callback handler in your second test is wrong 13:29:44 miyagawa: it's causing infinite loop and that's fine 13:29:56 miyagawa: the callback handler should check if it's given undef and return undef in the next call 13:30:09 miyagawa: that's how middleware filter callback is coded