たぷつきません

おなかがでてきた。もうたぷついてるやん。

PageRedirectExceptionを発生させるタイミングについての罠


Tapestryで表示ページをリダイレクトして切り替える便利なPageRedirectException例外ですが、throwさせる場所には注意が必要です。昨日同僚が抱えていた問題で明らかになりました。問題とは、
submitの中であればOKだけど、PageBeginRender(PageRenderListenerをインプリ)内で PageRedirectException するとリダイレクトされずに普通にインターナルサーバーエラーになってしまう…という問題でした。


拙作の自動ログイン機構()と組み合わせると起きるという不本意な状況だったのでどれどれと見たところ、TapestryのAbstractEngine.handlePageRedirectException のロジック上の問題でした。

結論から先に言えば、

リダイレクト先(PageRedirectExceptionの引数のページ)のrenderResponseから流れる一連のメソッドから、再びリダイレクトしてはならない

…というのが今回得たナレッジとなります。


handlePageRedirectException では PageRedirectException例外のチェインに対応していますし、無限循環リダイレクトをストップする安全な仕組みにもなっています。なかなか賢く作られています。例外の名前からするとリダイレクトだからHTTPステータスをTemporaryMovedで返すだけに見えますが、実際にはForward的な動作となっています。

具体的には受け取ったリダイレクト先で、cycle.activate(リダイレクト先)としつつ、それを try { } catch(PageRedirectException) {} で括り、例外が発生する限りリダイレクト先のactivateを繰り返しています。activateでPageRedirectException例外が発生しないことを確認するとループを抜けて、その対象ページのrenderResponse を行います。ところがこの renderResponse は try { } catch(PageRedirectException) {} で括られていないため、その中で発生するとサーブレットコンテナまで例外が飛んでいってしまうという仕組みになっていたのです。

このように、cycle.activateはhandlePageRedirectException 内で「循環チェック付きリダイレクトチェインのループ」とでも言うべきものが働きますので、throwするならcycle.activate内でやりましょう。そのための解決策としては、

PageRedirectExceptionは、ページの validate で throwする


…です。


こちら↓


http://www.saisse.jp/pukiwiki/pukiwiki.php?Tapestry%2Fknowledge


を見ても分かるとおりどのようなケースでもvalidateは走りますし、cycle.activateの中で呼び出されますので、うってつけです。

最初にあげた PageRenderListener のようにあるインターフェースを実装したら呼ばれるようにしたいのであれば、PageRenderListener の代わりに、

interface PageValidateListener  {
void pageValidate(PageEvent event);
}
を実装する

のがモアベター




※自動ログイン機構
適当なページに直リンクしようとしても先に決めておいたログイン画面に飛んで、認証成功を以って、最初に入ろうとしていたリンクに遷移するというEngineクラス(というかextends RequestCycle)です。S2+S2Tapestryと組み合わせたものであれば、RequestCycleのactivateをインターセプトしてEngineのcreateRequestCycleに外からインジェクトするものも用意してあったりします。基本的にはフォーム認証が普通でしょうが、認証方式はどのようなものでも構わず特定のインターフェースをログイン画面が実装すれば良いだけになっていますので、Tapestry内である限り、j2eeのものと比べればとても楽です。「ユーザー、パスワード」という概念さえありませんし、クッキーログインしても構わないようになっています。
このほかにactivateをインターセプトする別の機構として、ページ進入判定機構というものがあります。こちらは PageFirstEnter(IRequestCycle cycle), PageChangeEnter(IRequestCycle cycle, IPage prevPage) というメソッドをもった PageEnterListener をページクラスに実装するだけで、遷移元が分かるようになっています。これらは同じページからの戻りの場合は発生しません。もちろんこれらのメソッド内からであれば、PageRedirectExceptionを発生させても不具合は発生しません。別のページからの移動、もしくは直リンクなどRefererが同じTapestryアプリでないところからの移動に反応して発生します。…という拙作のものが、自社内ライブラリに入っています。Tapestry標準の話ではないのであしからず。