Hatena::Groupdann

dann's blog このページをアンテナに追加 RSSフィード

Fork me on GitHub

2008-12-14

merbがなかなか良さそうな件

| merbがなかなか良さそうな件 - dann's blog を含むブックマーク はてなブックマーク - merbがなかなか良さそうな件 - dann's blog merbがなかなか良さそうな件 - dann's blog のブックマークコメント

趣味でPerlのWAF作りをはじめてから、何故かRubyのコードばかり読んでいる今日この頃なんですが、merbはなかなか良さそうですね。幾つか面白い機構があるので、それは暇みつけて書いていってみよう。

merbはrailsよりは大分コードもわかりやすいですし、読みやすい。merb > rack >> rails という順でわかりやすいかなぁと。

merb, rack, rake, railsあたりは結構読んでみてるんですが、 今のところ見てる感じだと、merb, rackrubyの勉強教材としてもかなりいいなという印象です。

Middlewareにもconfig渡したくなりそう

Middlewareにもconfig渡したくなりそう - dann's blog を含むブックマーク はてなブックマーク - Middlewareにもconfig渡したくなりそう - dann's blog Middlewareにもconfig渡したくなりそう - dann's blog のブックマークコメント

Middlewareにもconfig渡すようにしてみると、使いやすいんじゃないかという気がした。イメージ的には、以下のようなイメージ。こうすると、handlerもconstructorに渡したくなるような気もする。。

sub build_request_handler {
    my ($self, $handler, $args) = @_;
    my $request_handler = $handler;
    for my $middleware ( @{ $self->middlewares } ) { 
       Mouse::load_class($middleware);
       $middleware->new($args->{config});
       $request_handler = $middleware->wrap($request_handler);
    }
    $request_handler;
}   

HTTPx::Middleware::Unicode

HTTPx::Middleware::Unicode - dann's blog を含むブックマーク はてなブックマーク - HTTPx::Middleware::Unicode - dann's blog HTTPx::Middleware::Unicode - dann's blog のブックマークコメント

MobircのMiddlewareの仕様にあわせてCatalyst::Plugin::Unicodeをそのまんま移植してみた。

package HTTPx::Middleware::Unicode;
use Mouse;
use utf8;

sub wrap {
    my ( $class, $next ) = @_; 
    sub {
        my $req = shift;
        decode_params($req);
        my $res = $next->($req);
        encode_body($res);
        $res;
    }   
}

sub encode_body {
    my $response = shift;

    if ( $response->body && utf8::is_utf8( $response->body ) ) { 
        utf8::encode( $response->body );
    }   
}

sub decode_params {
    my $request = shift;

    for my $value ( values %{ $request->params } ) { 
        if ( ref $value && ref $value ne 'ARRAY' ) { 
            next;
        }   
        utf8::decode($_) for ( ref($value) ? @{$value} : $value );
    }   
}

1;

HTTP::Engine::Middleware

HTTP::Engine::Middleware - dann's blog を含むブックマーク はてなブックマーク - HTTP::Engine::Middleware - dann's blog HTTP::Engine::Middleware - dann's blog のブックマークコメント

HTTP::Engine::Middleware

http://coderepos.org/share/browser/lang/perl/HTTP-Engine-Middleware/branches/functional/

wrapでrequest_handlerをwrapしていく感じ。後はrequestにメソッド生やす系。

組み立て部分は、以下の部分。

App/Mobirc/Plugin/Component/HTTPD.pm

    my $request_handler = App::Mobirc::Web::Middleware::Encoding->wrap( \&App::Mobirc::Web::Handler::handler );
    for my $mw ( @{ $self->middlewares } ) { 
      $mw->require or die $@; 
      $request_handler = $mw->wrap($request_handler);
    }

後は、HTTP::Engineにrequest_handler渡すだけと。なるほど、こういうwrapの仕方もあるなぁと。

