Mojolicious::Guides::Cookbook - Mojoliciousのクックブック
説明
Mojoliciousで料理できる楽しいレシピが満載。
概念
すべてのMojolicious開発者が知るべき本質
ブロッキング / ノンブロッキング処理
blocking
処理は、サブルーチンが終了するまで呼び出し元サブルーチンの実行をブロックするサブルーチンです。
sub foo { my $result = blocking_subroutine(); ... }
一方、non-blocking
処理は、サブルーチンがまだ終了していない場合でも呼び出しサブルーチンの実行が継続できるようにします。待機する代わりに、呼び出し元のサブルーチンは、サブルーチンが終了すると実行されるコールバックを渡します。これは、継続渡しスタイルと呼ばれます。
sub foo { non_blocking_subroutine(sub { my $result = shift; ... }); ... }
Mojolicious
はノンブロッキングI/Oおよびイベントループのためにゼロから設計されていますが、魔法のごとくPerlコードをノンブロッキングにはできません。 そのためには、subprocess
でブロッキングコードをラップして、ノンブロッキングコードを妨害することを防げます。
イベントループ
イベントループは基本的に、外部イベントを継続的にテストし、適切なコールバックを実行してそれらを処理するループです。多くの場合、これがプログラムのメインループとなります。ファイル記述子とタイマーの読み取り/書き込みのノンブロッキングテストは、単一のプロセスで同時に数千のクライアント接続を処理できるため、非常にスケーラブルなネットワークサーバーでよく使用されるイベントです。
while (1) { my @readable = test_fds_for_readability(); handle_readable_fds(@readable); my @writable = test_fds_for_writability(); handle_writable_fds(@writable); my @expired = test_timers(); handle_timers(@expired); }
Mojoliciousでは、このイベントループはMojo::IOLoopが行います。
リバースプロキシ
リバースプロキシアーキテクチャは、多くの運用環境で使用されるデプロイ手法です。reverse proxy
サーバーはアプリケーションの前に配置され、外部クライアントからアクセス可能なエンドポイントとして機能します。これには次のような多くの利点があります。外部からのSSL接続の終了、Mojoliciousアプリケーションへの同時オープンソケット数の制限(またはUnixソケットの使用についても)、複数インスタンス間での負荷分散、または複数アプリケーション間での同一IP/ポートの共有。
.......................................... : : +--------+ : +-----------+ +---------------+ : | |-------->| | | | : | client | : | reverse |----->| Mojolicious | : | |<--------| proxy | | application | : +--------+ : | |<-----| | : : +-----------+ +---------------+ : : : .. システム境界(例. 同一ホスト) ......
ただし、このセットアップではいくつかの問題が発生します。アプリケーションは、オリジナルのクライアントではなくリバースプロキシからリクエストを受け取ります。アプリケーション内部のアドレス/ホスト名は、外部から見えるものとは異なります。また、SSLを終了すると、リバースプロキシはHTTPS経由でサービスを公開し、Mojoliciousアプリケーションに対してはHTTPを使用します。
例として、クライアントからのサンプルリクエストとMojoliciousアプリケーションが受け取るリクエストを比べてみましょう。
クライアント リバースプロクシ Mojoliciousアプリ __|__ _______________|______________ ____|____ / \ / \ / \ 1.2.3.4 --HTTPS--> api.example.com 10.20.30.39 --HTTP--> 10.20.30.40 GET /foo/1 HTTP/1.1 | GET /foo/1 HTTP/1.1 Host: api.example.com | Host: 10.20.30.40 User-Agent: Firefox | User-Agent: ShinyProxy/1.2 ... | ...
ただし、クライアントアドレスは使用できなくなり(分析やGeo-IPに役立つ可能性があります)、Mojolicious::Controllerのurl_for
によって生成されたURLは次のようになります。
http://10.20.30.40/bar/2
以下のようであればクライアントにとってわかりやすいのですが。
https://api.example.com/bar/2
これらの問題を解決するには、不足しているデータを送信するようにリバースプロキシを構成します(/Nginx
および/"Apache/mod_proxy"
)。そして、環境変数MOJO_REVERSE_PROXY
を設定して、アプリケーションに通知します。 きめ細かく制御するために、/Rewriting
には変更を手動で実装する方法のサンプルが含まれています。
デプロイメント
Mojolicious と Mojolicious::Lite アプリケーションをさまざまなプラットフォームで実行させます。多くのリアルタイムWeb機能がMojo::IOLoopのイベントループに基づいているので、 イベントループの機能を完全に引き出すためには、組み込みウェブサーバーのひとつ以上がリアルタイム機能を使用できる必要があります。
組み込みサーバ
Mojolicious には、とてもポータブルな HTTP 1.1 準拠のウェブサーバが含まれます。通常これらは開発用に利用されますが、小中規模のアプリケーションであれば、十分に堅牢かつ高速に動きます。
$ ./script/my_app daemon Server available at http://127.0.0.1:3000.
コマンドMojolicious::Command::daemonを介してすべてのアプリケーションで使用できます。多くの設定オプションがあり、Perlが動作するすべてのプラットフォームにおいて単一プロセスアーキテクチャで動きます。
$ ./script/my_app daemon -h ...利用可能オプションのリスト...
もうひとつの大きな利点は、そのままで TLS と WebSoket をサポートして いることです。テスト目的のための開発証明書が適切に組み込まれているので、うまく動きます。ただし、すべての位置からリッスン先を Mojo::Server::Daemonのlisten
のサポートによって指定できます。
$ ./script/my_app daemon -l https://[::]:3000 Server available at https://[::]:3000.
systemdを使用してWebサーバーを管理するには、次のようなユニット構成ファイルを使用できます。
[Unit] Description=My Mojolicious application After=network.target [Service] Type=simple ExecStart=/home/sri/myapp/script/my_app daemon -m production -l http://*:8080 [Install] WantedBy=multi-user.target
プリフォーク
UNIXプラットフォームでは、Mojolicious::Command::preforkによって組み込みWebサーバにプリフォークが追加でき、複数プロセスアークテクチャに切り替えることができます。複数CPUコアとコピーオンライトメモリ管理を利用できるという利点があります。
$ ./script/my_app prefork Server available at http://127.0.0.1:3000.
組み込みのWebサーバーはMojo::IOLoopのイベントループに基づいているため、ノンブロッキング処理を利用するときに一番スケールします。しかし、何らかの理由によって多数のブロッキング処理をアプリケーションで実行する必要がある場合は、ワーカープロセスの数を増やしつつ、ワーカーあたりの同時接続数を減らすことでパフォーマンスを向上できます(多くの場合1
とする)。
$ ./script/my_app prefork -m production -w 10 -c 1 Server available at http://127.0.0.1:3000.
スタートアップの間にマネージャプロセスにおいてアプリケーションが事前ロードされます。このときイベントループは開始しないので、新しいワーカープロセスがフォークされ、イベントループが開始されるときはいつでも、Mojo::IOLoopのnext_tick
を使ってコードを実行できます。
use Mojolicious::Lite; Mojo::IOLoop->next_tick(sub { app->log->info("Worker $$ star...ALL GLORY TO THE HYPNOTOAD!"); }); get '/' => {text => 'Hello Wor...ALL GLORY TO THE HYPNOTOAD!'}; app->start;
また、systemdを使用してプリフォーク前のWebサーバーを管理するには、次のようなユニット構成ファイルを使用できます。
[Unit] Description=My Mojolicious application After=network.target [Service] Type=simple ExecStart=/home/sri/myapp/script/my_app prefork -m production -l http://*:8080 [Install] WantedBy=multi-user.target
Morbo
Mojolicious::Guides::Tutorialを読んだ後なら、Mojo::Server::Morboをすでに知っていることでしょう。
Mojo::Server::Morbo +- Mojo::Server::Daemon
基本的には、プロジェクト内の変更されたファイルを検知して、 新しいMojo::Server::DaemonWebサーバーをフォークするリスターターです。よって、これは開発用途でのみ使用してください。Morboでアプリケーションを起動するには、morbo
スクリプトを使用します。
$ morbo ./script/my_app Server available at http://127.0.0.1:3000.
Hypnotoad
もっと大きいアプリケーションのために、Mojolicious には UNIX に最適化されたプレフォーキングウェブサーバ Mojo::Server::Hypnotoad が含まれています。複数のCPUコアと書き込み時コピー (copy-on-write)が活用でき、スケールアップして数千の並列クライアントに対応できます。
Mojo::Server::Hypnotoad |- Mojo::Server::Daemon [1] |- Mojo::Server::Daemon [2] |- Mojo::Server::Daemon [3] +- Mojo::Server::Daemon [4]
サーバーはMojo::Server::Prefork Webサーバーをベースにしています。これはMojo::Server::Daemonにプリフォーク機能を追加するものですが、運用環境ですぐに使えるよう最適化されています。アプリケーションを開始するには、hypnotoadスクリプトを使用します。ポート8080
でリッスンし、サーバープロセスを自動的にデーモン化し、MojoliciousとMojolicious::Liteアプリケーションのモードを既定でproduction
にします。
$ hypnotoad script/my_app
多くの構成設定はアプリケーションからMojoのconfig
を使って調整できます。すべての設定のリストはMojo::Server::HypnotoadのSETTINGS
の項目を見てください.
use Mojolicious::Lite; app->config(hypnotoad => {listen => ['http://*:80']}); get '/' => {text => 'Hello Wor...ALL GLORY TO THE HYPNOTOAD!'}; app->start;
Mojolicious::Plugin::ConfigかMojolicious::Plugin::JSONConfigの設定ファイルにhypnotoad
セクションを追加することもできます。
# myapp.conf { hypnotoad => { listen => ['https://*:443?cert=/etc/server.crt&key=/etc/server.key'], workers => 10 } };
しかし、最大の利点の一つは、ダウンタイムなしのソフトウェア更新(ホットデプロイメント)をサポートしていることです。つまり、サーバーを止めたり、受信接続をひとつも失うことなく、上記のコマンドを実行するだけで Mojolicious や Perl、そして実行中のシステムライブラリでさえ更新できます。
$ hypnotoad script/my_app Starting hot deployment for Hypnotoad server 31841.(Hypnotoadサーバー31841のホットデプロイメントを開始しています。)
リバースプロキシの後ろでHypnotoadを使用している場合は、プロキシサポートを有効にすることもできます。MojoliciousがX-Forwarded-For
やX-Forwarded-Proto
ヘッダーを自動的に検知できるようになります。
# myapp.conf {hypnotoad => {proxy => 1}};
Hypnotoadをsystemdで管理するには、次のようなユニット構成ファイルを使用できます。
[Unit] Description=My Mojolicious application After=network.target [Service] Type=forking PIDFile=/home/sri/myapp/script/hypnotoad.pid ExecStart=/path/to/hypnotoad /home/sri/myapp/script/my_app ExecReload=/path/to/hypnotoad /home/sri/myapp/script/my_app KillMode=process [Install] WantedBy=multi-user.target
ゼロダウンタイム・ソフトウェア更新
Hypnotoadは、上記のように、ダウンタイムのないソフトウェアアップグレード(ホットデプロイメント)を非常に簡単にします。しかし、SO_REUSEPORT
をサポートするモダンなオペレーティングシステムでは、すべての組み込みWebサーバーで使える別の方法もあります。
$ ./script/my_app prefork -P /tmp/first.pid -l http://*:8080?reuse=1 Server available at http://127.0.0.1:8080.
すべきことは、同じポートで二つ目のWebサーバーを起動し、 その後に一つ目のWebサーバーをGraceful Shutdownさせることです。
$ ./script/my_app prefork -P /tmp/second.pid -l http://*:8080?reuse=1 Server available at http://127.0.0.1:8080. $ kill -s TERM `cat /tmp/first.pid`
両方のWebサーバーはreuse
パラメーターを付けて起動する必要があります。
Nginx
この頃、最も人気のある構成のひとつは、HypnotoadをNginxのリバースプロキシの後ろに置くものです。Nginxの新しいバージョンはWebSocketもサポートしています。
upstream myapp { server 127.0.0.1:8080; } server { listen 80; server_name localhost; location / { proxy_pass http://myapp; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } }
Apache/mod_proxy
その他ですぐれたリバースプロキシといえばApacheのmod_proxy
でしょう。設定は先ほどのNginxにとてもよく似ています。また、WebSocketサポートが必要な場合、新しいバージョンにはmod_proxy_wstunnel
が付属します。
<VirtualHost *:80> ServerName localhost <Proxy *> Require all granted </Proxy> ProxyRequests Off ProxyPreserveHost On ProxyPass /echo ws://localhost:8080/echo ProxyPass / http://localhost:8080/ keepalive=On ProxyPassReverse / http://localhost:8080/ RequestHeader set X-Forwarded-Proto "http" </VirtualHost>
Apache/CGI
CGI
はそのままですぐにサポートされ、Mojoliciousアプリケーションは、CGI
スクリプトとして実行されていることを自動的に検出します。ただし、本番環境での使用は推奨されません。CGI
の動作の仕組みがゆえに、非常に遅く、Webサーバーの種類が多いことから適切な設定が非常に難しくなっています。また、WebSocketなどの多くのリアルタイムWeb機能が使用できません。
ScriptAlias / /home/sri/myapp/script/my_app
PSGI/Plack
PSGIは、Perl WebフレームワークとWebサーバー間のインターフェースです。Plack
は、Perlモジュールおよびツールキットであり、PSGI
のミドルウェア、ヘルパー、およびWebサーバーへのアダプターを含みます。PSGI と Plack
は Python の WSGI と Ruby の Rack に触発されています。Plack
を使用したMojoliciousアプリケーションのデプロイはあっけないほど簡単です。ただし、WebSocketなどの多くのリアルタイムWeb機能は使用できないことに注意してください。
$ plackup ./script/my_app
Plack
は、FCGI
、uWSGI
、mod_perl
など、多くのサーバーやプロトコルのためのアダプタを提供します。
$ plackup ./script/my_app -s FCGI -l /tmp/myapp.sock
MOJO_REVERSE_PROXY
環境変数は、プロキシのサポートのために利用できます。Mojoliciousが自動的にX-Forwarded-For
とX-Forwarded-Proto
ヘッダーを取得できるようになります。
$ MOJO_REVERSE_PROXY=1 plackup ./script/my_app
古いサーバーのアダプタがアプリケーションのホームディレクトリを正しく検知できなかった場合は、単純にMOJO_HOME
環境変数を使用できます。
$ MOJO_HOME=/home/sri/my_app plackup ./script/my_app
.psgi
ファイルは必要ありません。サーバーアダプターをアプリケーションスクリプトにて指定しておけば、PLACK_ENV
環境変数を検知したときに、自動的に.psgi
ファイルであるかのように振舞います。
Plackミドルウェア
myapp.fcgi
などのラッパースクリプトを使うのは、デプロイメントとアプリケーションロジックを分離するとてもよい方法です。
#!/usr/bin/env plackup -s FCGI use Plack::Builder; builder { enable 'Deflater'; require './script/my_app'; };
Mojo::Server::PSGIを直接使うと、ラッパースクリプトのなかでアプリケーションを読み込んだりカスタマイズしたりできます。
#!/usr/bin/env plackup -s FCGI use Mojo::Server::PSGI; use Plack::Builder; builder { enable 'Deflater'; my $server = Mojo::Server::PSGI->new; $server->load_app('./script/my_app'); $server->app->config(foo => 'bar'); $server->to_psgi_app; };
アプリケーションの中でミドルウェアを使うことさえできます。
use Mojolicious::Lite; use Plack::Builder; get '/welcome' => sub { my $c = shift; $c->render(text => 'Hello Mojo!'); }; builder { enable 'Deflater'; app->start; };
書き換え
ときとして、アプリケーションを、自分でサーバの設定を変えることができないブラックボックス環境、または、 X-*
ヘッダーで補助情報を伝えるリバースプロキシの背後でデプロイする必要があるかもしれません。そのような場合、Mojoliciousのbefore_dispatch
フックを使用して受信リクエストを書き換えられます。
# "X-Forwarded-HTTPS"ヘッダーが"https"に設定されていた場合にスキーマを変更 $app->hook(before_dispatch => sub { my $c = shift; $c->req->url->base->scheme('https') if $c->req->headers->header('X-Forwarded-HTTPS'); });
一般的にリバースプロキシはアプリケーションがデプロイされたパスの前部分を渡さないので、受信するリクエストのベースパスの書き換えがよく行われます。これにより、たとえばMojolicious::Controllerの/"url_for"
を使って現在の環境に合わせたURLを生成できます。
# productionモードではパスの最初の部分とスラッシュをベースパスに移動 $app->hook(before_dispatch => sub { my $c = shift; push @{$c->req->url->base->path->trailing_slash(1)}, shift @{$c->req->url->path->leading_slash(0)}; }) if $app->mode eq 'production';
Mojo::URLオブジェクトの操作はとても簡単です。常に、ルーティングの行き先を表すURL (foo/bar?baz=yada)を、アプリケーションのデプロイメント場所を示すベースURL(http://example.com/myapp/)からの相対パスとなるようにします。
アプリケーションの埋め込み
時によって、設定ファイル、データベース接続、その他スクリプトのためのヘルパーなど、Mojoliciousアプリケーションのパーツを再利用したい場合があるかもしれません。次のようなMojo::Serverをベースにしたモックサーバーでそれらを埋め込むことができます。
use Mojo::Server; # モックサーバーでアプリケーションをロード my $server = Mojo::Server->new; my $app = $server->load_app('./myapp.pl'); # 完全に初期化されたアプリケーションにアクセス say for @{$app->static->paths}; say $app->config->{secret_identity}; say $app->dumper(just => 'a helper test'); say $app->build_controller->render_to_string(template => 'foo');
Mojolicious::Plugin::Mountは、この機能を使って複数のアプリケーションをひとつに結合し、まとめてデプロイできるようにします。
use Mojolicious::Lite; app->config(hypnotoad => {listen => ['http://*:80']}); plugin Mount => {'test1.example.com' => '/home/sri/myapp1.pl'}; plugin Mount => {'test2.example.com' => '/home/sri/myapp2.pl'}; app->start;
Webサーバーの埋め込み
Mojo::IOLoopのone_tick
を使用して、組み込みWebサーバーMojo::Server::Daemonを、なんらかの理由で新しいリアクターのバックエンドに統合できない外部イベントループのような異なった環境に埋め込むことができます。
use Mojolicious::Lite; use Mojo::IOLoop; use Mojo::Server::Daemon; # 通常のアクション get '/' => {text => 'Hello World!'}; # アプリケーションをWebサーバーと接続して、接続の受付を開始する my $daemon = Mojo::Server::Daemon->new(app => app, listen => ['http://*:8080']); $daemon->start; # 外部の環境から"one_tick"を繰り返し呼び出す Mojo::IOLoop->one_tick while 1;
リアルタイムWeb
リアルタイムWebとは、Comet(ロングポーリング)、EventSource、WebSockeといったテクノロジーの集まりのことです。伝統的なプルモデルに頼る代わりに長時間接続を用いることで、コンテンツが生成されるとすぐにクライアントにプッシュすることができます。すべての組み込みサーバーはノンブロッキングI/Oを使っていて、Mojo::IOLoopのリアクターをベースにしています。多くの強力な機能によって、リアルタイムWebアプリケーションをスケールアップすることができ、数千の並列のクライアントを処理できます。
Webサービスのバックエンド
Mojo::UserAgentはMojo::IOLoopのリアクターをベースに作られているため、ノンブロッキングで利用されるとき、高レイテンシのバックエンドWebサービスであっても、組み込みWebサーバーをブロックしません。
use Mojolicious::Lite; # MetaCPANで"mojolicious"を検索 get '/' => sub { my $c = shift; $c->ua->get('fastapi.metacpan.org/v1/module/_search?q=mojolicious' => sub { my ($ua, $tx) = @_; $c->render('metacpan', hits => $tx->result->json->{hits}{hits}); }); }; app->start; __DATA__; @@ metacpan.html.ep <!DOCTYPE html> <html> <head><title>MetaCPAN results for "mojolicious"</title></head> <body> % for my $hit (@$hits) { <p><%= $hit->{_source}{release} %></p> % } <body> </html>
Mojo::UserAgentの/"get"
に渡されるコールバックは、バックエンドWebサービスへのリクエストが完了すると実行されます。これは継続渡しスタイルと呼ばれます。
=head2ノンブロッキング処理の同期
同時リクエストなどの複数のノンブロッキング処理は、promiseおよびMojo::Promiseの/"all"
を使って簡単に同期できます。Mojo :: Promiseオブジェクトを手動で作成するか、Mojo :: UserAgentの/"get_p"
などのメソッドを使用してそれらが作成されるようにします。
use Mojolicious::Lite; use Mojo::Promise; use Mojo::URL; # MetaCPANで"mojo"と"minion"を検索 get '/' => sub { my $c = shift; # promiseをふたつ作成 my $url = Mojo::URL->new('fastapi.metacpan.org/v1/module/_search'); my $mojo = $c->ua->get_p($url->clone->query({q => 'mojo'})); my $minion = $c->ua->get_p($url->clone->query({q => 'minion'})); # promiseがふたつとも完了したらレスポンスを描画 Mojo::Promise->all($mojo, $minion)->then(sub { my ($mojo, $minion) = @_; $c->render(json => { mojo => $mojo->[0]->result->json('/hits/hits/0/_source/release'), minion => $minion->[0]->result->json('/hits/hits/0/_source/release') }); })->catch(sub { my $err = shift; $c->reply->exception($err); })->wait; }; app->start;
promiseを手動で作成するには、継続渡しスタイルのAPIをpromiseを返す関数でラップするだけです。ここで、Mojo::UserAgentの"get_p"
が内部でどのように動作するかを例にして説明します。
use Mojo::UserAgent; use Mojo::Promise; # ユーザーエージェントメソッドをpromiseでラップする my $ua = Mojo::UserAgent->new; sub get_p { my $promise = Mojo::Promise->new; $ua->get(@_ => sub { my ($ua, $tx) = @_; my $err = $tx->error; $promise->resolve($tx) if !$err || $err->{code}; $promise->reject($err->{message}); }); return $promise; } # 作成したpromiseを生成する関数を使う get_p('https://mojolicious.org')->then(sub { my $tx = shift; say $tx->result->dom->at('title')->text; })->wait;
promiseは3つの状態を持ちます。はじめはpending
となり、Mojo::Promiseの"resolve"
をコールするとfulfilled
に遷移し、あるいは、Mojo::Promiseの"reject"
をコールするとrejected
に遷移します。
タイマー
イベントループのもう1つの主要な機能であるタイマーは、Mojo::IOLoopの/"timer"
で作成します。タイマーは、たとえばレスポンスの描画を遅らせるために使用できます。sleep
とは異なり、並列に処理される他のリクエストをブロックしません。
use Mojolicious::Lite; use Mojo::IOLoop; # 3秒待ってレスポンスを描画する get '/' => sub { my $c = shift; Mojo::IOLoop->timer(3 => sub { $c->render(text => '3秒遅れています!'); }); }; app->start;
Mojo::IOLoopの/"recurring"
で作成した繰り返しタイマーは、もう少し強力ですが、手動で停止する必要があります。そうしないと、ひたすら動作し続けます。
use Mojolicious::Lite; use Mojo::IOLoop; # 1秒刻みで5まで数える get '/' => sub { my $c = shift; # 繰り返しタイマーを開始 my $i = 1; my $id = Mojo::IOLoop->recurring(1 => sub { $c->write_chunk($i); $c->finish if $i++ == 5; }); # 繰り返しタイマーを停止 $c->on(finish => sub { Mojo::IOLoop->remove($id) }); }; app->start;
タイマーは特定のリクエストや接続に関連付けられてはいないため、スタートアップ時に生成することもできます。
use Mojolicious::Lite; use Mojo::IOLoop; # 10秒ごとにバックグラウンドでタイトルをチェック my $title = 'Got no title yet.'; Mojo::IOLoop->recurring(10 => sub { app->ua->get('https://mojolicious.org' => sub { my ($ua, $tx) = @_; $title = $tx->result->dom->at('title')->text; }); }); # 現在のタイトルを表示 get '/' => sub { my $c = shift; $c->render(json => {title => $title}); }; app->start;
これらのノンブロッキング処理はすべて協調して処理されるため、コールバック関数が長時間ブロックされることはありません。
サブプロセス
サブプロセスをMojo::IOLoopの/"subprocess"
で作成して使用することで、イベントループをブロックせずに計算負荷の高い操作を実行できます。
use Mojolicious::Lite; use Mojo::IOLoop; # イベントループを5秒間ブロックする処理 get '/' => sub { my $c = shift; Mojo::IOLoop->subprocess( sub { my $subprocess = shift; sleep 5; return '♥', 'Mojolicious'; }, sub { my ($subprocess, $err, @results) = @_; $c->reply->exception($err) and return if $err; $c->render(text => "I $results[0] $results[1]!"); } ); }; app->start;
最初のコールバックは、親プロセスのイベントループをブロックせずに、子プロセスとして実行されます。最初のコールバックの結果は両方のプロセス間で共有され、2番目のコールバックが親プロセスで実行されます。
ノンブロッキング処理における例外
タイマーと他のノンブロッキング処理は、アプリケーション外部において単一のイベントループの中のみで実行されているため、コールバックの中で発生した例外は自動的にはキャッチできません。しかし、Mojo::Reactorのerror
イベントを購読することによって手動で処理することができます。また、コールバックの中でキャッチすることもできます。
use Mojolicious::Lite; use Mojo::IOLoop; # エラーメッセージをアプリケーションのログに転送する Mojo::IOLoop->singleton->reactor->on(error => sub { my ($reactor, $err) = @_; app->log->error($err); }); # 例外(と接続タイムアウト)だけをロギング get '/connection_times_out' => sub { my $c = shift; Mojo::IOLoop->timer(2 => sub { die 'このリクエストにはレスポンスが返ってこない。'; }); }; # 例外をキャッチして処理する get '/catch_exception' => sub { my $c = shift; Mojo::IOLoop->timer(2 => sub { eval { die 'このリクエストにはレスポンスが返る' }; $c->reply->exception($@) if $@; }); }; app->start;
通常、デフォルトですべてのエラーを警告に変換するサブスクライバーが、Mojo::IOLoopによってフォールバックとして追加されます。
Mojo::IOLoop->singleton->reactor->unsubscribe('error');
開発時またはクラッシュするのが望ましいアプリケーションのためには、サブスクライバーをすべて取り除くことによって、コールバックで発生するすべての例外が致命的なものになります。
WebSocketによるWebサービス
WebSocketプロトコルは、サーバーとクライアントの間を双方向で、低レイテンシでつなぐ通信チャンネルです。メッセージはMojo::Transaction::WebSocketの"message"
などのイベントをMojolicious::Controllerの"on"
でサブスクライブするだけで受信できます。そして、Mojolicious::Controllerの"send"
でメッセージを返します 。
use Mojolicious::Lite; # ブラウザ側コードを含むテンプレート get '/' => 'index'; # WebSocketのエコーサービス websocket '/echo' => sub { my $c = shift; # 接続を開く $c->app->log->debug('WebSocket opened'); # 接続のタイムアウト時間を少し増やす $c->inactivity_timeout(300); # 受信メッセージ $c->on(message => sub { my ($c, $msg) = @_; $c->send("echo: $msg"); }); # 接続を閉じる $c->on(finish => sub { my ($c, $code, $reason) = @_; $c->app->log->debug("WebSocket closed with status $code"); }); }; app->start; __DATA__; @@ index.html.ep <!DOCTYPE html> <html> <head><title>Echo</title></head> <body> <script> var ws = new WebSocket('<%= url_for('echo')->to_abs %>'); // 受信メッセージ ws.onmessage = function (event) { document.body.innerHTML += event.data + '<br/>'; }; // 送信メッセージ ws.onopen = function (event) { window.setInterval(function () { ws.send('Hello Mojo!') }, 1000); }; </script> </body> </html>
Mojo::Transaction::WebSocketの"finish"
イベントは、WebSocket接続が閉じるとすぐに発行されます。
$c->tx->with_compression;
Mojo::Transaction::WebSocketのwith_compression
を使って、permessage-deflate
圧縮を有効にすることができます。これはパフォーマンスを大きく改善しますが、接続あたりのメモリ使用量が最大で300KB増えます。
my $proto = $c->tx->with_protocols('v2.proto', 'v1.proto');
Mojo::Transaction::WebSocketの"with_protocols"
を使ってサブプロトコルをネゴシエートすることもできます。
EventSourceによるWebサービス
HTML5のEventSourceは、特別な形式のロングポーリング(Mojolicious::Controllerのwrite
を使うなど)です。サーバーからクライアントへDOMイベントを直接送信できます。送信は一方行なので、クライアントからサーバーへのデータの送信にはAjaxリクエストを使う必要があります。しかしながら、データ送信にHTTPプロトコルを再利用しており、インフラ要件が少ないという利点があります。
use Mojolicious::Lite; # ブラウザ側コードを含むテンプレート get '/' => 'index'; # ログメッセージのためのEventSource get '/events' => sub { my $c = shift; # 接続のタイムアウト時間を少し増やす $c->inactivity_timeout(300); # コンテンツタイプを変更して、レスポンスヘッダをファイナライズ $c->res->headers->content_type('text/event-stream'); $c->write; # "message"イベントをサブスクライブし、"log"イベントをブラウザに送る my $cb = $c->app->log->on(message => sub { my ($log, $level, $message) = @_; $c->write("event:log\ndata: [$level] @lines\n\n"); }); # 終わったら、"message"イベントのサブスクライブを解除する $c->on(finish => sub { my $c = shift; $c->app->log->unsubscribe(message => $cb); }); }; app->start; __DATA__; @@ index.html.ep <!DOCTYPE html> <html> <head><title>LiveLog</title></head> <body> <script> var events = new EventSource('<%= url_for 'events' %>'); // "log"イベントをサブスクライブする events.addEventListener('log', function (event) { document.body.innerHTML += event.data + '<br/>'; }, false); </script> </body> </html>
Mojo::Logのmessage
イベントは、ログメッセージが発生するごと放出され、Mojo::Transactionのfinish
イベントはトランザクションが完了した直後に放出されます。
マルチパートアップロードのストリーミング
Mojoliciousは、Mojo::EventEmitterを基盤とするとても洗練されたイベントシステムをもち、ほとんどすべての層でイベントがすぐに使えます。そして、このイベントシステムを組み合わせることで、WEB開発のなかで難易度が高い問題を解決することができるでしょう。
use Mojolicious::Lite; use Scalar::Util 'weaken'; # マルチパートアップロードの間に割り入って、チャンクを受け取るごとにログを出力する hook after_build_tx => sub { my $tx = shift; # "upgrade"イベントをサブスクライブして、マルチパートアップロードを識別する weaken $tx; $tx->req->content->on(upgrade => sub { my ($single, $multi) = @_; return unless $tx->req->url->path->contains('/upload'); # "part"イベントをサブスクライブして、目当てのモノを探す $multi->on(part => sub { my ($multi, $single) = @_; # "body"イベントをサブスクライブして、ヘッダがすべてあることを確認する $single->on(body => sub { my $single = shift; # 正しいパーツを持っていることを確認し、"read"イベントを置き換える return unless $single->headers->content_disposition =~ /example/; $single->unsubscribe('read')->on(read => sub { my ($single, $bytes) = @_; # 受け取ったチャンクごとにサイズをログに出力する app->log->debug(length($bytes) . ' bytes uploaded'); }); }); }); }); }; # DATAセクションにあるアップロードフォーム get '/' => 'index'; # マルチパートアップロードをストリーミング post '/upload' => {text => 'アップロードが成功しました。'}; app->start; __DATA__; @@ index.html.ep <!DOCTYPE html> <html> <head><title>Streaming multipart upload</title></head> <body> %= form_for upload => (enctype => 'multipart/form-data') => begin %= file_field 'example' %= submit_button 'Upload' % end <body> </html>
その他のイベントループ
内部的にMojo::IOLoopリアクターは複数のイベントループのバックエンドを利用できます。たとえばEVは、インストールされていれば自動的に使用されます。したがって、AnyEventのようなイベントループも正しく動かすことができます。
use Mojolicious::Lite; use EV; use AnyEvent; # 3秒待ってレスポンスを描画する get '/' => sub { my $c = shift; my $w; $w = AE::timer 3, 0, sub { $c->render(text => '3秒遅れています!'); undef $w; }; }; app->start;
バックエンドで実際に誰がイベントループを制御するかは重要ではありません。
use Mojo::UserAgent; use EV; use AnyEvent; # MetaCPANで"mojolicious"を検索 my $cv = AE::cv; my $ua = Mojo::UserAgent->new; $ua->get('fastapi.metacpan.org/v1/module/_search?q=mojolicious' => sub { my ($ua, $tx) = @_; $cv->send($tx->result->json('/hits/hits/0/_source/release')); }); say $cv->recv;
たとえば、組み込みのWebサーバーをAnyEventアプリケーションに組み込むこともできます。
use Mojolicious::Lite; use Mojo::Server::Daemon; use EV; use AnyEvent; # 通常のアクション get '/' => {text => 'Hello World!'}; # アプリケーションをWebサーバーと接続して、接続の受付を開始する my $daemon = Mojo::Server::Daemon->new(app => app, listen => ['http://*:8080']); $daemon->start; # AnyEventにコントロールさせる AE::cv->recv;
ユーザーエージェント
わたしたちが Mojolicious はウェブフレームワークだと言うとき、それは本気です。Mojo::UserAgentには、フル機能を備えたHTTPとWebSocketのユーザーエージェントが組み込まれています。
RESTウェブサービス
リクエストは、Mojo::UserAgentの"get"
などのメソッドによって快適に実行できます。返り値は常にMojo::Transaction::HTTPオブジェクトになります。このオブジェクトには多くの便利な属性とメソッドがあります。Mojo::Transactionの"result"
で接続エラーが確認できます。または、
use Mojo::UserAgent; #リソースをリクエストし、接続エラーがないことを確認する my $ua = Mojo::UserAgent->new; my $tx = $ua->get('mojolicious.org/perldoc/Mojo' => {Accept => 'text/plain'}); my $res = $tx->result; # 応答に合わせて何をするかを決める if ($res->is_success) { say $res->body } elsif ($res->is_error) { say $res->message } elsif ($res->code == 301) { say $res->headers->location } else { say 'Whatever...' }
Mojo::Message::Responseの"is_success"
とMojo::Message::Responseの"is_error"
のようなメソッドが、RESTクライアントをより洗練されたものにしています。
ウェブスクレイピング
過去、ウェブサイトから情報をスクレイピングするのがこれほど面白かったことはありません。組み込みの XML/HTML5 パーサ Mojo::DOM は、[[Mojo::Message]のdom
を通して利用でき、スタンドアロンパーサが理解できる全ての CSS3 セレクタをサポートします。とくにWebアプリケーションをテストするためのツールとしてとても強力です。
use Mojo::UserAgent; # ウェブサイトを取得 my $ua = Mojo::UserAgent->new; my $res = $ua->get('mojolicious.org/perldoc')->result; # タイトルを抽出 say 'Title: ', $res->dom->at('head > title')->text; # 見出しを抽出 $res->dom('h1, h2, h3')->each(sub { say 'Heading: ', shift->all_text }); # すべてのノードを再帰的にたどって、テキストとその他のものを抽出する for my $n ($res->dom->descendant_nodes->each) { # テキストまたはCDATAノード print $n->content if $n->type eq 'text' || $n->type eq 'cdata'; # 画像のAltテキストを含める print $n->{alt} if $n->type eq 'tag' && $n->tag eq 'img'; }
利用可能なCSSセレクタの完全なリストについては、Mojo::DOM::CSSの"SELECTORS"
を見てください。
JSON ウェブサービス
最近ではウェブサービスのほとんどが、データ交換フォーマットとしてJSONを使っています。 なのでMojoliciousには、ピュアPerl実装としてはおそらく最速であるMojo::JSONが組み込まれています。ここにはMojo::Messageの"json"
からアクセスできます。
use Mojo::UserAgent; use Mojo::URL; # 新しいユーザーエージェント my $ua = Mojo::UserAgent->new; # MetaCPANで"mojolicious"を検索して、最新のリリースを表示する my $url = Mojo::URL->new('http://fastapi.metacpan.org/v1/release/_search'); $url->query({q => 'mojolicious', sort => 'date:desc'}); for my $hit (@{$ua->get($url)->result->json->{hits}{hits}}) { say "$hit->{_source}{name} ($hit->{_source}{author})"; }
ベーシック認証
ユーザ名とパスワードを URL に追加するだけで、Authorization
ヘッダーが自動的に生成されます。
use Mojo::UserAgent; my $ua = Mojo::UserAgent->new; say $ua->get('https://sri:secret@example.com/hideout')->result->body;
追加リクエストの装飾
Mojo::UserAgentは自動的にリダイレクトを辿り、"start"
イベントが、トランザクションが初期化された後かつそれが接続と関連付けられる前に、それぞれのトランザクションへの直接アクセスを可能にします。
use Mojo::UserAgent; # 最大10回までリダイレクトを追跡するユーザーエージェント my $ua = Mojo::UserAgent->new(max_redirects => 10); # 各リクエストに気の利いたヘッダを追加 $ua->on(start => sub { my ($ua, $tx) = @_; $tx->req->headers->header('X-Bender' => 'Bite my shiny metal ass!'); say 'Request: ', $tx->req->url->clone->to_abs; }); # リダイレクトされるであろうリクエスト say 'Title: ', $ua->get('google.com')->result->dom->at('head > title')->text;
これはプロキシへの CONNECT
リクエストに対しても使えます。
コンテンツジェネレーター
コンテンツジェネレーターは、Mojo::UserAgent::Transactorの"add_generator"
で登録できます。こうすると、複数のリクエストに対して同じタイプのコンテンツを繰り返し生成することができます。
use Mojo::UserAgent; use Mojo::Asset::File; # "stream"ジェネレーターを追加 my $ua = Mojo::UserAgent->new; $ua->transactor->add_generator(stream => sub { my ($transactor, $tx, $path) = @_; $tx->req->content->asset(Mojo::Asset::File->new(path => $path)); }); # PUTとPOSTを通して複数のファイルのストリーミングを送信 $ua->put('http://example.com/upload' => stream => '/home/sri/mojo.png'); $ua->post('http://example.com/upload' => stream => '/home/sri/minion.png');
json
、form
およびmultipart
コンテンツジェネレーターが常に利用可能です。
use Mojo::UserAgent; # PATCHを使って"application/json"コンテンツを送信 my $ua = Mojo::UserAgent->new; my $tx = $ua->patch('http://api.example.com' => json => {foo => 'bar'}); # GETを使ってクエリパラメーターを送信 my $tx2 = $ua->get('search.example.com' => form => {q => 'test'}); # POSTを使って"application/x-www-form-urlencoded"コンテンツを送信 my $tx3 = $ua->post('http://search.example.com' => form => {q => 'test'}); # PUTを使って"multipart/form-data"コンテンツを送信 my $tx4 = $ua->put( 'upload.example.com' => form => {test => {content => 'Hello World!'}}); # PUTを使ってカスタムマルチパートを送信 my $tx5 = $ua->put('api.example.com' => multipart => ['Hello', 'World!']);
コンテンツジェネレーターについて、より詳しい情報はMojo::UserAgent::Transactorのtx
を見てください。
大きなファイルのダウンロード
Mojo::UserAgentで大きなファイルをダウンロードする場合、メモリ使用量を心配する必要はまったくありません。250KB以上のものはすべてが自動的に一時ファイルにストリーミングされ、Mojo::Messageの"save_to"
で永続ファイルに移動できるからです。
use Mojo::UserAgent; # 最新のMojolicious tarballを取得する my $ua = Mojo::UserAgent->new(max_redirects => 5); my $tx = $ua->get('https://www.github.com/mojolicious/mojo/tarball/master'); $tx->result->save_to('mojo.tar.gz');
極端に大きいファイルから保護するために、デフォルトで2GBの制限もあります。上限サイズはMojo::UserAgentの"max_response_size"
属性で調整できます。
# 上限を10GBに増やす $ua->max_response_size(10737418240);
大きなファイルのアップロード
大きなファイルのアップロードはさらに簡単です。
use Mojo::UserAgent; # POST と "multipart/form-data" 経由でファイルをアップロード my $ua = Mojo::UserAgent->new; $ua->post('example.com/upload' => form => {image => {file => '/home/sri/hello.png'}});
ここでもメモリ使用量を心配する必要はなく、すべてのデータがファイルから直接ストリーミングされます。
ストリーミングレスポンス
ストリーミングレスポンスの受信は、多くのHTTPクライアントで非常に難しくなりがちですが、Mojo::UserAgentでは実に簡単です。
use Mojo::UserAgent; # 不定サイズのレスポンスを受け入れる my $ua = Mojo::UserAgent->new(max_response_size => 0); # 通常のトランザクションを立てる my $tx = $ua->build_tx(GET => 'http://example.com'); # "read"イベントを置き換えて、デフォルトのコンテントパーサーを無効にする $tx->res->content->unsubscribe('read')->on(read => sub { my ($content, $chunk) = @_; say "Streaming: $chunk"; }); # トランザクションの処理 $tx = $ua->start($tx);
Mojo::Contentの"read"
イベントが受信されるすべてのデータチャンクに対して発行されます。チャンク転送エンコードやgzipコンテンツエンコードも、必要に応じて透過的に処理されます。
ストリーミングリクエスト
ストリーミングリクエストを送るのもほとんど同じくらい簡単です。
use Mojo::UserAgent; # 通常のトランザクションを立てる my $ua = Mojo::UserAgent->new; my $tx = $ua->build_tx(GET => 'http://example.com'); # ボディを準備する my $body = 'Hello World!'; $tx->req->headers->content_length(length $body); # 排出コールバックに直接書き込みを開始する my $drain; $drain = sub { my $content = shift; my $chunk = substr $body, 0, 1, ''; $drain = undef unless length $body; $content->write($chunk, $drain); }; $tx->req->content->$drain; # トランザクションの処理 $tx = $ua->start($tx);
Mojo::Contentの"write"
に渡されるドレインコールバックは、前のデータチャンク全体が実際に書き込まれるたびに実行されます。
ノンブロッキング
Mojo::UserAgentはノンブロッキングとしてゼロから設計されており、ブロッキングAPI全体は単に便宜的なラッパーです。特に、ウェブクローリングのような待ち時間が大きな処理では、同時に多くの並列接続をアクティブに保つことができて非常に便利です。
use Mojo::UserAgent; use Mojo::IOLoop; # 並列のノンブロッキングリクエスト my $ua = Mojo::UserAgent->new; $ua->get('https://metacpan.org/search?q=mojo' => sub { my ($ua, $mojo) = @_; say $mojo->result->dom->at('title')->text; }); $ua->get('https://metacpan.org/search?q=minion' => sub { my ($ua, $minion) = @_; say $minion->result->dom->at('title')->text; }); # 必要であればイベントループを開始 Mojo::IOLoop->start unless Mojo::IOLoop->is_running;
ただし、1つのサーバーに対して同時に多くの接続を開かないでください。接続過多になる可能性があります。キューを使用して、リクエストを小さなバッチで処理する方が適切です。
use Mojo::UserAgent; use Mojo::IOLoop; my @urls = ( 'mojolicious.org/perldoc/Mojo/DOM', 'mojolicious.org/perldoc/Mojo', 'mojolicious.org/perldoc/Mojo/File', 'mojolicious.org/perldoc/Mojo/URL' ); # 最大5つのリダイレクトに追跡するカスタム名を持つユーザーエージェント my $ua = Mojo::UserAgent->new(max_redirects => 5); $ua->transactor->name('MyParallelCrawler 1.0'); # 遅延を使用して、完了するまでイベントループを回し続ける my $delay = Mojo::IOLoop->delay; my $fetch; $fetch = sub { # URLがなくなったら停止 return unless my $url = shift @urls; # 次のタイトルを取得 my $end = $delay->begin; $ua->get($url => sub { my ($ua, $tx) = @_; say "$url: ", $tx->result->dom->at('title')->text; # 次のリクエスト $fetch->(); $end->(); }); }; # 一度に2つのリクエストを処理する $fetch->() for 1 .. 2; $delay->wait;
また、どんなときもサイトのrobots.txt
ファイルと利用規約を尊重し、同じホストへの接続を再度開く前には少し待つようにしましょう。そうでないと、管理者はアクセスをブロックせざるを得なくなるかもしれません。
並列のブロッキングリクエスト
これまでのサンプルですでにMojo::Promiseの"wait"
を見たことがあるかもしれません。これはノンブロッキング処理を移植可能にするために使用され、これらの処理を既に実行中のイベントループ内で動かしたり、必要に応じて開始したりできます。
use Mojo::UserAgent; use Mojo::Promise; # ノンブロッキングリクエストをプロミスと同期する my $ua = Mojo::UserAgent->new; my $mojo = $ua->get_p('https://metacpan.org/search?q=mojo'); my $minion = $ua->get_p('https://metacpan.org/search?q=minion'); Mojo::Promise->all($mojo, $minion)->then(sub { my ($mojo, $minion) = @_; say $mojo->result->dom->at('title')->text; say $minion->[0]->result->dom->at('title')->text; })->wait;
WebSocket
WebSocketはサーバー側だけのものではなく、Mojo::UserAgentの"websocket_p"
を使って、常にノンブロッキングで動く新しい接続を開くことができます。WebSocketハンドシェイクはHTTPを使用しています。また、通常のGET
メソッドにいくつかヘッダーが追加されたリクエストです。ここにクッキーを含むこともできます。ハンドシェイクに続いて、サーバーからの101
レスポンスによって、接続が確立したことがユーザーエージェントに通知されます。するとWebSocketプロトコルを使った双方向通信が開始します。
use Mojo::UserAgent; use Mojo::Promise; # エコーサービスのためにWebSocketを開く my $ua = Mojo::UserAgent->new; $ua->websocket_p('ws://echo.websocket.org')->then(sub { my $tx = shift; # メッセージを待つことができるようにフォローアップ用のプロミスを準備する my $promise = Mojo::Promise->new; # WebSocketが閉じるのを待つ $tx->on(finish => sub { my ($tx, $code, $reason) = @_; say "WebSocket closed with status $code."; $promise->resolve; }); # ひとつのメッセージを受け取った後にWebSocketを閉じる $tx->on(message => sub { my ($tx, $msg) = @_; say "WebSocket message: $msg"; $tx->finish; }); # サーバーにメッセージを送信 $tx->send('Hi!'); # 新しいプロミスをプロミスチェーンに挿入する return $promise; })->catch(sub { my $err = shift; # 失敗したWebSocketハンドシェイクおよびその他の例外を処理する warn "WebSocket error: $err"; })->wait;
UNIXドメインソケット
TCP / IPソケットだけでなく、UNIXドメインソケットもサポートされています。これはプロセス間通信に使用すると、セキュリティとパフォーマンスの面で大きなメリットがあります。http://
およびws://
の代わりにhttp+unix://
スキーマを使用できます。パーセントエンコードパス(/
は%2F
になる)をホストネームの代わりに渡します。
use Mojo::UserAgent; use Mojo::Promise; # UNIXドメインソケット"/tmp/foo.sock"を介したGETリクエスト my $ua = Mojo::UserAgent->new; say $ua->get('http+unix://%2Ftmp%2Ffoo.sock/index.html')->result->body; # UNIXドメインソケット "/tmp/bar.sock"を介したHOSTヘッダー付きのGETリクエスト my $tx = $ua->get('http+unix://%2Ftmp%2Fbar.sock' => {Host => 'example.com'}); say $tx->result->body; # UNIXドメインソケット "/tmp/baz.sock" を介したWebSocket接続 $ua->websocket_p('ws+unix://%2Ftmp%2Fbaz.sock/echo')->then(sub { my $tx = shift; my $promise = Mojo::Promise->new; $tx->on(finish => sub { $promise->resolve }); $tx->on(message => sub { my ($tx, $msg) = @_; say "WebSocket message: $msg"; $tx->finish; }); $tx->send('Hi!'); return $promise; })->catch(sub { my $err = shift; warn "WebSocket error: $err"; })->wait;
Host
ヘッダーを手動で設定してホスト名を渡すことができます。
コマンドライン
コマンドラインから巨大な HTML ファイルをチェックするのは嫌ですよね?コマンドMojolicious::Command::getのおかげでそれは変わろうとしています。実際に重要な部分だけをMojo::DOMのCSSセレクターやMojo::JSON::PointerのJSONポインターで取り出せます。
$ mojo get https://mojolicious.org 'head > title'
すべての id 属性のリストを見るには?
$ mojo get https://mojolicious.org '*' attr id
または、すべての見出しタグのテキスト内容は?
$ mojo get https://mojolicious.org 'h1, h2, h3' text
三番目の見出しのテキストは?
$ mojo get https://mojolicious.org 'h1, h2, h3' 3 text
ネストした子要素からもテキストをすべて抽出できます。
$ mojo get https://mojolicious.org '#mojobar' all
リクエストはカスタマイズすることもできます。
$ mojo get -M POST -H 'X-Bender: Bite my shiny metal ass!' http://google.com
レスポンスデータをSTDOUT
にリダイレクトして保存します。
$ mojo get mojolicious.org > example.html
リクエストデータをSTDIN
にリダイレクトして渡します。
$ mojo get -M PUT mojolicious.org < example.html
または、別のプログラムの出力を使用します。
$ echo 'Hello World' | mojo get -M PUT https://mojolicious.org
application/x-www-form-urlencoded
コンテンツとしてフォームを送信します。
$ mojo get -M POST -f 'q=Mojo' -f 'size=5' https://metacpan.org/search
さらに、multipart/form-data
コンテンツとしてファイルをアップロードします。
$ mojo get -M POST -f 'upload=@example.html' mojolicious.org
リダイレクトを辿ってすべての (HTTP) ヘッダメッセージを見ることができます。
$ mojo get -r -v http://google.com 'head > title'
JSONデータ構造から本当に必要な情報を抽出できます。
$ mojo get https://fastapi.metacpan.org/v1/author/SRI /name
アプリケーションのテストに真価を発揮するツールとなることでしょう。
$ ./myapp.pl get /welcome 'head > title'
ワンライナー
手早くハックしたいとき、とくにテストのためには、ojo
ワンライナーはすぐれた選択です。
$ perl -Mojo -E 'say g("mojolicious.org")->dom->at("title")->text'
アプリケーション
楽しい Mojolicious アプリケーションはどんな場面でもハックが満載です。
ベーシック認証
ベーシック認証のデータは、Authorization
ヘッダから自動的に抽出されます。
use Mojolicious::Lite; use Mojo::Util 'secure_compare'; get '/' => sub { my $c = shift; # ユーザー名"Bender"とパスワード"rocks"をチェック return $c->render(text => 'Hello Bender!') if secure_compare $c->req->url->to_abs->userinfo, 'Bender:rocks'; # 認証を要求 $c->res->headers->www_authenticate('Basic'); $c->render(text => 'Authentication required!', status => 401); }; app->start;
TLSを一緒に使って安全に認証することもできます。
$ ./myapp.pl daemon -l 'https://*:3000?cert=./server.crt&key=./server.key'
設定ファイルの追加
構成ファイルをアプリケーションに追加するのは、ファイルをホームディレクトリに追加してMojolicious::Plugin::Configプラグインをロードするのと同じくらい簡単です。デフォルト名はMojoliciousの"moniker"
(C .conf
拡張子を付け足したものになります(C
$ mkdir myapp $ cd myapp $ touch myapp.pl $ chmod 744 myapp.pl $ echo '{name => "my Mojolicious application"};' > myapp.conf
構成ファイル自体は、選択した構成設定を含むハッシュ参照を返す単なるPerlスクリプトです。これらの設定はすべて、メソッドMojoliciousの"config"
とMojolicious::Plugin::DefaultHelpersの"config"
で利用できます。
use Mojolicious::Lite; plugin 'Config'; my $name = app->config('name'); app->log->debug("Welcome to $name"); get '/' => 'with_config'; app->start; __DATA__; @@ with_config.html.ep <!DOCTYPE html> <html> <head><title><%= config 'name' %></title></head> <body>Welcome to <%= config 'name' %></body> </html>
または、Mojolicious::Plugin::JSONConfigでJSON形式の設定ファイルを使用することもできます。
アプリケーションにプラグインを追加
コードをよく整理し、アプリケーションがヘルパーでごちゃごちゃになるのを防ぐために、アプリケーションを指定してプラグインを作成できます。
$ mkdir -p lib/MyApp/Plugin $ touch lib/MyApp/Plugin/MyHelpers.pm
通常のプラグインと同じように動き、Mojolicious::Pluginのサブクラスにもなっています。プラグインの名前にプレフィックスを付け、ヘルパーをネストさせると、簡単に衝突を避けることができます。
package MyApp::Plugin::MyHelpers; use Mojo::Base 'Mojolicious::Plugin'; sub register { my ($self, $app) = @_; $app->helper('my_helpers.render_with_header' => sub { my ($c, @args) = @_; $c->res->headers->header('X-Mojo' => 'I <3 Mojolicious!'); $c->render(@args); }); } 1;
アプリケーション固有のプラグインはいくつでも使用できますが、通常のプラグインとの唯一の違いは、ロードするために完全なクラス名を使用するところです。
use Mojolicious::Lite; use lib 'lib'; plugin 'MyApp::Plugin::MyHelpers'; get '/' => sub { my $c = shift; $c->my_helpers->render_with_header(text => 'I ♥ Mojolicious!'); }; app->start;
もちろん、これらのプラグインにはヘルパー以外のものを含めることができます。Mojolicious::Pluginsの"PLUGINS"
にいくつかサンプルがあります。
Mojoliciousにコマンドを追加
おそらくこれまでに、Mojolicious::Commandsにある組み込みコマンドを多く使ったことでしょう。でも、新しいコマンドを追加するだけで、自動的にコマンドラインインターフェイスから使えるようになることは知っていましたか? そのためにはコマンドを@INC
が参照するディレクトリに追加します。
package Mojolicious::Command::spy; use Mojo::Base 'Mojo::Command'; has description => 'Spy on application'; has usage => "Usage: APPLICATION spy [TARGET]\n"; sub run { my ($self, @args) = @_; # シークレットパスフレーズを漏らす if ($args[0] eq 'secrets') { say for @{$self->app->secrets} } # モードを漏らす elsif ($args[0] eq 'mode') { say $self->app->mode } } 1;
コマンドライン引数はそのまま渡され、Mojolicious::Commandには、使用したりオーバーロードでる多くの便利な属性とメソッドがあります。
$ mojo spy secrets HelloWorld $ ./script/myapp spy secrets secr3t
また、指定のアプリケーションでコマンドを使うようには、Mojolicious::Commandsの"namespaces"
にカスタム名前空間を追加するだけです。このとき、MyApp::Command::spyの代わりにMojolicious::Command::spyのように名前を付けてください。
# アプリケーション package MyApp; use Mojo::Base 'Mojolicious'; sub startup { my $self = shift; # コマンドを読み込む名前空間を追加 push @{$self->commands->namespaces}, 'MyApp::Command'; } 1;
オプション-h
/ -help
、-home
および-m
/ -mode
は、Mojolicious::Commandsによって自動的に処理され、すべてのコマンドで共有されます。
$ ./script/myapp spy -m production mode production
共有オプションの全一覧は、Mojolicious::Commandsの"SYNOPSIS"
を見てください。
アプリケーションに対してコードを実行する
あなたのMojoliciousアプリケーションをテストするために、ささっとワンライナーを走らせたいと思ったことはありますか?コマンドMojolicious::Command::evalを使うと、まさにこれが実現できます。アプリケーションオブジェクト自体にはapp
を通じてアクセスできます。
$ mojo generate lite_app myapp.pl $ ./myapp.pl eval 'say for @{app->static->paths}' $ ./myapp.pl eval 'say for sort keys %{app->renderer->helpers}'
verbose
オプションで、返り値またはPerlのデータ構造をSTDOUT
に自動的に出力できます。
$ ./myapp.pl eval -v 'app->static->paths->[0]' $ ./myapp.pl eval -v 'app->static->paths->[0]'
アプリケーションをインストール可能できるようにする
MojoliciousアプリケーションをCPANにリリースしたいと思ったことはありますか?これは思ったよりも簡単にできます。
$ mojo generate app MyApp $ cd my_app $ mv public lib/MyApp/ $ mv templates lib/MyApp/
コツはpublic
とtemplates
ディレクトリを移動させ、モジュールとともに自動的にインストールされるようにすることです。Mojolicious::Command::Author名前空間から追加される作成者コマンドは、通常、インストールされたアプリケーションには不要なので、除外できます。
# アプリケーション package MyApp; use Mojo::Base 'Mojolicious'; use Mojo::File 'path'; use Mojo::Home; # CPANモジュールには常にバージョンが必要です our $VERSION = '1.0'; sub startup { my $self = shift; # インストール可能なホームディレクトリへ切替 $self->home(Mojo::Home->new(path(__FILE__)->sibling('MyApp'))); # インストール可能な "public" ディレクトリへ切替 $self->static->paths->[0] = $self->home->child('public'); # インストール可能な "templates" ディレクトリへ切替 $self->renderer->paths->[0] = $self->home->child('templates'); # 作成者コマンドを除外 $self->commands->namespaces(['Mojolicious::Commands']); my $r = $self->routes; $r->route('/welcome')->to('example#welcome'); } 1;
最後に、アプリケーションスクリプトに小さな変更を1つ加えるだけです。シェバン行は、推奨される#!perl
になり、これはインストール中にツールチェーンによって適切なshebangに書き換えることができます。
#!perl use strict; use warnings; use FindBin; BEGIN { unshift @INC, "$FindBin::Bin/../lib" } use Mojolicious::Commands; # アプリケーションのためにコマンドラインインターフェイスを開始 Mojolicious::Commands->start_app('MyApp');
これが本当に全部です。これで他のCPAN モジュールのようにアプリケーションをパッケージすることができます。
$ ./script/my_app generate makefile $ perl Makefile.PL $ make test $ make manifest $ make dist
また、PAUSEアカウントを持っている場合(http://pause.perl.orgでリクエストできます)アップロードすることもできます。
$ mojo cpanify -u USER -p PASS MyApp-0.01.tar.gz
Hello World
1バイトも無駄にしたくないのであれば、これがMojolicious::Liteで作ることのできる最小のHello World
アプリケーションです。
use Mojolicious::Lite; any {text => 'Hello World!'}; app->start;
パターンがない場合、ルートはすべてデフォルトで /
となり、ルータによって コードが実際には実行されなくとも自動レンダリングが開始します。レンダラは、スタッシュから text
の値を拾い、レスポンスを生成します。
Hello World ワンライナー
上記のHello World
の例は、ojoワンライナーを使うともう少し短くできます。
$ perl -Mojo -E 'a({text => "Hello World!"})->start' daemon
そしてすべてのコマンドが Mojolicious::Commandsから使用できます。
$ perl -Mojo -E 'a({text => "Hello World!"})->start' get -v /
もっと学ぶには
さあ、Mojolicious::Guides を続けるか、Mojolicious wikiを見てみましょう。多くの著者がドキュメントやサンプルをたくさん書いています。
サポート
このドキュメントでわからない部分があれば、 mailing list かirc.freenode.net
(chat now!)の公式IRCチャンネル #mojo
まで気軽に質問してください。(2019/11/28 Mojolicious 8.12を反映)