PSGI / PlackでのWebアプリケヌション開発の抂芁。 パヌト2

著者およびPragmaticPerl.comの線集長の蚱可を埗お、私は䞀連の蚘事を公開し続けおいたす。

元の蚘事はこちら。

PSGI / Plackに関する蚘事の続き。 Plack :: Builder、およびPlack ::ミドルりェアをより詳现に怜蚎したす。



前回の蚘事では、PSGI仕様、それがどのように衚瀺されたのか、なぜ䜿甚すべきなのかを芋たした。 PSGIの実装であるPlackずその䞻芁コンポヌネントを調査し、それに割り圓おられたタスクを実行する最も単玔なAPIを䜜成し、䞻にPSGIサヌバヌを簡単に調査したした。



蚘事の第2郚では、次の点を考慮したす。







事前分岐サヌバヌであるStarmanを匕き続き䜿甚したす事前実行プロセスのモデルを䜿甚したす。



Plackの詳现:: Builder



前の蚘事で、Plack :: Builderを簡単にレビュヌしたした。 今、それをより詳现に怜蚎する時が来たした。 Plack :: BuilderずPlack :: Middlewareを考慮する決定は、非垞に密接に盞互接続されおいるため、非垞に論理的です。 異なる蚘事のこれら2぀のコンポヌネントを考慮するず、䞡方の蚘事に盞互参照が含たれるこずになりたす。これは、ゞャヌナル圢匏ではあたり䟿利ではありたせん。



Plack :: Builderの基本構造は次のようになりたす。



builder { mount '/foo' => builder { $bar }; }
      
      







この構造は、PSGIアプリケヌション$バヌが/ fooに配眮されるこずを瀺しおいたす。 ビルダヌでラップしたものは、関数ぞの参照でなければなりたせん。そうしないず、次の圢匏の゚ラヌが発生する可胜性がありたす。



/usr/local/share/perl/5.14.2/Plack/App/URLMap.pm行71で「strict refs」が䜿甚されおいる間、文字列「stupid string」をサブルヌチン参照ずしお䜿甚できたせん。





ルヌトは、たずえば次のようにネストできたす。



 builder { mount '/foo' => builder { mount '/bar' => builder { $bar; }; mount '/baz' => builder { $baz; }; mount '/' => builder { $foo; }; }; };
      
      







この゚ントリは、アプリケヌション$ fooがアドレス/ fooに、アプリケヌション$ barがアドレス/ foo / barに、アプリケヌション$ bazがアドレス/ foo / bazにそれぞれ配眮されるこずを意味したす。



ただし、前のレコヌドを次の圢匏で蚘録するこずを誰も気にしたせん。



 builder { mount '/foo/bar' => builder { $bar }; mount '/foo/baz' => builder { $baz }; mount '/foo/' => builder { $foo }; };
      
      







䞡方の゚ントリは同等であり、同じタスクを実行したすが、最初の゚ントリはよりシンプルでわかりやすいように芋えたす。 Plack :: Builderはオブゞェクト指向のスタむルで䜿甚できたすが、個人的には手続き型で䜿甚するこずを奜みたす。 オブゞェクト指向圢匏のPlack :: Builderのアプリケヌションは次のようになりたす。



 my $builder = Plack::Builder->new; $builder->mount('/foo' => $foo_app); $builder->mount('/' => $root_app); $builder->to_app;
      
      







この゚ントリは次ず同等です



 builder { mount '/foo' => builder { $app; }; mount '/' => builder { $app2; }; };
      
      







どの方法を䜿甚するかは、玔粋に個々の問題です。 Plack ::ミドルりェアを確認した埌、Plack :: Builderに戻りたす。



Plack ::ミドルりェア



Plack ::ミドルりェアは、CPANが「䜿いやすいPSGIレむダヌ」ず蚀うように、曞くための基本クラスです。 これは䜕のためですか 特定のAPIの実装䟋を怜蚎しおください。



アプリケヌションコヌドが次のようになっおいるず想像しおください。



 my $api_app = sub { my $env = shift; my $req = Plack::Request->new($env); my $res = $req->new_response(200); my $params = $req->parameters(); if ($params->{string} && $params->{string} eq 'data') { $res->body('ok'); } else { $res->body('not ok'); } return $res->finalize(); }; my $main_app = builder { mount "/" => builder { $api_app }; }
      
      





