Mojolicious::Guides::Routing

Yuki Kimoto edited this page Apr 12, 2016 · 28 revisions

Mojoliciousガイド

Mojolicious::Guides::Routing

名前

Mojolicious::Guides::Routing - ルーティング

概観

このドキュメントはMojoliciousのルータ(router)のシンプルで楽しい入門と基本的なコンセプトを含んでいます。

コンセプト

すべてのMojolicious開発者が知っておくべき必需品

ディスパッチャ

すべてのWebフレームワークの基盤は、到着したリクエストと適切なレスポンスを生成するコードを結びつける小さなブラックボックスです。

GET /user/show/1 -> $c->render(text => 'Sebastian');

このブラックボックスは普通ディスパッチャと呼ばれます。これらの接続を確立するため、いろいろな戦略を使う多くの実装がありますが、そのかなり多くはリクエストのパスと、いくつかの種類のレスポンスジェネレータを対応させることに基づいています。

/user/show/2 -> $c->render(text => 'Daniel');
/user/show/3 -> $c->render(text => 'Sara');
/user/show/4 -> $c->render(text => 'Baerbel');
/user/show/5 -> $c->render(text => 'Wolfgang');

これらすべてのコネクションを静的なものにすることは可能ですが、少し非効率です。それでディスパッチ処理をより動的にするために、通常は正規表現が利用されます。

qr!/user/show/(\d+)! -> $c->render(text => $users{$1});

現代的なディスパッチャは、ほとんどすべてのHTTPを自由に扱うために、リクエストメソッド、ヘッダ(Host、User-Agent、Acceptなど)の、リクエストのパスよりもさらに多くの情報を利用することがあります。

GET /user/show/23 HTTP/1.1
Host: mojolicio.us
User-Agent: Mojolicious (Perl)
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8

ルーティング(routes)

正規表現はとても強力である一方、見た目はあまりよくないですし、一般的に言って普通のパスのマッチングには行きすぎな面があります。

qr!/user/show/(\d+)! -> $c->render(text => $users{$1});

ここにルーティング(routes)があります。プレースホルダーによってパスを表すために徹底的にデザインされています。

/user/show/:id -> $c->render(text => $users{$id});

上記の例において、静的なパスとルーティングのたったひとつの違いは「:id」プレースホルダーです。一つ以上のプレースホルダーをルーティングの中のどこにでも置くことができます。

/user/:action/:id

Mojoliciousのルータの基本的なコンセプトは、抽出されたプレースホルダーの値はハッシュに変換されるということです。

/user/show/23 -> /user/:action/:id -> {action => 'show', id => 23}

このハッシュは基本的にすべてのMojoliciousアプリケーションの中心です。これについては後で学びます。内部的にはルーティングは正規表現にコンパイルされます。少しの経験でふたつの世界を乗り越えることができるでしょう。

/user/show/:id -> qr/(?-xism:^\/user\/show/([^\/.]+))/

パスの末尾のスラッシュは任意です。

/user/show/23/ -> /user/:action/:id -> {action => 'show', id => 23}

可逆性

正規表現に対するひとつのより大きなルーティングの利点は、簡単に元に戻すことができることです。抽出されたプレースホルダーはいつでもパスに戻すことができます。

/sebastian -> /:name -> {name => 'sebastian'}
{name => 'sebastian'} -> /:name -> /sebastian

たとえ空の文字列であっても、すべてのプレースホルダーは名前を持ちます。

標準的なプレースホルダー

標準的なプレースホルダーはプレースホルダーのもっとも簡単な形式で「/」と「.」を除くすべての文字にマッチします。

/hello              -> /:name/hello -> undef
/sebastian/23/hello -> /:name/hello -> undef
/sebastian.23/hello -> /:name/hello -> undef
/sebastian/hello    -> /:name/hello -> {name => 'sebastian'}
/sebastian23/hello  -> /:name/hello -> {name => 'sebastian23'}
/sebastian 23/hello -> /:name/hello -> {name => 'sebastian 23'}

一般的なプレースホルダーは、周囲のテキストから区別するために、括弧「()」で囲むことができます。正規表現の([^/.]+)に似ています。

/hello             -> /(:name)hello -> undef
/sebastian/23hello -> /(:name)hello -> undef
/sebastian.23hello -> /(:name)hello -> undef
/sebastianhello    -> /(:name)hello -> {name => 'sebastian'}
/sebastian23hello  -> /(:name)hello -> {name => 'sebastian23'}
/sebastian 23hello -> /(:name)hello -> {name => 'sebastian 23'}

