ziguzagu.org

Plack::Middleware::DefaultDocument

Plack::Middleware::DefaultDocument というのを作った。

MogileFS のクライアントとしてだけ動く小さな PSGI アプリかいていて、MogileFS になかったらアプリがもってるデフォルトのファイルを 404 ではなく 200 で返したい、ということをやりたかったんだけど、これを実現する方法として、

  • app 側で対応する
    • 汎用的にしたかったのでなし
  • 手前にいる reverse proxy(apache2.2)にやらせる
    • プロキシした結果 404 だったら別の何かを 200 で返す、という方法がわからなかった
    • あったとしても mod_rewrite での対応となると今回はきつい
      • このアプリにプロキシされるまでにすでにカオス RewriteRule をくぐり抜けてきているので…
  • さらに手前にいる varnish / perlbal にやらせる
    • varnish がバックエンドから 404 うけとったら req.url 書き換えて restart、varnish の後ろにいる perlbal の web server につかませる
    • キャッシュがあればバックエンドひと通り巡るコストもなく速いが関係者が増えるのでなんかいや
  • Middleware::ErrorDocument で subrequest なげて Middleware::Static につかませる
    • 404 は普通につかいたいので、それとは別に enable_if { $_[0]->{PATH_INFO} =~ m{/favicon.ico} } ErrorDocument 〜 とかしてわけて、Static で path => sub { s/// } で PATH_INFO 書き換えて特定ディレクトリからさがす
    • 別にこれでよかった(え)

など堂々めぐりした結果、簡単にできるやつをということで作った。

使い方はこんなん感じ。

enable "DefaultDocument",
    '/favicon\.ico$' => '/path/to/htodcs/favicon.ico',
    '/robots\.txt'   => '/path/to/htdocs/robots.txt';

app からのレスポンスコードが 404 だった場合に、PATH_INFO が key の正規表現にマッチしたら value のファイルを返す、という動作をする。404 以外が返っていこうとした場合は何もしない。

MogileFS からファイル探して、なかったらデフォルトのなにかを必要に応じてだす感じだとこういう風の例。

use strict;
use warnings;

use FindBin;
use MogileFS::Client;
use Plack::Builder;
use Plack::MIME;

my $mog;

my $app = sub {
    my $env = shift;

    $mog = MogileFS::Client->new(
        domain => 'brad',
        hosts  => [ 'mog01', 'mog02' ]
    ) or die $@;

    my $key = $env->{PATH_INFO};
    $key .= 'index.html' if $key =~ m{/$};
    my @urls = $mog->get_paths($key)
        or return [ '404', [], [] ];

    my %header = (
        'X-REPROXY-URL'       => join(' ', @urls),
        'X-REPROXY-CACHE-FOR' => '86400; Content-Type',
        'Content-Type'        => Plack::MIME->mime_type($key),
    );
    return [ 200, [ %header ], [] ];
};

builder {
    enable 'ErrorDocument',
        404 => "$FindBin::Bin/htdocs/error/404.html";
    enable 'DefaultDocument',
        '/favicon\.ico$' => "$FindBin::Bin/htdocs/favicon.ico";
    $app;
};

DefaultDocument という名前は英語的、動作の説明的に微妙な気がしないでもない…。そして相変わらずの俺得 module 感…。