Last Modified:
Release Slack-v0.8.0 #Perl #Slack
この文書は以前のバージョンv0.7.0とv0.8.0の変更点を記述しています。
このリリースには以前のバージョンと互換性の無い変更が含まれています。
インターフェースの追加
コード節に共通の前後処理を記述できるようになりました #16
コード節において、各コード共通の前処理および後処理をそれぞれ特殊なメソッド名^および$を利用して記述できるようになりました。
action crud => qr/(.+)/ => {
'^' => sub {
res->stash->{item} = c->action->controller->prepare( id => req->argv->[0] );
},
GET => sub {
res->stash->{item}->select;
},
PUT => sub {
res->stash->{item}->update( req->body_parameters );
},
DELETE => sub {
res->stash->{item}->delete;
},
'$' => sub {
res->stash->{item}->execute;
},
};
以前のバージョンではこのインターフェースが無かったため、同じ記述を繰り返す必要がありました。
action crud => qr/(.+)/ => {
GET => sub {
res->stash->{item} = c->action->controller->prepare( id => req->argv->[0] );
res->stash->{item}->select;
res->stash->{item}->execute;
},
PUT => sub {
res->stash->{item} = c->action->controller->prepare( id => req->argv->[0] );
res->stash->{item}->update( req->body_parameters );
res->stash->{item}->execute;
},
DELETE => sub {
res->stash->{item} = c->action->controller->prepare( id => req->argv->[0] );
res->stash->{item}->delete;
res->stash->{item}->execute;
},
};
インターフェースの変更
各アクションの役割に関する仕様が整理されました #18
prep,action,viewの各アクションがどういう役割を担うべきか、という仕様が整理されました。
prepは事前処理を行なうために存在するよう定められました。- 無条件で次の
prepの探索が継続され、複数個のprepが実行されます。
- 無条件で次の
actionはres->statusを定義するために存在するよう定められました。actionの探索開始より前に(つまりprepで)既にres->statusが定義されていると、actionの探索は行なわれません。res->statusが定義されない間、次のactionの探索が継続されますが、パス条件節(PATH_INFO,/,.)のいずれかがマッチすると必ずres->statusは何らかの値で定義されます。つまりパス条件節のいずれかがマッチするとactionの探索は終了することになります。
viewはres->bodyを定義するために存在するよう定められました。viewの探索開始より前に(つまりprepまたはactionで)既にres->bodyが定義されていると、viewの探索は行なわれません。res->bodyが定義されない間、次のviewの探索が継続され、複数個のviewが実行されます。
別の側面から見てみると、viewが呼び出されるときには必ずres->statusに何らかの値が入っていることになります。
prepでres->statusに適当な値を設定しておいてactionをスキップし、res->statusをviewで参照・処理することが出来ます。
これは例えば認証機能の実装に役立つでしょう。
prep auth => sub {
my $self = c->action->controller;
if ( not $self->authenticate ) {
res->status(0);
}
elsif ( not $self->authorize ) {
res->status(1);
}
};
action admin => sub {
res->stash->{message} = 'you are an administrator';
};
view default => sub {
if ( res->status == 0 ) {
res->status(HTTP_FOUND);
res->redirect('/login');
}
elsif ( res->status == 1 ) {
res->status(HTTP_FORBIDDEN);
res->body('you do not have the permission');
}
else {
res->body( res->stash->{message} );
}
};
res->statusに数値以外を設定することもできるかもしれませんが、それを推奨するようなことはしません。
コード節にコードリファレンスを渡した場合のRequest Methodの補完が変更されました #15
コード節の実体は{ Request Method => sub { } }ですが、コードリファレンスを渡すこともできます。
コード節にコードリファレンスを渡した場合は条件を指定していないと見なされ、全てのRequest Methodにマッチします。
ただし、actionの場合に限りGETのみにマッチするよう補完されます。
prep prep => sub { # any Request Method
c->action->controller->authenticate or die;
};
action action => sub { # only GET Request Method
res->stash->{message} = 'you are authenticated';
};
# # equivalent to
# action action => {
# GET => sub {
# res->stash->{message} = 'you are authenticated';
# },
# };
view view => sub { # any Request Method
c->app->{template}->process;
};
これは全てのRequest Methodに同じ振る舞いをさせないための措置です。
仮にactionも全てのRequest Methodにマッチすることを考えてみましょう。
その場合、GETのみに対応するアクション(これは往々にして存在します)は、action hello => { GET => sub { } }と記述する必要がありますが、怠惰な開発者によってaction hello => sub { }と記述されることでしょう。
開発者は恐らくGETだけに対応したつもりでしょうが、全てのRequest Methodで(たとえDELETEだったとしても!)同じ結果になってしまいます。
それではコードリファレンスを許容するというシンタックスシュガーを廃止すれば良いのではと思うかもしれません。
しかしaction hello => { GET => sub { } }よりaction hello => sub { }の方が格好良いと思いませんか?
以前のバージョンではprep,action,view全てにおいて、コード節にコードリファレンスを渡した場合に補完されるRequest MethodはGETでした。
prep,viewはRequest Methodが異なっていても同じ処理になる場合が多く、同じ記述を繰り返す必要がありました。
prep prep => {
GET => sub { c->action->controller->authenticate or die; },
POST => sub { c->action->controller->authenticate or die; },
PUT => sub { c->action->controller->authenticate or die; },
DELETE => sub { c->action->controller->authenticate or die; },
};
action action => sub {
res->stash->{message} = 'you are authenticated';
};
view view => {
GET => sub { c->app->{template}->process; },
POST => sub { c->app->{template}->process; },
PUT => sub { c->app->{template}->process; },
DELETE => sub { c->app->{template}->process; },
};
条件節を省略した場合の補完が変更されました
前項に合わせて条件節の補完も見直されました。
条件節を省略した場合は条件を指定していないと見なされて{}が補完され、全てのリクエストにマッチします。
ただし、actionの場合に限りアクション名にマッチするよう補完されます。
これは単にCatalystのLocalを真似てaction hello => sub { }が/helloにマッチする方が自然だというだけです。
prep prep => sub { };
action action => sub { };
view view => sub { };
# are equivalent to
prep prep => {} => sub { };
action action => { '/' => 'action' } => sub { };
view view => {} => sub { };
以前のバージョンではprep,action,view全てにおいて、条件節を省略した場合に補完されるのは{ '/' => 'アクション名' }でした。
prep,viewは全てのリクエストに対応することが多く、そのための条件節がこの変更によって省略できるようになりました。
複数の拡張子への対応が自然に記述できるようになりました
複数の拡張子(例えばindex.ja.xml.gz)に対応する条件節が自然に記述できるようになりました。
パス条件として{ '.' => 'ja' }を記述すると、.jaが含まれたリクエストにマッチします。
拡張子の順序は気にする必要がありません。
index.ja.xml.gz,index.xml.ja.gz,index.xml.gz.ja全てのリクエストは{ '.' => 'ja' }にマッチします。
逆に言うとパスの途中に現れてはいけない拡張子については、それ用に処理を記述する必要があります。
例として.ja,.xml,.gzに対応するpseudo実装を示します。
action index => sub {
res->stash->{message} = 'hello, world';
res->stash->{formatter} = \&sprintf;
};
view view_ja => { '.' => 'ja' } => sub {
res->stash->{message} = maketext( res->stash->{message} );
};
view view_xml => { '.' => 'xml' } => sub {
res->stash->{formatter} = \&XMLout;
};
view view_gz_finalize => { '.' => 'gz' } => sub {
if ( req->env->{PATH_INFO} !~ /[.]gz\z/ ) { # .gz shoud be last extension
res->status(400);
res->body(q{});
return;
}
my $fh = IO::Compress::Gzip->new( \my $output );
$fh->syswrite( res->stash->{formatter}->( res->stash->{message} ) );
$fh->close;
res->body($output);
};
view view_any_finalize => sub {
res->body( res->stash->{formatter}->( res->stash->{message} ) );
};
view view_any_finalizeはview view_gz_finalizeより後方に記述する必要があることに注意してください。- このpseudo実装は
/index( (\.ja)?(\.xml)?(\.gz)? | (.xml)?(.ja)?(.gz)? )\z/なリクエストに対して正常なレスポンスを返します。 .gzが途中に含まれるのを拒否するために、PATH_INFOをチェックしています。 内部実装の都合上、req->env->{'.'} = '.ja.xml.gz'になっていますが、これを頼るべきではありません。将来的に変更される可能性があります。- 例えば
index.xml.xmlのように同一拡張子が複数個含まれていても、対応するアクションview view_xmlはただ1度だけ呼び出されます。
以前のバージョンでは単一の拡張子のみを想定しており、複数の拡張子に対応するには{ '.' => qr/(?:ja|xml|gz)(?:[.].+)*/ }のようなトリッキーなパス条件とアクション内での分岐の記述が必要でした。
また、この変更によって{ '/' => 'empty', '.' => 'json' }と{ PATH_INFO => qr{.*/empty.json\z} }は等価ではなくなりました。
PATH_INFOは変更されなくなりました
req->env->{PATH_INFO}は初期状態のまま一切変更されなくなりました。
その副作用として、内部実装の都合上req->env->{'/'}とreq->env->{'.'}が追加されています。
以前のバージョンではactionの探索をしている間のみreq->env->{PATH_INFO}が拡張子を取り除いた状態になっていました。
そのため、条件節に.を記述した場合の影響範囲が広く、また複雑に絡まっていて混乱していました。
副作用としてactionの条件節に.の指定ができるようになりました。
しかしactionはビューに関わる拡張子についてとやかく言うべきではありませんので、なるべくactionに拡張子を意識させないことを推奨します。
パス条件がマッチしたときの挙動が変更されました #19
パス条件PATH_INFO,/,.のいずれかがマッチしたが、それ以外の条件でマッチしなかった場合においては、HTTP_NOT_FOUND扱いではなくなりました。
少し分かり難いですか?具体的にどうなるかを説明します。
この変更はprep,viewに影響はありません。
actionの探索において
- いずれかのパス条件がマッチしなかった場合、次の
actionの探索が継続されます。 - 全てのパス条件がマッチした場合
- パス条件以外の条件のいずれかにマッチしなかったら
HTTP_METHOD_NOT_ALLOWEDもしくはHTTP_BAD_REQUESTが設定されます。
以前のバージョンでは、次のactionの探索を継続していました。 - パス条件以外の条件に全てマッチしたらアクションコードが呼び出され、(
res->statusが未定義であれば)HTTP_OKが設定されます。
- パス条件以外の条件のいずれかにマッチしなかったら
インターフェースの削除
ありません。
実装の変更
デバッグ出力のアクションテーブル表示における正規表現が簡略化されました
フラグは全て取り除かれ、いくつかのエスケープシーケンスや括弧も取り除かれ、実用上、概ね問題が無いと思われる状態まで簡略化されます。 そのため実際のマッチングに使用する正規表現と等価ではありません。
正確な正規表現を見たい場合はSmart::Commentsのレベルを上げてください。($ENV{Smart_Comments}に####を含めるか、もしくは1を設定する)
そうすれば### try:の出力行によって実際にマッチングに使用している正規表現を見ることができるでしょう。
リテラルが多過ぎるとソースフィルターが失敗するバグを修正 #13 #14
Filter::Simple::FILTER_ONLYを使用する場合、リテラルがエスケープされることがあります。
エスケープされた部分を誤って壊してしまうと致命的エラーが発生します。
これは$Filter::Simple::placeholderを置換対象から除外することによって回避されました。