Plack::Builder+Plack::Middleware::Staticで Plack::App::Directoryみたいな事をやってみる。

そんなわけでとりとめもない記事です。

ディレクトリ構成

こんな感じ

static.psgi
root/
root/index.html
root/xxx.html
root/hoge/index.html
root/hoge/xxx.html

psgi

% cat static.psgi
use Plack::Builder;builder {
enable "Plack::Middleware::Static",
    path => sub { s!(.*/$)!${1}/index.html! or return qr{^/.+} },
    root => './root/';
};

結果

解説

Plack::Middleware::Staticの_handle_static()を見てみます。

package Plack::Middleware::Static;
use strict;
use warnings;
use parent qw/Plack::Middleware/;
use Plack::App::File;

use Plack::Util::Accessor qw( path root encoding pass_through );

...

sub _handle_static {
    my($self, $env) = @_; 

    my $path_match = $self->path or return;
    my $path = $env->{PATH_INFO};

    for ($path) {
        my $matched = 'CODE' eq ref $path_match ? $path_match->($_) : $_ =~ $path_match;
        return unless $matched;
    }   

    $self->{file} ||= Plack::App::File->new({ root => $self->root || '.', encoding => $self->encoding }); 
    local $env->{PATH_INFO} = $path; # rewrite PATH
    return $self->{file}->call($env);
}

以下の行でAccessorとしてpath, rootと他2つ定義されています。

use Plack::Util::Accessor qw( path root encoding pass_through );

そのpathですが、以下の記述からコードリファレンス、もしくは正規表現のリファレンスを期待している事がわかります。

my $matched = 'CODE' eq ref $path_match ? $path_match->($_) : $_ =~ $path_match;

ここで自前で実装したstatic.psgiでpathに対して次のようなコードリファレンスを渡します。これはこの無名関数に渡された文字列の最後が/で終わる場合、その部分を/index.htmlと書き換えます。

sub { s!(.*/$)!${1}/index.html! or return qr{^/.+} }

もし、書き換えられた場合は次の行でrewriteされます。localはそのスコープ内でのみ値を一時的に保存します。

local $env->{PATH_INFO} = $path; # rewrite PATH

localで一時的に保存された値はスコープ内でのみ有効。で、そのスコープ内でPlack::App::File->new->call($env)が実行されます。

$self->{file} ||= Plack::App::File->new({ root => $self->root || '.', encoding => $self->encoding }); 
local $env->{PATH_INFO} = $path; # rewrite PATH
return $self->{file}->call($env);

余談

ちなみになんとなくこの処理はこの順番のほうが(処理の流れを説明する場合は)いい気がする。次のような感じ。

local $env->{PATH_INFO} = $path; # rewrite PATH

$self->{file} ||= Plack::App::File->new({ root => $self->root || '.', encoding => $self->encoding }); 
return $self->{file}->call($env);

でもlocalで変化している期間を最小にしたいなら元の書き方が正しい気もする。うーん。

おわりに

ちなみにこの正規表現では'ディレクトリの最後は/'という前提のものです。
おしまい。

独り言

はてなぶろぐつかってみたいなー