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.txt と localhost: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の情報を持つ配列であることが分かりました。
続きます。
複雑になってきて頭混乱してきました。