Catalyst::Plugin::Static::Simple と mod_perl の食い合わせ

Catalyst::Plugin::PageCache を使って caching をするようにしたら、 何故か時々 Internal Server Error が起きる。

access_log にはこのような記録。何故に HTTP Status が '1' なのだ?

*.*.*.* - - [03/Mar/2006:05:17:05 +0900] "GET / HTTP/1.1" 1 757

どうも、PageCache が 304 (Not Modified) を返そうとしたときにこうなるらしい。

調査した結論から言うと、

  • Catalyst::Plugin::Static::Simple をロードしている
  • mod_perl 環境で動かしている
  • $c->res->status に /^(1\d\d|[23]04)$/ な値をセットする

上記の条件を全て満たす場合に、エラーが起きる。

mod_perl 環境で Static::Simple を使用する積極的な理由はない (静的ファイルは Apache に serve させたほうがいい) ので、mod_perl で動かす場合は Static::Simple をロードしない、という方法で回避可能。

具体的には、

package MyApp;
use Catalyst qw/-Debug ConfigLoader Static::Simple/;
__PACKAGE__->setup;

こうではなくて、

package MyApp;
use Catalyst;
our @plugins = qw/-Debug ConfigLoader/;
push @plugins, 'Static::Simple' unless $ENV{MOD_PERL};
__PACKAGE__->setup(@plugins);

こうやって、環境変数を見て Static::Simple を使うかどうか決めてやるのが良さそう。

で、以下が原因の詳細。(VERSION は 5.64)

Static::Simple は finalize で以下のような処理をしている

    if ( $c->res->status =~ /^(1\d\d|[23]04)$/xms ) {
        $c->res->headers->remove_content_headers;
        return $c->finalize_headers;
    }

status が /^(1\d\d|[23]04)$/ にマッチする場合、finalize_headers を実行して return してしまうので、他の finalize が実行されない。 これが、mod_perl 環境では困る。

何故かというと、C::P::Engine::Apache には以下のようなコメントがあって……

    # The trick with Apache is to set the status code in $apache->status but
# always return the OK constant back to Apache from the handler.

mod_perl の handler 関数では戻り値は Apache::Const::OK (等) の値 (実は 200 とか 304 とかの HTTP Status code の数値) でなくてはならず、 1 を返したりしてはいけないのだ。

ちゃんと Catalyst::finalize (一番親の finalize) までたどり着けば、戻り値は $c->res->status になるのだが、finalize_headers で return してしまうと、その値が handler の戻り値になってしまう。で、こける。

これは Static::Simple の問題なのか、それとも finalize_headers で最終的に 1 が返るというのが問題なのか……まあどっちにしろ困るのだ。

ということで、mod_perl 環境下で動かす場合は Static::Simple を使わない、というのが簡単な回避法。