HTTP::Engine + Path::Dispatcherでのアプリの作り方

リクエストを処理してDispatchするメインのロジックと、ディスパッチテーブルをまずはじめに作ります。後は、ディスパッチテーブルで参照したControllerを作っていけばいいだけです。

メイン

リクエストを処理して、Dispatcherに処理を委譲するところです。

「requestをhandleするところで(handle_requestメソッド)で、dispatcher作ってdispatchして、matchしたものに、contextオブジェクト渡して実行」

という形になります。Path::Dispatcherをdispatcherとして使っていますが、他のdispatcher(http://dann.g.hatena.ne.jp/dann/20081106/p1) 使っても、大体同じような作りになると思います。

handle_request以外のところは、remedieの実装まんまですが、大体同じような感じの作りになるんじゃないかと思います。

package Mediras::Server;
use Moose;
use HTTP::Engine;
use Mediras::Dispatcher;
use Mediras::Context;
 
has 'conf' => ( is => 'rw', );
 
has 'engine' => (
    is => 'rw',
    isa => 'HTTP::Engine',
    lazy => 1,
    builder => 'build_engine',
    handles => [qw(run)],
);
 
has 'dispatcher' => (
    is => 'rw',
    lazy => 1,
    builder => 'build_dispathcer',
);
 
no Moose;
 
sub bootstrap {
    my ( $class, $conf ) = @_;
 
    my $self = $class->new( conf => $conf );
 
    my $exit = sub { CORE::die('caught signal') };
    eval {
        local $SIG{INT} = $exit;
        local $SIG{QUIT} = $exit;
        local $SIG{TERM} = $exit;
        $self->run;
    };
}
 
sub build_engine {
    my $self = shift;
    return HTTP::Engine->new(
        interface => {
            module => 'ServerSimple',
            args => $self->conf,
            request_handler => sub { $self->handle_request(@_) },
        },
    );
}
 
sub build_dispathcer {
    my $self = shift;
    return Mediras::Dispatcher->new;
}
 
sub handle_request {
    my ( $self, $req ) = @_;
 
    my $path = $req->path;
    $path = "/index/" if $path eq "/";
 
    my $res = HTTP::Engine::Response->new;
    my $c = Mediras::Context->new( req => $req, res => $res );
 
    my $dispatch = $self->dispatcher->dispatch($path);
    unless ( $dispatch->has_matches ) {
        $c->res->status(404);
        $c->res->body("404 Not Found");
        return $c->res;
    }
    eval { $dispatch->run($c); };
 
    if ($@) {
        $c->res->status(500);
        $c->res->body("Internal Server Error");
        return $c->res;
    }
 
    return $c->res;
}
 
__PACKAGE__->meta->make_immutable;
 
1;

ディスパッチテーブル(Dispatcher)

ディスパッチテーブルをここで作ります。dispatchのためのルールを追加しているだけです。ルールがマッチしたら、どのControllerに処理割り振るのか記述します。

ここではController一つしか使ってませんが、複数Controllerかけるようにしたり、ControllerのインスタンスをCacheしてみたりしてもいいでしょうね。

package Mediras::Dispatcher;
use Moose;
use Path::Dispatcher;
use Path::Dispatcher::Rule::Regex;
use Path::Dispatcher::Rule::Tokens;
use Mediras::Web::Controller::Root;
 
has 'dispatcher' => (
    is => 'ro',
    handles => [ 'dispatch', 'run' ],
    default => sub {
        Path::Dispatcher->new;
    }
);
 
has 'controller' => (
    is => 'rw',
    default => sub {
        Mediras::Web::Controller::Root->new;
    },
);
 
no Moose;
 
sub BUILD {
    my $self = shift;
    $self->setup_rules;
}
 
sub setup_rules {
    my $self = shift;
    my $controller = $self->controller;
    $self->dispatcher->add_rule(
        Path::Dispatcher::Rule::Regex->new(
            regex => qr{^/index/},
            block => sub {
                my $c = shift;
                $controller->index($c);
            },
        )
    );
 
    $self->dispatcher->add_rule(
        Path::Dispatcher::Rule::Tokens->new(
            tokens => [ 'rss', qr/^.+$/ ],
            delimiter => '/',
            block => sub {
                my $c = shift;
                $controller->rss( $c, $2 );
            },
        )
    );
 
    $self->dispatcher->add_rule(
        Path::Dispatcher::Rule::Regex->new(
            regex => qr{^/files/(\w+)/(\w+\.\w+)$},
            block => sub {
                my $c = shift;
                $controller->files( $c, $1, $2 );
            },
        )
    );
 
}
 
__PACKAGE__->meta->make_immutable;
 
1;

Controller

ControllerはPlainなPerlクラスにして、contextオブジェクトをメソッドの第一引数に渡すように作っておけばよいです。

雑感

HTTP::Engine、Dispatcherとフレームワークを作る部品は用意されていて、誰でも簡単にフレームワークを作れるようになっているので、小物ツールを作るための機能を削ったオレオレフレームワークを作るというのは面白いかもしれないですね。