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

北海道苫小牧市出身の初老PGが書くブログ

永遠のプログラマを夢見る、苫小牧市出身のおじさんのちらしの裏

PSGIを勉強したメモ(2) (PSGI 1.03でのストリーミング)

perl+web メモ

PSGI Specificationが1.03に更新されています。新しく psgi.streaming が追加されました (Tatsumakiでも使ってる)。

最初にとても大事な注意

このエントリはPSGI 1.03の仕様について勉強したことを書いてます。PSGIの仕様はまだ流動的なので、かならず最新の仕様を見て下さい

psgi.streaming とは

PSGIアプリの戻り値は配列リファレンスですが、 psgi.streaming が有効になっているサーバではコールバック関数を返せます。

コールバック関数には返答用の関数が渡ってくるので、そこにいつもの配列リファレンスを返します。

sub {
    my $env = shift;
    $env->{'psgi.streaming'} or die;

    return sub {
       my $respond = shift;
       my $t; $t = AE::timer 5, 0, sub {
            $respond->([
                '200',
                [ 'Content-Type' => 'text/plain' ],
                [ "Hello World" ],
            ]);
            undef $t;
        };
    };
};

また、返答用の関数に3値ではなく2値の配列リファレンスを渡すと、書き込み用のオブジェクトが返ってきます。そいつにだらだらwriteを繰り返せば、ストリーミングができます。

psgi.streaming と Middleware

Middlewareは必ずしも psgi.streaming でのコールバック関数の戻り値に対応する必要はないですが、戻り値が理解できない場合は素通しすべきとのことです。

psgi.nonblocking とは

PSGIアプリケーションが、イベントループからコールバックとして呼ばれていることを示す値です。psgi.streamingと違い、戻り値等の変更ありません。

PSGI 1.02の時は、 psgi.nonblocking が有効なときに IO::Handle 的なオブジェクトを返すことでストリームを実現することが想定されてましたが、PSGI 1.03のFAQではpsgi.streamingの仕様を利用するように変更されています*1。IO::Writerへの言及もなくなりました。

psgi.nonblocking 時のイベントループの選択

psgi.nonblockingが有効になっているサーバではイベントループを利用して非同期処理を書くことが許されます*2が、どのイベントループを使うかの指針をtwitterid:miyagawaさんに聞いてみました。

hiratara psgi.nonblocking が TRUEだった時に、アプリ側では何ができるんだろうか。AnyEvent だと思って組んでいいってことでもないだろうしなあ。Perlbalもあるし。

miyagawa @hiratara You can use Danga::Socket::AnyEvent to run AE modules on Perlbal

hiratara @miyagawa AEとかPerlbalの話ですが、PSGI的には、psgi.nonblocking が設定された環境下で application が何を使ってブロックしないようにするかは、デプロイしようとしているサーバに依存ですか? それとも、AnyEvent推奨ですか?

miyagawa @hiratara AnyEvent 推奨かつサーバ依存です :)

ということで、現状ではAnyEventで書くのが一番のようです。

まとめ

PSGI 1.03でのストリーミングの書き方のまとめです。

  1. 利用するPSGIサーバで psgi.streaming と psgi.nonblocking が有効か調べる
  2. PSGIアプリからの戻り値を、コールバック関数にする
    • psgi.streamingの仕様
  3. コールバック関数内で、AnyEventを使って非同期にレスポンスを返す
    • psgi.nonblocking なのでAnyEventが(たぶん推奨されてるので)使える

*1: IO::Handle ライクなオブジェクトだけだと、確かに Coro がないと non-blocking にできなさそう。

*2:むしろ、ブロックしないように書かないとサーバ全体に影響を与えてしまう。