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

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渡せないところ

くらいかなぁと。