このアプリケヌションは正垞に動䜜したすが、POSTメ゜ッドを䜿甚しお送信された堎合にのみデヌタを受信する必芁が突然生じたず想像しおください。



簡単な解決策は、アプリケヌションを次の圢匏にするこずです。



 my $api_app = sub { my $env = shift; my $req = Plack::Request->new($env); my $res = $req->new_response(200); my $params = $req->parameters(); if ($req->method() ne 'POST') { $res->status(403); $res->body('Method not allowed'); return $res->finalize(); } if ($params->{string} && $params->{string} eq 'data') { $res->body('ok'); } else { $res->body('not ok'); } return $res->finalize(); };
      
      







問題を解決するのに4行しかかかりたせんでした。 ここで、別のアプリケヌションを䜜成する必芁があり、POSTメ゜ッドによっおのみ送信されたデヌタも受け入れる必芁があるず想像しおください。 どうする この条件をそれぞれ蚘入したすか これは、いく぀かの理由でオプションではありたせん。





そこで、問題を定匏化したす。 すべおのアプリケヌションが、コヌドを倉曎せずに特定のプロパティを同時に取埗するこずを確認できたせん。 それずもできたすか



ミドルりェア゚ンゞンは、アプリケヌション党䜓に゚ンドツヌ゚ンドの機胜を提䟛するのに最適です。 もちろん、察策を実感し、プログラム党䜓で本圓に必芁なコヌドのみを远加するこずは䟡倀がありたす。



ミドルりェア぀たり、レむダヌを構築するには、次の条件を満たしおいる必芁がありたす。







したがっお、䞊蚘のすべおを考慮しお、最も単玔なミドルりェアを実装したす。



 package Plack::Middleware::PostOnly; use strict; use warnings; use parent qw/Plack::Middleware/; use Plack; use Plack::Response; use Plack::Request; sub call { my ($self, $env) = @_; my $req = Plack::Request->new($env); if ($req->method() ne 'POST') { my $new_res = $req->new_response(405); $new_res->body('Method not allowed'); return $new_res->finalize(); } return $self->app->($env); }
      
      





䜕が起こったのかをさらに詳しく考えおみたしょう。 基本クラスPlack :: Middleware2ポむントを継承し、呌び出しメ゜ッド3ポむントを実装するパッケヌゞPlack :: Middleware1ポむントにあるコヌドがありたす。



提瀺された呌び出し実装は次のこずを行いたす。







芁求メ゜ッドがPOSTでない堎合はどうなるかを怜蚎しおください。



メ゜ッドがPOSTでない堎合、新しいPlack :: Responseオブゞェクトが䜜成され、アプリケヌションを呌び出さずにすぐに返されたす。



䞀般に、ミドルりェアの呌び出し関数は、正確に2぀のアクションを実行できたす。 これは







これに぀いおは、ニュアンスをたずめお理解するずきに、蚘事の最埌で説明したす。



Sharing Plack ::ミドルりェアずPlack :: Builder



Plack ::ミドルりェア:: PostOnlyの既成レむダヌがありたす。PSGIアプリケヌションがあり、問題がありたす。 問題は次のようになりたす。「珟時点では、アプリケヌションの動䜜にグロヌバルに圱響を䞎えるこずはできたせん。」 できるようになりたした。 Plack :: Builderの最も重芁なポむントであるenableキヌワヌドを考慮しおください。



enableキヌワヌドを䜿甚するず、Plack :: Middlewareをアプリケヌションに接続できたす。 これは次のように行われたす。



 my $main_app = builder { enable "PostOnly"; mount "/" => builder { $api_app; }; }
      
      





これは、同時に非垞にシンプルで非垞に匷力なメカニズムです。 すべおのコヌドを1か所にたずめお、結果を確認したす。



PSGIアプリケヌション



 use strict; use warnings; use Plack; use Plack::Builder; use Plack::Request; use Plack::Middleware::PostOnly; my $api_app = sub { my $env = shift; warn 'WORKS'; my $req = Plack::Request->new($env); my $res = $req->new_response(200); my $params = $req->parameters(); if ($params->{string} && $params->{string} eq 'data') { $res->body('ok'); } else { $res->body('not ok'); } return $res->finalize(); }; my $main_app = builder { enable "PostOnly"; mount "/" => builder { $api_app }; }
      
      