コロンのプレフィックスは、括弧で囲まれた一般的なプレースホルダーのための任意のものです。

/i♥mojolicious -> /(one)♥(two) -> {one => 'i', two => 'mojolicious'}

リラックスプレースホルダー

リラックスプレースホルダーは、上記のふたつのプレースホルダーに似ていますが、「/」を除いたすべての文字にマッチする点が異なります。正規表現の([^/]+)に似ています。

/hello              -> /#name/hello -> undef
/sebastian/23/hello -> /#name/hello -> undef
/sebastian.23/hello -> /#name/hello -> {name => 'sebastian.23'}
/sebastian/hello    -> /#name/hello -> {name => 'sebastian'}
/sebastian23/hello  -> /#name/hello -> {name => 'sebastian23'}
/sebastian 23/hello -> /#name/hello -> {name => 'sebastian 23'}

特に拡張子のついたファイル名を手動でマッチングさせるときに、 フォーマット検知を使うよりも、 便利です。

/music/song.mp3 -> /music/#filename -> {filename => 'song.mp3'}

ワイルドカードプレースホルダー

ワイルドカードプレースホルダーは、ジェネリックプレースホルダーに似ていますが、 「/」と「.」を含むすべてにマッチします。(.+)

/hello              -> /*name/hello -> undef
/sebastian/23/hello -> /*name/hello -> {name => 'sebastian/23'}
/sebastian.23/hello -> /*name/hello -> {name => 'sebastian.23'}
/sebastian/hello    -> /*name/hello -> {name => 'sebastian'}
/sebastian23/hello  -> /*name/hello -> {name => 'sebastian23'}
/sebastian 23/hello -> /*name/hello -> {name => 'sebastian 23'}

基礎

すべてのMojolicious開発者が知っておくべきもっとも一般的に利用される機能

最小のルーティング

Mojoliciousroutes属性はルーティング構造を生成するために 使用することができるルーターを含みます。 これらは定義された順番にマッチします。

# アプリケーション
package MyApp;
use Mojo::Base 'Mojolicious';

sub startup {
  my $self = shift;

  # ルーター
  my $r = $self->routes;

  # ルーティング
  $r->get('/welcome')->to(controller => 'foo', action => 'welcome');
}

1;

上記の例の最小のルーティングは、MyApp::Fooというクラスをロードし、そのインスタンスを生成してwelcomeメソッドを呼び出します。

# コントローラー
package MyApp::Controller::Foo;
use Mojo::Base 'Mojolicious::Controller';

# アクション
sub welcome {
  my $self = shift;

  # レスポンスの描画
  $self->render(text => 'Hello there.');
}

1;

ルーティングは普通はアプリケーションクラスのstartupメソッドの中で設定されますが、すべての場所(たとえ実行中であっても)からアクセスすることができます。

ルーティングの行き先

Mojolicious::Routesrouteメソッドで新しいルーティングを開始した後、 Mojolicious::Routestoメソッドをチェーンして、行き先をハッシュ形式で与えることができます。

# /welcome -> {controller => 'foo', action => 'welcome'}
$r->get('/welcome')->to(controller => 'foo', action => 'welcome');

Mojolicious::Routes::Routerouteメソッドで、やってきたリクエストにルーティングがマッチすれば、 Mojolicious::Routes::Routetoメソッドを使ったハッシュの内容を使って レスポンスを生成するために適切なコードを見つけようとするでしょう。

HTTPメソッド

ルーティングオブジェクトのMojolicious::Routes::Routeviaメソッドを使えば、 特別なHTTPメソッドのみを通過させることができます。

Mojolicious::Routes::Routepostのような ほとんど一般的なHTTPリクエストメソッドには、 ショートカットがあります。 もっとコントロールしたい場合は、 Mojolicious::Routes::Routeany を使えば、 最初の引数として、任意のリクエストメソッドを 配列のリファレンスで指定することができます。

# PUT /hello  -> undef
# GET /hello  -> {controller => 'foo', action => 'hello'}
$r->get('/hello')->to(controller => 'foo', action => 'hello');

# PUT /hello -> {controller => 'foo', action => 'hello'}
$r->put('/hello')->to(controller => 'foo', action => 'hello');

# POST /hello -> {controller => 'foo', action => 'hello'}
$r->post('/hello')->to(controller => 'foo', action => 'hello');

# GET|POST /bye  -> {controller => 'foo', action => 'bye'}
$r->any([qw(GET POST)] => '/bye')->to(controller => 'foo', action => 'bye');

# * /whatever -> {controller => 'foo', action => 'whatever'}
$r->any('/whatever')->to(controller => 'foo', action => 'whatever');

ひとつの小さな例外として、HEADリクエストはGETリクエストと同じですが、 たとえ存在しても、レスポンスにおいてコンテンツは送られません。

# GET /test  -> {controller => 'bar', action => 'test'}
# HEAD /test -> {controller => 'bar', action => 'test'}
$r->get('/test')->to(controller => 'bar', action => 'test');

また、リクエストメソッドをオーバーライドするために、_methodクエリパラメーター を使用することもできます。 これは、GETPOSTだけをサポートするブラウザーのフォームのサブミットを行うときに とても便利です。

# PUT  /stuff             -> {controller => 'baz', action => 'stuff'}
# POST /stuff?_method=PUT -> {controller => 'baz', action => 'stuff'}
$r->put('/stuff')->to(controller => 'baz', action => 'stuff');

IRIs

IRIsを透過的に扱うことができます。これはパスがアンエスケープされ、Perlの文字にデコードされることが保証されるということです。

# /☃ (unicode snowman) -> {controller => 'foo', action => 'snowman'}
$r->route('/☃')->to(controller => 'foo', action => 'snowman');

スタッシュ

ルーティングにマッチして生成されたハッシュは、実際のMojoliciousのリクエストサイクル全体の中心です。私たちはそれをスタッシュと呼び、レスポンスが生成されるまで存続します。

# /bye -> {controller => 'foo', action => 'bye', mymessage => 'Bye'}
$r->get('/bye')
  ->to(controller => 'foo', action => 'bye', mymessage => 'Bye');

controlleractionなどのスタッシュの値のいくつかは特別な意味を持ちますが、一般的にはレスポンスを生成するために必要などんなデータでも入れることができます。いったんディスパッチされれば、スタッシュのすべての内容をいつでも変更することができます。

sub bye {
  my $self = shift;

  # スタッシュからメッセージを得る
  my $message = $self->stash('mymessage');

  # スタッシュのメッセージを変更
  $self->stash(mymessage => 'Welcome');
}

すべての予約された値を見るにはMojolicious::Controllerstashを参考にしてください。

ネストされたルーティング

重複するコードを取り除くために、ルーティングからの木構造を構築することも可能です。 子を持つルーティングはそれ自身はマッチしません。それらのネストしたルーティング の実際の終端だけがマッチします。

# /foo     -> undef
# /foo/bar -> {controller => 'foo', action => 'bar'}
my $foo = $r->any('/foo')->to(controller => 'foo');
$foo->get('/bar')->to(action => 'bar');

スタッシュは単にルーティングからルーティングに移動して、新しい値が古い値をオーバーライドします。

# /cats      -> {controller => 'cats', action => 'index'}
# /cats/nyan -> {controller => 'cats', action => 'nyan'}
# /cats/lol  -> {controller => 'cats', action => 'default'}
my $cats = $r->any('/cats')->to(controller => 'cats', action => 'default');
$cats->get('/')->to(action => 'index');
$cats->get('/nyan')->to(action => 'nyan');
$cats->get('/lol');

特別なスタッシュの値

ディスパチャはスタッシュの中のcontrolleractionの値を見るとき、ディスパッチするためにいつでもそれらをクラスとメソッドに変換しようとします。controllerの値はMojo::Utilcamelizeによってキャメライズされ、namespace(デフォルトではアプリケーションクラス)が先頭に追加されます。actionの値はまったく変更されません。このため両方の値は大文字と小文字が区別されます。

# アプリケーション
package MyApp;
use Mojo::Base 'Mojolicious';

sub startup {
  my $self = shift;

  # ルーター
  my $r = $self->routes;

  # /bye -> MyApp::Controller::Foo->bye
  $self->routes->get('/bye')->to(controller => 'foo', action => 'bye');
}

1;

# コントローラー
package MyApp::Controller::Foo;
use Mojo::Base 'Mojolicious::Controller';

# アクション
sub bye {
  my $self = shift;

  # Render response
  $self->render(text => 'Good bye.');
}

1;

コントローラクラスは大きなプロジェクトでコードを体系化するのにぴったりです。より多くのディスパッチの戦略がありますが、コントローラはもっとも一般的に使用されるものなので、「controller#action」という形式の特別なショートカットが用意されています。

# /bye -> {controller => 'foo', action => 'bye', mymessage => 'Bye'}
$r->get('/bye')->to('foo#bye', mymessage => 'Bye');

-はキャメライゼーションの間に、::に置き換えられます。 これは複数レベルのcontrollerの階層を許可しています。

# / -> MyApp::Controller::Foo::Bar->hi
$r->get('/')->to('Foo::Bar#hi');

セキュリティ上の理由で、ディスパッチャーはいつも、 ディスパッチを行う前に controllerMojolicious::ControllerあるいはMojoのサブクラスかどうかをチェックします。

名前空間

ときどきまったく異なる名前空間へとディスパッチしたいかもしれません。

# /bye -> MyApp::MyController::Foo::Bar->bye
$r->get('/bye')
  ->to(namespace => 'MyApp::MyController::Foo::Bar', action => 'bye');

コントローラは可能であれば、いつでも名前空間を追加することができます。

# /bye -> MyApp::MyController::Foo::Bar->bye
$r->get('/bye')->to('foo-bar#bye', namespace => 'MyApp::MyController');

# /hey -> MyApp::MyController::Foo::Bar->hey
$r->get('/hey')->to('Foo::Bar#hey', namespace => 'MyApp::MyController');

すべてのルーティングのためのデフォルトの名前空間を、 Mojolicious::Routesnamespaces属性を使って変更することもできます。

$r->namespaces(['MyApp::MyController']);

コールバックへのルーティング(cb)

コントローラをバイパスして、代わりにコールバックを実行するためにスタッシュのcbを使うことができます。

$r->get('/bye')->to(cb => sub {
  my $c = shift;
  $c->render(text => 'Good bye.');
});

Mojolicious::Liteと同じようにコールバックを直接渡すこともできます。 これは通常はよりよく見えます。

$r->get('/bye' => sub {
  my $c = shift;
  $c->render(text => 'Good bye.');
});

名前つきルーティング

ルーティングに名前をつければフレームワーク全体をとおして、多くの種類のヘルパー関数によって逆参照することができます。 これらのほとんどは、内部的にはMojolicious::Controllerurl_forを頼りにしています。

# /foo/marcus -> {controller => 'foo', action => 'bar', user => 'marcus'}
$r->get('/foo/:user')->to('foo#bar')->name('baz');

# Generate URL "/foo/marcus" for route "baz" を生成
my $url = $c->url_for('baz');

# URL "/foo/jan" for route "baz" を生成
my $url = $c->url_for('baz', user => 'jan');

# URL "http://127.0.0.1:3000/foo/jan" for route "baz" を生成

名前のないルーティングは自動的に生成された名前を割り当てられます。その名前は単純にルーティング名からワード文字(a-zA-Z0-9_)以外を取り除いたものになります。

# /foo/bar ("foobar")
$r->get('/foo/bar')->to('test#stuff');

# URL "/foo/bar"を生成
my $url = $c->url_for('foobar');

現在のルーティングを言及するために、予約語のcurrentを使うか、何も名前を指定しないことができます。

# Generate URL for current route
$self->url_for('current');
$self->url_for;

Mojolicious::Plugin::DefaultHelperscurrent_routeで 現在のルーティング名をチェックあるいは取得することができます。

# 現在のルーティング名
my $name = $c->current_route;

# 複数のルーティングで共有されるコードにおいてルーティング名をチェック
$c->stash(button => 'green') if $c->current_route('login');

オプショナルなプレースホルダー

抽出されたプレースホルダーの値は古いスタッシュの値が存在していれば単に再定義されます。

# /bye -> {controller => 'foo', action => 'bar', mymessage => 'bye'}
# /hey -> {controller => 'foo', action => 'bar', mymessage => 'hey'}
$r->get('/:mymessage')->to('foo#bar', mymessage => 'hi');

さらにもうひとつの興味深い効果として、プレースホルダーは、もし同じ名前のスタッシュの値がすでに存在していれば、自動的にオプショナルになります。正規表現([^/.]+)?に似ています。

# / -> {controller => 'foo', action => 'bar', mymessage => 'hi'}
$r->get('/:mymessage')->to('foo#bar', mymessage => 'hi');

# /test/123     -> {controller => 'foo', action => 'bar', mymessage => 'hi'}
# /test/bye/123 -> {controller => 'foo', action => 'bar', mymessage => 'bye'}
$r->get('/test/:mymessage/123')->to('foo#bar', mymessage => 'hi');

ふたつのオプショナルなプレースホルダーが、スラッシュによって分割されている場合だけ、 スラッシュはオプショナルなものになります。

# /           -> {controller => 'foo',   action => 'bar'}
# /users      -> {controller => 'users', action => 'bar'}
# /users/list -> {controller => 'users', action => 'list'}
$r->get('/:controller/:action')->to('foo#bar');

controlleractionなどの特別なスタッシュの値もまたプレースホルダーとなりえます。これによってきわめて柔軟なルーティングの構築が可能になります。 これは開発の間は特にとても便利ですが、 すべてのコントローラーメソッドがルーティングになる可能性 があるので、注意深く利用すべきです。 アンダースコアで始まるこれらのメソッドと同じように、すべての大文字のメソッドは 自動的にルーターから隠されます。 追加的に隠すためにMojolicious::Routeshideを使うこともできます。

# すべてのコントローラーで「create」メソッドを隠す
$r->hide('create');

Mojolicious::Controllerのすべての属性とメソッドでは、 これはすでに行われています。

より制約的なプレースホルダー

よりプレースホルダーを制約する簡単な方法がもうひとつあります。 可能な値をリストにするだけです。これは正規表現(bender|leela)に似ています。

# /fry    -> undef
# /bender -> {controller => 'foo', action => 'bar', name => 'bender'}
# /leela  -> {controller => 'foo', action => 'bar', name => 'leela'}
$r->get('/:name' => [name => [qw(bender leela)]])->to('foo#bar');

必要があればプレースホルダーの正規表現を調節することができます。 <^>と$を使ったりグループ(...)のキャプチャは行わないでください。 プレースホルダーは内部の大きな正規表現の一部だからです。 けれども(?:...)は大丈夫。

# /23   -> {controller => 'foo', action => 'bar', number => 23}
# /test -> undef
$r->get('/:number' => [number => qr/\d+/])->to('foo#bar');

# /23   -> undef
# /test -> {controller => 'foo', action => 'bar', name => 'test'}
$r->get('/:name' => [name => qr/[a-zA-Z]+/])->to('foo#bar');

このようにして読みやすいルーティングと生の正規表現の力を簡単に得ることができます。

アンダー

Mojolicious::Routes::Routeunderメソッドで作られたブリッジのルーティングは 複数のネストされたルーティングで共有されるコードとして利用されます。 通常のネストされたルーティングとは異なるので、 それらがマッチしたときに追加的なディスパッチサイクルをもたらします。

# /foo     -> undef
# /foo/bar -> {controller => 'foo', action => 'baz'}
#             {controller => 'foo', action => 'bar'}
my $foo = $r->under('/foo')->to('foo#baz');
$foo->get('/bar')->to('#bar');

実際のブリッジのコードは真の値を返す必要があります。そうでなければ、ディスパッチのチェーンは壊れます。このためにブリッジは認証のためのとても強力なツールになっています。

# /blackjack -> {cb => sub {...}}
#               {controller => 'hideout', action => 'blackjack'}
my $auth = $r->under('/' => sub {
  my $c = shift;

  # 認証された
  return 1 if $c->req->headers->header('X-Bender');

  # 認証されなかった
  $c->render(text => "You're not Bender.");
  return undef;
});
$auth->get('/blackjack')->to('hideout#blackjack');

壊れたディスパッチチェーンは Mojolicious::Controllercontinueメソッドを呼び出すことによって、 続けることができます。 これによって、たとえば、ノンブロッキング処理を次のディスパッチサイクルに到達する前に 終了することができます。

my $maybe = $r->under('/maybe' => sub {
  my $c = shift;

  # 3秒まって、50%の確率で継続する
  Mojo::IOLoop->timer(3 => sub {

    # Loser
    return $c->render(text => 'No luck.') unless int rand 2;

    # Winner
    $c->continue;
  });

  return undef;
});
$maybe->get('/')->to('maybe#winner');

フォーマット

ルーティングの末尾にある.html.txtなどのファイルの拡張子は自動的に検知されスタッシュのformatに保存されます。

# /foo      -> {controller => 'foo', action => 'bar'}
# /foo.html -> {controller => 'foo', action => 'bar', format => 'html'}
# /foo.txt  -> {controller => 'foo', action => 'bar', format => 'txt'}
$r->get('/foo')->to('foo#bar');

たとえばこれによって異なるフォーマットの複数のテンプレートで、同じアクションのコードを共有することができます。 制約的なプレースホルダーは許可されたフォーマットを制限するのに利用されます。

# /foo.txt -> undef
$r->get('/foo' => [format => [qw(rss xml)]])->to('foo#bar');

制約的なプレースホルダーもまた、フォーマットの検知に利用することができます。

# /foo.rss -> {controller => 'foo', action => 'bar', format => 'rss'}
# /foo.xml -> {controller => 'foo', action => 'bar', format => 'xml'}
# /foo.txt -> undef
$r->route('/foo', format => [qw(rss xml)])
  ->to(controller => 'foo', action => 'bar');

あるいは、特別な種類の制限プレースホルダーを使って、 フォーマットの検知を無効にすることができます。 これはネストされたルーティングに受け継がれ、 選択的に再度有効にすることができます。

# /foo      -> {controller => 'foo', action => 'bar'}
# /foo.html -> undef
$r->get('/foo' => [format => 0])->to('foo#bar');

# /foo      -> {controller => 'foo', action => 'bar'}
# /foo.html -> undef
# /baz      -> undef
# /baz.txt  -> {controller => 'bar', action => 'baz', format => 'txt'}
# /baz.html -> {controller => 'bar', action => 'baz', format => 'html'}
# /baz.xml  -> undef

WebSocket

Mojolicious::Routes::Routewebsocketメソッドを使ってWebSocketハンドシェイクへアクセスを制限することができます。 これは追加の情報がついた普通のGETリクエストです。

# /echo (WebSocket handshake)
$r->websocket('/echo')->to('foo#echo');

# コントローラー
package MyApp::Controller::Foo;
use Mojo::Base 'Mojolicious::Controller';

# アクション
sub echo {
  my $self = shift;
  $self->on(message => sub {
    my ($self, $message) = @_;
    $self->send("echo: $message");
  });
}

1;

WebSocketハンドシェイクリクエストに、101レスポンスで応答したときに、コネクションは確立されます。 これはMojolicious::Controlleronでイベントを購読したとき、またMojolicious::Controllersendでメッセージを送ったときにも、自動的に行われます。

GET /echo HTTP/1.1
Host: mojolicio.us
User-Agent: Mojolicious (Perl)
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Key: IDM3ODE4NDk2MjA1OTcxOQ==
Sec-WebSocket-Version: 13

HTTP/1.1 101 Switching Protocols
Server: Mojolicious (Perl)
Date: Tue, 03 Feb 2015 17:08:24 GMT
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Accept: SWsp5N2iNxPbHlcOTIw8ERvyVPY=

キャッチオールルート

定義された順番でルートはマッチするので、 オプショナルなワイルドカードプレースホルダーによって、 最後のルートにおいてマッチしない、 すべてのリクエストをキャッチすることができます。

# * /*
$r->any('/*whatever' => {whatever => ''} => sub {
  my $c        = shift;
  my $whatever = $c->param('whatever');
  $c->render(text => "/$whatever did not match.", status => 404);
});

フック

ルーティングシステムの外でフックを実行し、 すべてのリクエストで共有することによって、 Mojoliciousを拡張することができます。 プラグインにとって特に強力なツールになります。 フックはルーティングシステムの外側で実行され、 Mojolicioushookを使って、 すべてのリクエストでコードを共有することによって フレームワーク自体を拡張 することを可能にします。 これはプラグインのためのとても強力なツールです。

# アプリケーション
package MyApp;
use Mojo::Base 'Mojolicious';

sub startup {
  my $self = shift;

  # "/test"プレフィックスを含んでいるすべてのリクエストをチェック
  $self->hook(before_dispatch => sub {
    my $c = shift;
    $c->render(text => 'This request did not reach the router.')
      if $c->req->url->path->contains('/test');
  });

  # 上のフックがレスポンスを描画すれば、これには到達しない。
  my $r = $self->routes;
  $r->get('/welcome')->to('foo#welcome');
  $r->post('/bye')->to('foo#bye');
}

1;

追加のレスポンスヘッダを設定するような後処理の仕事などは一般的な使用方法でしょう。

# 静的ファイルがキャッシュされるようにする
$app->hook(after_static => sub {
  my $c = shift;
  $c->res->headers->cache_control('max-age=3600, must-revalidate');
});

# デフォルトヘッダを削除
$app->hook(after_dispatch => sub {
  my $c = shift;
  $c->res->headers->remove('Server');
});

リクエストのプリプロセッシングにおいて同じことをする。

# リクエストヘッダーに基づいたテンプレートバリアントを選択
$app->hook(before_dispatch => sub {
  my $c = shift;
  return unless my $agent = $c->req->headers->user_agent;
  $c->stash(variant => 'ie') if $agent =~ /Internet Explorer/;
});

モニタリングをアプリケーションに加える発展的な拡張

# Webサービスに例外を通知する
$self->hook(after_dispatch => sub {
  my $c = shift;
  return unless my $e = $c->stash('exception');
  $c->ua->post('https://example.com/bugs' => form => {exception => $e});
});

コアの機能の多くを拡張することも可能です。

# コントローラーオブジェクトをアクションの中で$_で利用できるようにする
$app->hook(around_action => sub {
  my ($next, $c, $action, $last) = @_;
  local $_ = $c;
  return $next->();
});

# ルーティング名をアクションへの引数として渡す
$app->hook(around_action => sub {
  my ($next, $c, $action, $last) = @_;
  return $c->$action($c->current_route);
});

フックのすべてのリストについはMojolicioushookをご覧ください。

内観

すべての利用可能なルーティングとその実体である正規表現を列挙するために、コマンドラインからMojolicious::Command::routesのコマンドが利用できます。

$ ./myapp.pl routes -v
/foo/:name  ....  POST  fooname  ^/foo/([^/\.]+))(?:\.([^/]+)$)?
/bar        B...  *     bar      ^/bar
  +/baz     ...W  GET   baz      ^/baz(?:\.([^/]+)$)?
/yada       ....  *     yada     ^/yada(?:\.([^/]+)$)?

発展

一般的ではないが、より強力な機能

ショートカット

ルーティングの生成をより表現豊かにするために、 Mojolicious::Routesadd_shortcutで ショートカットを追加することもできます。

# 簡単な"resource"ショートカット
$r->add_shortcut(resource => sub {
  my ($r, $name) = @_;

  # リソースのプレフィックス
  my $resource = $r->any("/$name")->to("$name#");

  # リソースのリストの描画
  $resource->get->to('#index')->name($name);

  # 新しいリソースを作成するために、フォームを描画する("store"によってサブミット)
  $resource->get('/create')->to('#create')->name("create_$name");

  # 新しく作成されたリソースを保存する ("create"によってサブミット)
  $resource->post->to('#store')->name("store_$name");

  # 特別なリソースを描画する
  $resource->get('/:id')->to('#show')->name("show_$name");

  # リソースを編集するためにフォームを描画する ("update"へのサブミット)
  $resource->get('/:id/edit')->to('#edit')->name("edit_$name");

  # リソースをアップデートする("edit"によるサブミット)
  $resource->put('/:id')->to('#update')->name("update_$name");

  # リソースを削除する
  $resource->delete('/:id')->to('#remove')->name("remove_$name");

  return $resource;
});

# GET /users         -> {controller => 'users', action => 'index'}
# GET /users/create  -> {controller => 'users', action => 'create'}
# POST /users        -> {controller => 'users', action => 'store'}
# GET /users/23      -> {controller => 'users', action => 'show', id => 23}
# GET /users/23/edit -> {controller => 'users', action => 'edit', id => 23}
# PUT /users/23      -> {controller => 'users', action => 'update', id => 23}
# DELETE /users/23   -> {controller => 'users', action => 'remove', id => 23}
$r->resource('users');

ショートカットはルーティングでも、ブリッジでも、さらに両方でも、なんにでもなります。 危険な情報にならないように気をつけてください。

ルーティングのリアレンジ

最初のリクエストが処理されるまでは、すべてのルーティングは、 Mojolicious::Routes::Routeadd_childMojolicious::Routes::Routeremoveなどによって 移動させたり、削除することができます。 特にプラグインによって生成されたリアレンジされたルーティングにとって、 これはとても便利でしょう。

# GET /example/show -> {controller => 'example', action => 'show'}
my $show = $r->get('/show')->to('example#show');
$r->any('/example')->add_child($show);

# Nothing
$r->get('/secrets/show')->to('secrets#show')->name('show_secrets');
$r->find('show_secrets')->remove;

名前によってルーティングを探すために、 Mojolicious::Routes::Routefindを使うことができます。

条件

時々もう少しの力が必要になるかもしれません。たとえば複数のルーティングでUser-Agentヘッダをチェックしたりする場合です。こういうときは条件の出番です。これは基本的にルータのプラグインで、ルーティングがマッチしたときは、 真を返す必要があります。

# 簡単な"User-Agent"の条件
$r->add_condition(
  agent => sub {
    my ($r, $c, $captures, $pattern) = @_;

    # ユーザーが満たす正規表現
    return unless $pattern && ref $pattern eq 'Regexp';

    # "User-Agent"ヘッダーがマッチすれば、成功して真を返す
    my $agent = $c->req->headers->user_agent;
    return 1 if $agent && $agent =~ $pattern;

    # 失敗
    return;
  }
);

# /firefox_only (Firefox) -> {controller => 'foo', action => 'bar'}
$r->route('/firefox_only')->over(agent => qr/Firefox/)
  ->to(controller => 'foo', action => 'bar');

Mojolicious::Routes::Routeadd_conditionメソッドはルータに新しい条件を登録します。 一方Mojolicious::Routes::Routeoverは実際にそれをルーティングに適用します。

条件プラグイン

再利用可能なプラグインとして条件をパッケージ化できます。

# プラグイン
package Mojolicious::Plugin::WerewolfCondition;
use Mojo::Base 'Mojolicious::Plugin';

use Astro::MoonPhase;

sub register {
  my ($self, $app) = @_;

  # "werewolf"条件を追加
  $app->routes->add_condition(
    werewolf => sub {
      my ($r, $c, $captures, $days) = @_;

      # Keep the werewolfs out!
      return if abs(14 - (phase(time))[2]) > ($days / 2);

      # It's ok, no werewolf
      return 1;
    }
  );
}

1;

プラグインをロードして、すべてのアプリケーションでコンディションを使うことができます。

# アプリケーション
package MyApp;
use Mojo::Base 'Mojolicious';

sub startup {
  my $self = shift;

  # Plugin
  $self->plugin('WerewolfCondition');

  # Routes
  my $r = $self->routes;

  # /hideout (keep them out for 4 days after full moon)
  $r->route('/hideout')->over(werewolf => 4)
    ->to(controller => 'foo', action => 'bar');
}

1;

マウントアプリケーション

完全な自分を含んだアプリケーションをあるドメインあるいはプレフィックス(もしくは両方)の下にマウントするのに、 Mojolicious::Plugin::Mountを利用することができます。

use Mojolicious::Lite;

# 「/prefix」の下に完全なアプリケーションをマウント
plugin Mount => {'/prefix' => '/home/sri/myapp.pl'};

# サブドメインでアプリケーションをマウント
plugin Mount => {'test.example.com' => '/home/sri/myapp2.pl'};

# 普通のルート
get '/' => sub { shift->render_text('Hello World!') };

app->start;

埋め込みアプリケーション

コントローラの代わりに、アプリケーション全体を簡単に埋め込むことができます。これによって、たとえばMojoliciousのコントローラの中で、Mojolicious::Liteのドメイン固有言語を使用することができます。

# コントローラー
package MyApp::Controller::Bar;
use Mojolicious::Lite;

# /hello
get '/hello' => sub {
  my $c    = shift;
  my $name = $c->param('name');
  $c->render(text => "Hello $name.");
};

1;

Mojolicious::Routes::RoutetoととてもよくにているMojolicious::Routes::Routedetourを使えば、 埋め込みのアプリケーションの中で、部分的にマッチするルーティングと残りのパスを使用する記述を行うことができます。

# /foo/*
$r->any('/foo')->detour('bar#', name => 'Mojo');

最小の埋め込み可能なアプリケーションは、MojoのサブクラスでMojolicious::Controllerオブジェクトを受け入れるためのhandlerメソッドだけを持つクラスです。

package MyApp::Controller::Bar;
use Mojo::Base 'Mojo';

sub handler {
  my ($self, $c) = @_;
  $c->res->code(200);
  my $name = $c->param('name');
  $c->res->body("Hello $name.");
}

1;

アプリケーションプラグイン

Mojoliciousアプリケーションを埋め込むのは簡単ですが、自身を含めた再利用可能なプラグインとしてすべてをパッケージ化すればより簡単です。

# プラグイン
package Mojolicious::Plugin::MyEmbeddedApp;
use Mojo::Base 'Mojolicious::Plugin';

sub register {
  my ($self, $app) = @_;

  # ルートを自動的に追加
  $app->routes->any('/foo')->detour(app => EmbeddedApp::app());
}

package EmbeddedApp;
use Mojolicious::Lite;

get '/bar' => 'bar';

1;
__DATA__
@@ bar.html.ep
Hello World!

スタッシュのappの値はすでにインスタンス化されたアプリケーションのために利用されます。単にプラグインをロードするだけです。

# アプリケーション
package MyApp;
use Mojo::Base 'Mojolicious';

sub startup {
  my $self = shift;

  # Plugin
  $self->plugin('MyEmbeddedApp');
}

1;

より学ぶには

Mojolicious::Guidesを学び続けてください。またMojolicious wikiを見ることもできます。wikiには多くのユーザーによる多くのドキュメントやサンプルがあります。

サポート

ドキュメントに答えが見つからなくって質問があるときは、 メーリングリストか 公式のIRCチャンネルであるirc.perl.orgの#mojoでためらわずに質問してください。

(Mojolicious 6.05を反映)

Clone this wiki locally
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.