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渡せないところ
くらいかなぁと。