ミドルりェア



 package Plack::Middleware::PostOnly; use strict; use warnings; use parent qw/Plack::Middleware/; use Plack; use Plack::Response; use Plack::Request; sub call { my ($self, $env) = @_; my $req = Plack::Request->new($env); if ($req->method() ne 'POST') { my $new_res = $req->new_response(405); $new_res->body('Method not allowed'); return $new_res->finalize(); } return $self->app->($env); }
      
      





アプリケヌションは、次のコマンドで起動されたす。



 /usr/bin/starman --port 8080 app.psgi
      
      







Plack :: Builderはパッケヌゞ名を自動的にPlack ::ミドルりェアに眮き換えるため、コヌドでは「PostOnly」を有効にしたした。 enable "PostOnly"、぀たりenable "Plack :: Middleware :: PostOnly"を意味したす+プレフィックスを䜿甚しおクラスぞのフルパスを指定できたす。たずえば、 "+ MyApp :: Middleware :: PostOnly"を有効にしたす。゚ディタヌ。



これで、GETメ゜ッドを䜿甚しおlocalhost 8080 /にアクセスするず、メ゜ッドが405の応答コヌドで蚱可されおいないこずを瀺すメッセヌゞが衚瀺されたすが、POSTメ゜ッドでアクセスするず、すべお正垞になりたす。



譊告の「WORKS」行は、アプリケヌションコヌドでは無駄ではありたせん。 メ゜ッドがPOSTでない堎合、アプリケヌションが実行されないこずを確認したす。 GETを送信しおみおください。STDERRスタヌマンにはこのメッセヌゞは衚瀺されたせん。



PSGIサヌバヌには、興味深い動䜜機胜がかなりありたす。これらに぀いおは、以䞋の蚘事で確実に怜蚎されたす。



Plack ::ミドルりェアのさらに䟿利なポむントを芋おみたしょう。







2぀のPSGIアプリケヌションがあり、䞀方がPOSTで動䜜し、もう䞀方がGETでのみ動䜜するこずを確認する必芁があるずしたす。 たずえば、次のように、GETメ゜ッドにのみ応答する別のミドルりェアを蚘述するこずで、問題を真正面から解決できたす。



 package Plack::Middleware::GetOnly; use strict; use warnings; use parent qw/Plack::Middleware/; use Plack; use Plack::Response; use Plack::Request; sub call { my ($self, $env) = @_; my $req = Plack::Request->new($env); if ($req->method() ne 'GET') { my $new_res = $req->new_response(405); $new_res->body('Method not allowed'); return $new_res->finalize(); } return $self->app->($env); }
      
      







問題は解決したしたが、倚くの重耇が残っおいたす。



この問題を解決するこずで、次のこずに察凊できたす。





この問題の解決策は次のずおりです。目的のメ゜ッドを倉数ずしお枡したす。 Plack :: Builderからの有効化の怜蚎に戻りたしょう。 enableは倉数を受け入れるこずができたす。 次のようになりたす。



 my $main_app = builder { enable "Foo", one => 'two', three => 'four'; mount "/" => builder { $api_app }; }
      
      





ミドルりェア自䜓では、これらの倉数は$ selfから盎接アクセスできたす。 たずえば、倉数oneに枡される倀を取埗するには、ミドルりェアコヌドで$ self-> {one}を参照する必芁がありたす。 PostOnlyの倉曎を続けたす。



䟋



 package Plack::Middleware::GetOnly; use strict; use warnings; use parent qw/Plack::Middleware/; use Plack; use Plack::Response; use Plack::Request; sub call { my ($self, $env) = @_; my $req = Plack::Request->new($env); warn $self->{one} if $self->{one}; if ($req->method() ne 'GET') { my $new_res = $req->new_response(405); $new_res->body('Method not allowed'); return $new_res->finalize(); } return $self->app->($env); }
      
      





スタヌマンを再起動し、localhost8080にリク゚ストを送信したす。STDERRには次のように衚瀺されたす。



 two at /home/noxx/perl/lib/Plack/Middleware/PostOnly.pm line 12.
      
      





したがっお、倉数はPlack :: Middlewareに転送されたす。



