indexアクションへのtrailing slash

筆者は、なるべくデザインをローカル(つまりfileプロトコル)で確認できるように、外部JS/CSS/画像へのパスは絶対パスではなく相対パスで書くようにしています。また、少し話はズレますが、あるアクション固有の静的リソースの配置を

/action/
/images/action/foo.png
/js/action/bar.js

ではなく

/action/
/action/images/foo.png
/action/js/bar.js

として、/action/内のHTMLには「./images/foo.png」「./js/bar.png」と書くようにしています。
/action/以下を丸ごとアーカイブして他所へ持っていってもそれなりに見えたり見えなかったり
新規に作り直したいと思ったら、色々ファイル名を変更しなくても

% mv ./action ./action_old
% mkdir ./action

とするだけでまっさらになります(筆者はこんなことしませんが、デザイナーの中にはこれやる人・やりたい人が多い気がします)


まぁぶっちゃけそんなにメリットがあるとは思ってないわけですが、この疎結合感が気持ちいいのです(*´д`*)ハァハァ



ところで、相対パスで書くことによって問題になるのは、パスの基点が今見ているページのURIになるということです。
例えばHTMLソース中に

<img src="./images/foo.png">

と書くとします。すると、

http://localhost:3000/action
http://localhost:3000/action/

のようにtrailing slash(末尾の/)の有無によってそれぞれ

http://localhost:3000/images/foo.png
http://localhost:3000/action/images/foo.png

と、違うリソースを指し示すことになり、デザインが崩れてしまいます。

また同じ処理をして同じ結果を返すURIが「trailing slash有り・無し」の二つができてしまうのも嫌なのです。



これを解消するために、Root.pmのautoアクションに「リクエストがindexアクションで、パス名が/で終わっていなかったらリダイレクト」という処理を入れています。

package MyApp::Web::Controller::Root;

use base qw(Catalyst::Controller);

use HTTP::Status;

__PACKAGE__->config->{namespace} = q{};

sub auto : Private {
    my ( $self, $c ) = @_;

    if (   $c->action->name eq 'index'
        && $c->req->method eq 'GET'
        && ( rindex $c->req->path, q{/} ) != ( length $c->req->path ) - 1 )
    {
        $c->res->status(RC_MOVED_PERMANENTLY);
        my $uri = $c->req->uri;
        $uri =~ s{(\?|\z)}{/$1}msx;
        $c->res->location($uri);
        return 0;
    }

    return 1;
}

こうしておくと、/actionへのアクセスには301 Moved Permanentlyが返り、/action/へリダイレクトするようになります。
※POSTの場合は良いのか?という話ですが、どうせ処理後にリダイレクトするので問題無いです(PRGパターン)。PUT・DELETEも同様です。
indexアクションにtrailing slash無しでアクセスするのはバグの一種だという捉え方なので、RC_MOVED_PERMANENTLYの代わりにRC_NOT_FOUNDやRC_INTERNAL_SERVER_ERRORを返してもいいかなーとか思ったり。

WebServer側でやっても良いのですが、WebServerを立てていなくても(script/myapp_server.plで開発している時でも)同じ挙動をするようにという意図があります。



最近採用したばかりなのでちょっと怪しい感じもあります。突っ込みどころ満載です。何か問題が出る度に考え直すつもりです。アホだなあと思ったら、優しく教えてください。