mod_perl2 の $r->path_info 等のクセ

Apache2::RequestRec#path_info() 等を触っててもどうも安定しないというか望み通りのものが得られないなぁと思っていたんで調べてみました*1

まずは Apache2::RequestRec のリファレンスから。

path_info

Get/set the PATH_INFO, what is left in the path after the URI --> filename translation:

(略)

filename

Get/set the filename on disk corresponding to this response (the result of the URI --> filename translation).

(略)

この下線部がにおいます。なので,実験。

<Location /sandbox/abc>
    SetHandler perl-script    # modperl でもいいけど
    PerlResponseHandler Sandbox::PrintPathname
</Location>

のような設定で,$r->path_info() を出力するとどうなるのか。

http://〜/sandbox/abc/def/ghi/jkl にアクセスしたとき,

$r->path_info() == '/abc/def/ghi/jkl'

が戻ります。個人的には /def/ghi/jkl が戻って欲しいんですが。

ここで,DocumentRoot 配下に sandbox というディレクトリを掘ると,

$r->path_info() == '/def/ghi/jkl'

が戻ります(おお,望む結果だ)。

さらに,sandbox/abc を掘ると,

$r->path_info() == '/ghi/jkl'

Location も越えて sandbox/abc/def を掘ると,

$r->path_info() == '/jkl'

が戻ります。ファイルシステム上にパスが存在する限り,とことん下っていく模様です。

なので,上記のような例で SCRIPT_NAME を <Location> と同じく /sandbox/abc にしたい場合,その一個上の /sandbox まで物理的なディレクトリを掘るのがベストということで。余計に掘りすぎないよう注意。


というのもあんまりなので,代替案を考えてみました。Apache2::RequestUtil に $r->location() という関数が定義されています。こいつを使うとまさに <Location> の値を取得することができます。

じゃあこれで万事解決か,と思いきや,

<LocationMatch "/sandbox/(abc|def)">
    〜
</LocationMatch>

のような設定では,$r->location() に '/sandbox/(abc|def)' という正規表現込みのデータが返ります。がーん。

というわけで,どうしても汎用的にしたい,という場合,自分でなんらかのロジックを書く必要があります。

SCRIPT_NAME を取得したい場合,

use Apache2::RequestRec  ();
use Apache2::RequestUtil ();

my $script_name = $r->uri;
my $location    = $r->location;

if ($script_name =~ m/ \A \Q$location\E /xms) {
    # <Location>      タグを利用しているか
    # <LocationMatch> タグで正規表現を利用せずにマッチした場合
    $script_name = $location;
}
elsif ($script_name =~ s{ ( $location ) .* \z }{$1}xms) {
    # <LocationMatch> タグでマッチした場合
}
else {
    # 不明な状況なので $r->path_info を利用する
    my $path_info = $r->path_info;
    $script_name =~ s{ \Q$path_info\E \z }{}xms;
}

たとえばこんなコードになるかと。

*1:以前も調べたんですが新しい知見も得られたんでご容赦