Perl Hackers Hub

第41回 Plack::Middleware再入門(3)

この記事を読むのに必要な時間:およそ 3.5 分

(1)こちら⁠2)こちらから。

汎用的なPlack::Middlewareを書く

さて,いろいろなPlack::Middlewareを利用していると,CPANにはないものが欲しくなって自分で書く場面がやってきます。ただ,Plack::Middlewareの実装にはPSGIの仕様上いくらか気を付けなければいけない点があり,ゼロから汎用的なものを目指すのは大変です。ここで紹介するユーティリティを利用すると,そうした問題を避けることができます。Plack::Middlewareを作る際には,ぜひ活用しましょう。

Plack::Util::Accessor─⁠─アクセサの生成

Plack::Util::Accessorを利用することで,Class::Accessor::Fastのようなアクセサモジュールを継承することなく,アクセサを生成できます。

Plack::Middlewareは,次のようにロード時にオプションを渡すことができます。file_etagがオプションです。

enable "ETag",
    file_etag => [qw/size/];

ミドルウェア側からは,Plack::Util::Accessorで生成したアクセサによって値を簡単に取得できます。

use Plack::Util::Accessor qw/
    file_etag
/;

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

    my $file_attr = $self->file_etag;
}

Plack::Util─⁠─Plackに関するユーティリティ

Plack::Utilは,フレームワークやPlackサーバなどを書く際に利用できる関数が集まったクラスです。Plackアプリケーションやクラスのロードユーティリティをはじめ,PlackにおけるHTTPヘッダやレスポンスボディを操作する際に便利な関数が集まっています。その中からいくつかピックアップして紹介します。

response_cb─⁠─遅延レスポンスやストリーミングに対応する

Plackのレスポンスと言えば,要素が3つのARRAYリファレンスという認識の人も多いと思います。しかし,実は遅延レスポンスやストリーミングに対応するために,コードリファレンスを返すcallback形式のレスポンスもあります。そうしたコードリファレンスのレスポンスに対応するためのユーティリティがresponse_cbです。Plack::Middlewareの中では$self->response_cbとして利用できます。

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

    my $res = $app->($env);

    return Plack::Util::response_cb($res, sub {
        my $res = shift;
        # do something with $res;
    });
}

遅延レスポンスやストリーミングを扱わないアプリケーションの中だけであればレスポンスを直接操作しても問題ないかもしれませんが,response_cbを利用することで汎用性を持った実装ができます。

header_*関数群─⁠─HTTPヘッダを操作する

Plackレスポンスのヘッダ部分はkey-value形式で並んだARRAYリファレンスです。シンプルな構造なので直接操作してしまいそうになりますが,Plack::Utilのheader_get,header_exists,header_set,header_push,header_remove,header_iterを利用することをお勧めします。

これらの中で注意が必要なのはheader_setとheader_pushです。header_setは引数にヘッダ名と値を渡して利用しますが,すでにそのHTTPヘッダが存在すれば上書きし,なければ追加するという挙動をします。それに対してheader_pushは単純に追加するだけなので,意図せず同じヘッダが重複する可能性があります。

また,Plack::Utilのドキュメントには書かれていませんが,header_iterという関数もあります。次のようにヘッダとコールバックを渡してヘッダ全体に処理を行う場面で有用です。

Plack::Util::header_iter(
    ['Content-Type' => 'text/plain'],
    sub {
        my ($http_header_key, $http_header_value) = @_;
        # do something
    },
);

また,関数ではなくオブジェクト指向な操作ができるheadersというメソッドも用意されています。

my $headers = ['Content-Type' => 'text/plain'];
my $h = Plack::Util::headers($headers);
$h->get($key);
if ( $h->exists($key) ) {(省略)}
$h->set($key => $val);
$h->push($key => $val);
$h->remove($key);
$h->iter($code);
is_real_fh─⁠─ファイルが実体を持っているかを判定する

Plackレスポンスのボディには,実はIO::Handleのようなgetlineメソッドとcloseメソッドを備えたオブジェクトを渡すことができます。ファイルを配信する場面などでデータ全体をメモリに載せずに済むアプローチです。Plack::Util::is_real_fhは,そうしたレスポンスがファイルの実体なのかそうでないかを判定する関数です。

set_io_path─⁠─ファイルハンドルに実体のパスを設定する

ファイルの配信をアプリケーションの前段にいるlighttpdやApacheやnginxに任せるX-Sendfileというしくみがあります。アプリケーション自体による転送がなくなるので配信効率の向上が望めます。Plack::MiddlewareでX-Sendfileに対応するにはPlack::Middleware::XSendfileを利用すると簡単なのですが,このミドルウェアはレスポンスのボディにあるオブジェクトが内部ファイルパスを返すpathメソッドを持っていることを期待しています。しかし,一般的なIO::Handleのようなオブジェクトはpathメソッドを持ちません。そこでこのPlack::Util::set_io_pathを使うと,オブジェクトにpathメソッドを追加できます。

著者プロフィール

岡林大(おかばやしだい)

主にPerlでサーバサイドを書く人ですが,たまにJavaScriptでフロントも書きます。自由でロックなWebが大好き。お仕事以外ではラーメンを食べるかOSSに貢献する#yapcramenの人。

GitHub:bayashi

コメント

コメントの記入