Akihiro's Programmer Blog

Technology Notes for Personal

Amon2の流れを掴む - 2

 前回の続きから。

script/sample-server

my $app = builder {
    enable 'Plack::Middleware::Static',
        path => qr{^(?:/static/)},
        root => File::Spec->catdir(dirname(__FILE__), '..');
    enable 'Plack::Middleware::Static',
        path => qr{^(?:/robots\.txt|/favicon\.ico)$},
        root => File::Spec->catdir(dirname(__FILE__), '..', 'static');
    enable 'Plack::Middleware::ReverseProxy';

    sample::Web->to_app();
};

 前回見たPlack::Builderを使っているところですね。

 builderブロックの中で3つのMiddlewareを読み込んでいます。


enable 'Plack::Middleware::Static',
    path => qr{^(?:/static/)},
    root => File::Spec->catdir(dirname(__FILE__), '..');

 1つ目が Plack::Middleware::StaticというMiddlewareを読み込んでいます。

 Plack::Builderにenableというメソッドが定義されていて、第1引数に読み込むMiddlewareを指定して、以降の引数にそれぞれのMiddlewareに渡す値を指定しています。

 Plack::Middleware::Staticは静的配信を提供してくれるMiddlewareです。

 rootで指定したサーバーのディレクトリを起点として、pathで指定したパターンとURLがマッチすれば静的ファイルにアクセス出来ます。

 上のコードで言えば localhost:5000/static/hoge.txt とアクセスすれば ./static/hoge.txt を見ることが出来ます。


enable 'Plack::Middleware::Static',
    path => qr{^(?:/robots\.txt|/favicon\.ico)$},
    root => File::Spec->catdir(dirname(__FILE__), '..', 'static');

 2つ目も同様です。

 robot.txtとfavicon.icoをトップに配置しておきたいので、このような指定になっています。

 これで localhost:5000/robots.txtlocalhost:5000/favicon.ico にアクセスすれば、それぞれのファイルを参照出来ます。

favicon.icoはデフォルトでは配置されていないので、用意出来れば ./static 配下に置くといいでしょう。


enable 'Plack::Middleware::ReverseProxy';

 3つ目のMiddlewareである Plack::Middleware::ReverseProxyを読み込んでいます。

 これはアクセス元がプロキシサーバを介してアプリケーションサーバにアクセスする場合にも、アプリケーションサーバでアクセス元のIPアドレスを参照することが出来る仕組みを提供してくれます。

 こうすることで、意図に反して直接アプリケーションサーバにアクセスしてきた場合にアクセス制限したり出来ます。

 正直、サービス公開や運用を行ったことが無いので、あまりピンとこないし確かめてもいないので、ここの辺りは認識が正しくないかもしれません。    こちらを参考にさせてもらいました。


sample::Web->to_app();

 そして最後に、sample::Web->to_app()を実行している。

 sample::Webを見に行くと、以下のような感じになっていた。

package sample::Web;
use strict;
use warnings;
use utf8;
use parent qw/sample Amon2::Web/;
use File::Spec;

# dispatcher
use sample::Web::Dispatcher;
sub dispatch {
    return (sample::Web::Dispatcher->dispatch($_[0]) or die "response is not generated");
}

# load plugins
__PACKAGE__->load_plugins(
    'Web::FillInFormLite',
    'Web::JSON',
    '+sample::Web::Plugin::Session',
);

# setup view
use sample::Web::View;
{
    sub create_view {
        my $view = sample::Web::View->make_instance(__PACKAGE__);
        no warnings 'redefine';
        *sample::Web::create_view = sub { $view }; # Class cache.
        $view
    }
}

# for your security
__PACKAGE__->add_trigger(
    AFTER_DISPATCH => sub {
        my ( $c, $res ) = @_;

        # http://blogs.msdn.com/b/ie/archive/2008/07/02/ie8-security-part-v-comprehensive-protection.aspx
        $res->header( 'X-Content-Type-Options' => 'nosniff' );

        # http://blog.mozilla.com/security/2010/09/08/x-frame-options/
        $res->header( 'X-Frame-Options' => 'DENY' );

        # Cache control.
        $res->header( 'Cache-Control' => 'private' );
    },
);

1;

 to_appメソッドが見当たらない。

 しかし、