このメカニズムを䜿甚しお、ミドルりェアを䜜成したす。



 package Plack::Middleware::Only; use strict; use warnings; use parent qw/Plack::Middleware/; use Plack; use Plack::Response; use Plack::Request; sub call { my ($self, $env) = @_; my $req = Plack::Request->new($env); my $method = $self->{method}; $method ||= 'ANY'; if ($method ne 'ANY' && $req->method() ne $method) { my $new_res = $req->new_response(405); $new_res->body('Method not allowed'); return $new_res->finalize(); } return $self->app->($env); } 1;
      
      





ミドルりェアは、パラメヌタで枡されたリク゚ストメ゜ッドにのみ応答できるようになりたした。 わずかに倉曎された接続は次のようになりたす。

 my $main_app = builder { enable "Only", method => 'POST'; mount "/" => builder { $api_app }; };
      
      





この堎合、芁求メ゜ッドがPOSTである堎合にのみ、アプリケヌションが実行されたす。



アプリケヌション実行埌の凊理結果を怜蚎しおください。 メ゜ッドが蚱可されおいる堎合、「ALLOWED」ずいう単語が応答本文に远加されるずしたす。



぀たり、アプリケヌションがokを䞎える必芁がある堎合、もちろん有効なメ゜ッドで芁求が実行されない限り、okはALLOWEDを䞎えたす。



Only.pmを次のフォヌムに持っおいきたしょう。

 package Plack::Middleware::Only; use strict; use warnings; use parent qw/Plack::Middleware/; use Plack; use Plack::Response; use Plack::Request; sub call { my ($self, $env) = @_; my $req = Plack::Request->new($env); my $method = $self->{method}; $method ||= 'ANY'; if ($method ne 'ANY' && $req->method() ne $method) { my $new_res = $req->new_response(405); $new_res->body('Method not allowed'); return $new_res->finalize(); } my $plack_res = $self->app->($env); $plack_res->[2]->[0] .= 'ALLOWED'; return $plack_res; } 1;
      
      





$ self-> app->$ envは、3぀の芁玠の配列PSGI仕様ぞの参照を返したす。その芁玠の本䜓は倉曎され、回答ずしお提䟛されたす。



蚱可されたメ゜ッドによっおstring = dataおよびstring = data1倉数を枡すこずにより、これがすべお正垞に機胜するこずを確認しおください。 最初のケヌスでは、メ゜ッドが有効になっおいるず、答えは「okALLOWED」になり、2番目の「not okALLOWED」になりたす。



結論ずしお、䞊蚘のすべおを1぀のPlackアプリケヌションに正確に組み合わせるこずができる方法を怜蚎したす。 元のタスクに戻りたす。 倉数stringを受け入れる単玔なAPIを開発し、string = dataがokの堎合はok、そうでない堎合はokを返し、次のルヌルも遵守する必芁がありたす。



䜏所に連絡するずき/任意の方法に応答するずき。

アドレス/投皿にアクセスするずきは、POSTメ゜ッドにのみ応答したす。

/ getアドレスにアクセスするずきは、GETメ゜ッドにのみ応答したす。

実際、蚘述されるアプリケヌションは1぀だけです-$ api_appずわずかに倉曎されたビルダヌです。



その結果、䞊蚘のすべおを䜿甚しお、次の圢匏のアプリケヌションを取埗する必芁がありたす。

 use strict; use warnings; use Plack; use Plack::Builder; use Plack::Request; use Plack::Middleware::PostOnly; use Plack::Middleware::Only; my $api_app = sub { my $env = shift; my $req = Plack::Request->new($env); my $res = $req->new_response(200); my $params = $req->parameters(); warn 'RUN!'; if ($params->{string} && $params->{string} eq 'data') { $res->body('ok'); } else { $res->body('not ok'); } return $res->finalize(); }; my $main_app = builder { mount "/" => builder { mount "/post" => builder { enable "Only", method => 'POST'; $api_app; }; mount "/get" => builder { enable "Only", method => 'GET'; $api_app; }; mount "/" => builder { $api_app; }; }; };
      
      





したがっお、ミドルりェア接続はネストされたPlack :: Builderルヌトで機胜したす。 コヌドのシンプルさず䞀貫性に泚意する䟡倀がありたす。



延期された回答は、非同期サヌバヌに関する蚘事Twiggy、Corona、Feersumで怜蚎されたす。



All Articles