# HTTP::Engine::MiddlewareはHTTP::Engineのcontextなし版でなかったりmouse版でもないので、HTTP::EngineのコアからMiddlewareを外す場合には、HTTPx::Middlewareとかの置き場所があるといいですね。

HTTPx::Middlewareのイメージ request_handlerのwrap版

HTTPx::Middlewareのイメージ request_handlerのwrap版 - dann's blog を含むブックマーク はてなブックマーク - HTTPx::Middlewareのイメージ request_handlerのwrap版 - dann's blog HTTPx::Middlewareのイメージ request_handlerのwrap版 - dann's blog のブックマークコメント

HTTPx::Middlewareのイメージ。request_handlerをwrapできればよいというのをMouseで実装すると以下のような感じ。

Middlewareの仕様としては、

  • MouseのRoleとする
  • handle_requestメソッドを実装
  • handle_requestの引数は、request

Rackとかのcallメソッドに対応するのがhandle_requestメソッドになる.

request_handlerをMiddlewareでラップするところをMouse::Roleで実現する。

Middleware

package HTTPx::Middleware::ShowExceptions;
use Mouse::Role;

around 'handle_request' => sub {
    my $orig = shift;
    my ( $self, $request ) = @_; 

    my $response = eval {$orig->( $self, $request )};     
    if($@) {
        $self->show_exceptions($@);
    }
    $response;
};

sub show_exceptions {
    my ($self, $exceptions) = @_; 
    warn $exceptions; 
}

1;

# show_exceptionsなんかはafterだけでいいかな。

package HTTPx::Middleware::CommonLogger;
use Mouse::Role;
use Data::Dumper;

around 'handle_request' => sub {
    my $orig = shift;    my ( $self, $request ) = @_; 

    $self->log_request($request);    my $response = $orig->( $self, $request );
    $self->log_response($response);
    $response;
};
sub log_request {
    my ( $self, $request ) = @_; 
    warn Dumper $request;
}

sub log_response {
    my ( $self, $response ) = @_; 
    warn Dumper $response;
}

1;

アプリ側

アプリではMiddlwareをwithで組み立て。

package MyApp::RequestHandler;
use Mouse;
use HTTP::Engine::Response;
with 'HTTPx::Middleware::CommonLogger', 'HTTPx::Middleware::ShowExceptions';
sub handle_request {
    my ( $self, $request ) = @_;
    HTTP::Engine::Response->new( body => 'Hello World' );
}
__PACKAGE__->meta->make_immutable;

1;

HTTP::EngineでのMiddlewareの使い方

use HTTP::Engine;
use MyApp::RequestHandler;

my $engine = HTTP::Engine->new(
    interface => {
        module => 'ServerSimple',
        args   => {
            host => '192.168.87.129',
            port => 1978,
        },  
        request_handler => sub {
            my $req  =  shift;
            MyApp::RequestHandler->new->handle_request($req);
        }   
    },  
);
$engine->run;

利用する側は、withするだけでもいいし、HTTPx::Builderのようなものを作って、Roleをconsumeするような実装を用意してみてもいいかもなぁという感じ。

このMouseのRole使う形だとhook pointは、リクエスト処理前/後の2カ所になるけれど、Rackとかみてるとこれでいいのかなぁという気もする。

もっとCatalystのように細かいhookポイントを渡した方が作りやすいのかもしれないという気もするのだけれど、その辺はまだよくわからない。

Middlewareの実行順

with 'Middleware1','Middleware2'とすると、

request -> Middleware2 -> Middleware1 -> app->handle_request -> Middleware1 -> Middleware2 -> response

のような感じで実行される。

以下の方がイメージが近いのかなぁという気もするけれど、そこらはBuilderが吸収してあげればいいのかもしれない。

request -> Middleware1 -> Middleware2 -> app->handle_request -> Middleware2 -> Middleware1 -> response

TODO

上記の実装で、Rackとかと違うのは、

  • pathに応じてMiddleware適用する機構
  • Middlewareにconfig渡せないところ

くらいかなぁと。