use parent qw/sample Amon2::Web/;

 このように Amon2::Webを継承していたので、おそらくそちらにto_appがあるだろうと予想します。

 見に行くと案の定ありました。

...
sub to_app {
    my ($class, ) = @_;
    return sub { $class->handle_request(shift) };
}

sub handle_request {
    my ($class, $env) = @_;

    my $req = $class->create_request($env);
    my $self = $class->new(
        request => $req,
    );
    my $guard = $self->context_guard();

    my $response;
    for my $code ($self->get_trigger_code('BEFORE_DISPATCH')) {
        $response = $code->($self);
        goto PROCESS_END if Scalar::Util::blessed($response) && $response->isa('Plack::Response');
    }
    $response = $self->dispatch() or die "cannot get any response";
PROCESS_END:
    $self->call_trigger('AFTER_DISPATCH' => $response);

    return $response->finalize;
}
...

 返しているのはhandle_requestの戻り値。

  shiftは何を表しているのだろうと、中身を見てみたらHTTPヘッダの情報がHashで入っていました。

 それをcreate_requestに渡して・・・と辿って行くと本懐である所の「Amon2の流れ」から外れてしまいそうなので、ここで少し省略します。決してめんど(ry

 続けて眺めて行くと、sample::Webのdispatchメソッドが呼ばれ、その戻り値が$responseに格納されています。

 $responseの中身を見てみると、Amon2::Web::Responseオブジェクトであることが分かります。

bless( {
    'body' => '<!doctype html>
    <html>
        <head>
        ...
        </head>
        <body>
        ...
        </body>
    </html>
',
    'headers' => bless( {
        'content-type' => 'text/html; charset=UTF-8',
        'content-length' => 4987
        }, 'HTTP::Headers' ),
    'status' => 200
}, 'Amon2::Web::Response' );

 この流れに関連する部分だけを見ると、handle_requestで読んでいるcall_triggerメソッドは、sample::Webのadd_triggerに対応していそうです。

# for your security
__PACKAGE__->add_trigger(
    AFTER_DISPATCH => sub {
        my ( $c, $res ) = @_;

        # http://blogs.msdn.com/b/ie/archive/2008/07/02/ie8-security-part-v-comprehensive-protection.aspx
        $res->header( 'X-Content-Type-Options' => 'nosniff' );

        # http://blog.mozilla.com/security/2010/09/08/x-frame-options/
        $res->header( 'X-Frame-Options' => 'DENY' );

        # Cache control.
        $res->header( 'Cache-Control' => 'private' );
    },
);

 確認してみると、call_triggerが呼ばれるとadd_triggerに書いてある各HTTPヘッダーが$responseに追加されているのが分かります。

call_trigger前
---------------
...
'headers' => bless( {
    'content-type' => 'text/html; charset=UTF-8',
    'content-length' => 4987
}, 'HTTP::Headers' ),
...


call_trigger後
---------------
...
'headers' => bless( {
    'x-frame-options' => 'DENY',
    'content-type' => 'text/html; charset=UTF-8',
    'cache-control' => 'private',
    'set-cookie' => [
        'hss_session=1401559592%3A6082333eb23b4c2027e90d74bca60cc%3ABQgDAAAAAQiHAAAAB2NvdW50ZXI%3D%3A61663362363030323632636236313739336633376433326266383734636439616163643065383430; path=/; HttpOnly',
        'XSRF-TOKEN=6082333eb23b4c2027e90d74bca60cc; path=/'
    ],
    'x-content-type-options' => 'nosniff',
    'content-length' => 4987,
    '::std_case' => {
        'x-frame-options' => 'X-Frame-Options',
        'set-cookie' => 'Set-Cookie',
        'x-content-type-options' => 'X-Content-Type-Options'
    }
}, 'HTTP::Headers' ),
...

 そして、最後の

$response->finalize;

をすることで、$responseに格納してあった情報を配列の形式に落とし込んでいます。

[
    200,       #status_code
    [
        ...    #headers
    ],
    [
        ...    #body
    ],
]

 つまり、sample::Web->to_app();で返されていたのは、 ステータスコード、HTTPヘッダー、HTMLの情報を持つ配列であることが分かりました。


 続きます。

 複雑になってきて頭混乱してきました。