Catalystでハマった
あるControllerで、指定の条件下ではアクションを実行させず404にしたい、というときにbeginとかautoで
package MyApp::Controller::Hoge; use Moose; use namespace::autoclean; BEGIN { extends 'Catalyst::Controller' } sub begin :Private { my ($self, $c) = @_; $c->detach('/default'); } sub index :Path :Args(0) { my ($self, $c) = @_; $c->log->debug('hoge'); $c->detach('/default'); $c->log->debug('fuga'); } __PACKAGE__->meta->make_immutable; 1;
のように書いて、
package MyApp::Controller::Root; use Moose; use namespace::autoclean; BEGIN { extends 'Catalyst::Controller' } sub default :Path { my ( $self, $c ) = @_; $c->response->body( 'Page not found' ); $c->response->status(404); } sub end : ActionClass('RenderView') {} __PACKAGE__->meta->make_immutable; 1;
の'/default'にdetachするように書いてみる。/hoge/begin内でdetachするので/hoge/indexには到達しない、と思っていたらどうやら実行されてしまうらしい。
[debug] "GET" request for "hoge" from "127.0.0.1" [debug] Path is "hoge" [debug] hoge [debug] Response Code: 404; Content-Type: text/html; charset=utf-8; Content-Length: 14 [info] Request took 0.010449s (95.703/s) .------------------------------------------------------------+-----------. | Action | Time | +------------------------------------------------------------+-----------+ | /hoge/begin | 0.002575s | | -> /default | 0.001170s | | /hoge/index | 0.001678s | | -> /default | 0.000795s | | /end | 0.000408s | '------------------------------------------------------------+-----------'
index内でdetachしたあとの部分は実行されない。
beginとかautoとかの挙動を把握できていない。ちょっとソースを読んでみた…
package Catalyst::Controller; ... __PACKAGE__->_dispatch_steps( [qw/_BEGIN _AUTO _ACTION/] ); ... sub _DISPATCH : Private { my ( $self, $c ) = @_; foreach my $disp ( @{ $self->_dispatch_steps } ) { last unless $c->forward($disp); } $c->forward('_END'); }
というところで"_BEGIN", "_AUTO", "_ACTION"が順番に実行されるらしい。$c->forward("_BEGIN"), $c->forward("_AUTO"), $c->forward("_ACTION") それぞれ返り値が真であれば順番に実行される、ということのようだ。それぞれ途中で$c->detach('/default')された場合に何が起こるのか…はCatalyst::executeを追いかければいいのかな? どうも最終的に返って来るのは'/default'の返り値のようだ。
sub default :Path { my ( $self, $c ) = @_; $c->response->body( 'Page not found' ); $c->response->status(404); }
と書いてある場合、"_DISPATCH"で呼ばれる$c->forward("_BEGIN")の結果は404になるっぽい。ので、ここで偽値が返るよう
sub default :Path { my ( $self, $c ) = @_; $c->response->body( 'Page not found' ); $c->response->status(404); return 0; }
と書いてやればbegin内のdetachでそれ以降のアクションが実行されないようになるようだ。
[debug] "GET" request for "hoge" from "127.0.0.1" [debug] Path is "hoge" [debug] Response Code: 404; Content-Type: text/html; charset=utf-8; Content-Length: 14 [info] Request took 0.008613s (116.104/s) .------------------------------------------------------------+-----------. | Action | Time | +------------------------------------------------------------+-----------+ | /hoge/begin | 0.003071s | | -> /default | 0.001199s | | /end | 0.000442s | '------------------------------------------------------------+-----------'
また、"_AUTO"では
sub _AUTO : Private { my ( $self, $c ) = @_; my @auto = $c->get_actions( 'auto', $c->namespace ); foreach my $auto (@auto) { $auto->dispatch( $c ); return 0 unless $c->state; } return 1; }
と返り値(?)をみているので("_BEGIN"では$c->errorしかみていない)、'/default'が偽値を返さなくても
sub auto :Private { my ($self, $c) = @_; $c->forward('/default'); return 0; }
と書くことでその後の"_ACTION"に処理が進まないように指定できる。
[debug] "GET" request for "hoge" from "127.0.0.1" [debug] Path is "hoge" [debug] Response Code: 404; Content-Type: text/html; charset=utf-8; Content-Length: 14 [info] Request took 0.005840s (171.233/s) .------------------------------------------------------------+-----------. | Action | Time | +------------------------------------------------------------+-----------+ | /hoge/auto | 0.001469s | | -> /default | 0.000943s | | /end | 0.000376s | '------------------------------------------------------------+-----------'
一方、Arkは
Arkの場合は、begin, autoどちらの場合もdetachするとそれ以降のアクションは実行されない。
sub begin :Private { my ($self, $c) = @_; $c->detach('/default'); }
.----------------------------------------------------------------+-----------. | Action | Time | +----------------------------------------------------------------+-----------+ | /hoge/begin | 0.002738s | | -> /default | 0.000226s | '----------------------------------------------------------------+-----------'
sub auto :Private { my ($self, $c) = @_; $c->detach('/default'); }
.----------------------------------------------------------------+-----------. | Action | Time | +----------------------------------------------------------------+-----------+ | /hoge/auto | 0.002725s | | -> /default | 0.000241s | '----------------------------------------------------------------+-----------'
Arkのなかの処理は、
package Ark::Action; ... sub dispatch { my ($self, $context, @args) = @_; return if $context->detached; ... } sub dispatch_chain { my ($self, $context) = @_; $self->dispatch_begin($context) and $self->dispatch_auto($context) and $self->dispatch($context); $context->detached(0); $self->dispatch_end($context) unless $context->res->is_deferred or $context->res->is_streaming; }
となっていて、dispatch_beginにしろdispatch_autoにしろdispatchの一番最初に$context->detachedを見て既にdetachされている状態だったら処理しない、という仕組みになっているおかげで、こういう挙動になるようだ。