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

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

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

Amon2のソースを読む(2)

perl+web

今日はAmon2-2.49のコンテキスト周りで、具体的には Amon2、Amon2::Web、Amon2::WebRequest、Amon2::WebResponse、ついでにAmon2::Config::Simple 。Dispatcher はこれらと粗結合なので、次回以降。他残っているのは、scaffoldのコードと、トリガーとプラグイン周り。

Amon2::Web と Amon2 の関係

Amon2::Web はAmon2(コンテキスト) を継承しておらず、コンストラクタも持っていない。YourProject::Web ではAmon2 と Amon2::Web の双方を継承して使う。という構造を考えると、Amon2::Web はAmon2 のサブクラスに対する Mix-in として働いていると見た方がわかりやすい。

Amon2::Web

scaffoldの YourProject/app.psgi内では YourProject::Web 内のto_app を呼んでPSGIアプリケーションを作成しているが、このメソッドの定義元はAmon2::Webであり、Amon2::Web がPSGIリクエストを処理する際のエントリポイントになっている。


生成されるPSGIアプリケーションは単純な物で、Amon2::Web::Request を作ってYourProject::Web->new をして、dispatch() を呼び出すだけ。ここで、Amon2::Web は1リクエスト1インスタンスのモデルを取っていることがわかる。dispatchを呼ぶ前後には BEFORE_DISPATCH と AFTER_DISPATCH という二つのトリガーがあり、呼ばれる。BEFORE_DISPATCH や dispatch() から返すレスポンスはPlack::Response のサブクラスを想定しており、Amon2::Web::Response もその1つとなっている。


YourProject::Web 内からレスポンスを返す方法として、Amon2::Web はredirect、res_404、renderの3つの方法を提供している。res_404 が最も単純で、404 Not Foundの画面を返すもの。これはAmon2::Web::Dispatcher:: 以下のクラスでも使っている。htmlもres_404 メソッドの中で定義されているので、404画面を置き換えたければYourProject::Web 内でこのメソッドをオーバーライドするのがいいと思われる。redirect はQUERY_STRING の扱いのためにコードは長いが302のレスポンスを返す以外の特別なことはしてない。render はcreate_view->render するだけ。create_view はYourProject::Web で自分で定義する必要がある(scaffoldに入っている)。HTML_FILTER のトリガはここで処理されるので、render 以外の方法でレスポンスを返すとHTML_FILTER トリガは呼ばれない。


Amon2::Web のやってることはこれだけ。リクエストをルーティングしたいときはdispatchメソッド内でAmon2::Web::Dispatcher:: へ処理を委譲する。その他の処理はプラグインをロードすることで実現する。その辺はscaffold にもデフォルトで書いてあるので、必要に応じていじくるとよい。


あと、Amon2::Web 内のencoding はhtmlやURLに含めるパラメータのエンコーディングを決めているので、UTF-8以外の文字コードを使いたければ encoding と html_content_type のメソッドをオーバーロードする。

Amon2::Web::Request, Response

Amon2::Response はマンマ Plack::Response。Amon2::Request はがっつりコードがあるように見えるが、これはparameters 系のメソッドを(バイト列ではなく)文字列を返すようにしているだけ。Plack::Request と同じ値が欲しければ、parameters_raw などを用いる。


ところで、Amon2::Web->url_for と Amon2::Web::Request->url_with の二つが存在してこれらがテンプレから使えるように YourProject::Web で設定されるが、これらは全く別もの。前者はベースURLを元にURLを作る物で、このアプリがルートにデプロイされない場合はこのメソッドを通してURLを作る必要がある。後者は現在のリクエストを元にQUERY_STRINGの部分だけ置き換え(もしくは追加した)リクエストを作る物で、Catalystに由来する。

Amon2

最後に、Amon2 クラスだが、これはコンテキストを表すクラス。言い換えれば、CLIでも使える機能をAmon2::Webから分離した物とも言え、プラグインのロードやコンフィグへのアクセスなどの機能を提供する。Amon2->context で現在のコンテキストを取得可能であり、WEBアプリだとYourProject::Web のインスタンスであり、CLIアプリだとAmon2クラス自身のインスタンスが入っている。


コンフィグ周りはAmon2::Config::Simple が面倒を見ているが、こいつはAmon2->mode_name と同じ名前の*.pl をdo して返す。この振る舞いが気に食わなければAmon2->mode_name とか Amon2->load_config をオーバーライドして動きを変えればよい。


load_plugin は指定されたプラグインをrequire してパラメータ(hash-ref)付きでinit() を呼ぶ。デフォルトでは Amon2::Plugin 以下のモジュール、+付きだと任意のパッケージ名のモジュールをロードするが、この仕事をしてるのは2.49の実装だとPlack::Util。プラグインはinit()メソッド内でコンテキストにメソッドを追加したりトリガを仕掛けたりして動作を拡張する。


load_plugins は一度に複数をload_plugin できる。Data::OptList::mkopt を噛ませてるお陰で、モジュール名とパラメータを混ぜた形式で指定ができる。

# YourProject::Web より
__PACKAGE__->load_plugins(
    'Web::FillInFormLite',
    'Web::NoCache', # do not cache the dynamic content by default
    'Web::CSRFDefender',
    'Web::HTTPSession' => {
        state => 'Cookie',
        store => HTTP::Session::Store::File->new(
            dir => File::Spec->tmpdir(),
        )
    },
);


ちなみに、base_dir と config は、2度目以降の呼び出しを高速にするために、初回実行時にサブクラス側で同名のメソッドを生やし、オーバーライドして自身を隠している。