TABLE OF CONTENTS

文档

Mojolicious::Guides::Routing - Routing

综述

本文件包含一个简单而有趣的介绍 Mojolicious 路径选择器 ( Router ) 和它的基本概念.

本文更新到 Mojolicious 6.05 版本

概念

基本每个 Mojolicious 的开发者都知道. Mojolicious 的路径选择器用于识别网址, 分配给对应的 Controller 做动作的处理.

调度 (Dispatche)

每一个 Web 框架的基础都是一个小的黑盒子, 对连接所传入的请求, 转换到相应的代码来生成适当的响应内容.

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

这个黑盒子通常被称为调度 ( dispatcher ). 有不同的策略来对应建立这些关联, 但几乎都是基于映射请求的路径到某个子函数 ( 响应生成器 ).

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

虽然有可能所有的请求都是静态文件, 这时就会变得非常的低效. 所以大多的调度过程使用的都是正则的来动态指向资源的原因.

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

现代的调度器变得比以前更加复杂, 因为不但要处理 HTTP 请求的路径, 还有请求方法中 header 中一些参数会影响调度, 如 Host, User-AgentAccept.

GET /user/show/23 HTTP/1.1
Host: mojolicio.us
User-Agent: Mozilla/5.0 (compatible; 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});

这时路径选择器开始发挥作用, 它低层设计可以取路径上面的的占位符.

/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}

可逆性

还有一个巨大的优势就是当 Routes 使用正则表达式时, 他们很容易可逆, 提取的占位符的内容可以在任何时候都变回路径.

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

每个占位符, 都有一个 name, 即使它只是一个空字符串.

标准占位符

标准占位符是最简单的占位符的应用形式, 他们使用冒号做为前缀来匹配除了 /. 之外的所有字符, 类似于使用正则表达式 ([^/.]+) 的效果.

/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'}

这个占位符可以匹配文件文件的扩展名. 不用使用 format detection.

/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 的通用特性, 每个开发者都需要知道.

迷你 route

每个 Mojolicious 应用程序中有一个路径选择的对象, 你可以用它来生成路由结构.

# Application
package MyApp;
use Mojo::Base 'Mojolicious';

sub startup {
  my $self = shift;

  # Router 对象
  my $r = $self->routes;

  # Route 开始做路径的选择
  $r->get('/welcome')->to(controller => 'r');foo', action => 'welcome');
}

1;

这个小的静态路径选择器会加载并实例化类 MyApp::Controller::Foo 和调用 welcome 的方法.

# Controller
package MyApp::Controller::Foo;
use Mojo::Base 'Mojolicious::Controller';

# Action
sub welcome {
  my $c = shift;

  # Render response
  $c->render(text => 'Hello there.');
}

1;

路径选择器通常是配置在你应用的类中 startup 的方法中 ( '全功能的 Mojolicious 时' ). 但路径选择器可以从任何地方访问.

Routing 目标

在你使用 "get" in Mojolicious::Routes 中的方法开始创建一个新的路径选择, 你可以给一个目标参数关联到路径选择器上, 可以是一个 "to" in Mojolicious::Routes::Route 的链方法指向一个哈希.

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

现在如果进入的请求的路径匹配到了, 就会调用这个哈希中的目标尝试这些使用指定的 controller 和 action 的代码来生成响应.

HTTP 方法

最常用的 HTTP 的请求有个短的方法 "post" in Mojolicious::Routes::Route, 想有更多的控制可以看 "any" in Mojolicious::Routes::Route, 它的第一个参数用来接收一个数组引用, 数组中可以包含可以使用的方法.

# 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 是指透明方式处理, 这意味着路径是保证被转义和解码是使用的 bytes 的字符.

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

Stash

在路径选择器上生成的哈希, 在整个 Mojolicious 请求的周期都有效, 我们可以通过调用 stash 方法来访问到它, 直到返回了响应给客户端后才会消失.

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

在这个中 stash 中的键值对有几个特别的, 不能自定义. 比如 controlleraction, 但在普通情况下, 你可以写任何的数据来给传给生成的目标响应的函数. 这个 stash 的内容可以在调度过程的任何时候被修改.

sub bye {
  my $c = shift;

  # Get message from stash
  my $msg = $c->stash('mymessage');

  # Change message in stash
  $c->stash(mymessage => 'Welcome');
}

要看看本次请求的全部的保留值的列表可以调用 "stash" in Mojolicious::Controller.

嵌套路径选择

使用嵌套可以为路径选择器来构建树的结构, 给相同的功能放在一起以消除重复的代码, 象下面第一层为 $foo 后, 接下来直接接着调用就好了.

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

如果有 stash 的信息会从路径选择中继承来新的路径选择器中覆盖掉旧的

# /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');

特别的 stash 值

当调度中见到了 controlleraction 的值在 stash 中时, 总是尝试着指向到相应指定的类和方法. 对于 controller 关键字所给定的值会被 "camelize" in Mojo::Util 处理来提供名字空间 namespace 的前缀, 默认的控制器的名字空间是基于于应用类 (MyApp::Controller) 以及赤裸的应用类 (MyApp), 会通过这些命名空间来顺序查找. 但 action 关键字所给的值并不改变, 是区分大小写的, 相当于调用指定的方法或者函数.

# Application
package MyApp;
use Mojo::Base 'Mojolicious';

sub startup {
  my $self = shift;

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

1;

# Controller
package MyApp::Controller::Foo;
use Mojo::Base 'Mojolicious::Controller';

# Action
sub bye {
  my $c = shift;

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

1;

这个 Controller 的类, 用于在大型项目中组织代码非常完美. 我们来看看更多的调度策略的用法, 因为控制器这个非常常用, 所以可以使用这个特别的快捷方式, 就是 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');

你也可以调整指定 controller 使用 CamelCase 来替换 snake_case.

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

如果 controller 是一个 Mojolicious::ControllerMojo 的子类, 在调度之前, 为了安全调度器总是会检查上面这个.

命字空间

你可以使用 stash 中的 namespace 的值来改变整个整体和所有的路径选择器的命名的空间.

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

这个新的名字空间会加到 controller 的上面.

# /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');

你也可以修改你的应用中全部的路径选择中默认的命名空间. 只需要使用 "namespace" in Mojolicious::Routes.

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

Route 到指定的回调

路径选择上的在 stash 哈希可以使用一个 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.');
});

名路由 (Named routes)

给你的路径取一个名字, 可以让你逆向引用, 在整个框架中的许多方法和 helper 可以通过 "url_for" in Mojolicious::Controller 来使用它.

# /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');

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

# Generate URL "http://127.0.0.1:3000/foo/jan" for route "baz"
my $url = $c->url_for('baz', user => 'jan')->to_abs;

没有名字的路径会自动分配一个名字, 就是等于本身的没有非单词字母的字符连接串.

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

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

指到当前的路径, 可以不加名字, 也可以加一个名为 current 的名字.

# Generate URL for current route
my $url = $c->url_for('current');
my $url = $c->url_for;

你需要检查当前的路径名字, 可以使用 helper 方法中的 "current_route" in Mojolicious::Plugin::DefaultHelpers.

# Name for current route
my $name = $c->current_route;

# Check route name in code shared by multiple routes
$c->stash(button => 'green') if $c->current_route('login');

可选占位符

提取的占位符的值, 如果你象下面一样, 它会简单地重新定义 stash 的值.

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

一个有意思的效果, 就是当占位符结束路径选择时已经存在相同的名字的 stash 的值时, 会自动的变成可选的, 存在就不使用, 不存在就使用, 就象正则的 ([^/.]+)?.

# / -> {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');

所以指定 stash 的值象 controlleraction 这二个关键字也可以使用占位符. 这非常的方便, 尤其是在开发的时候, 但我们要非常小心, 因为每一个 Controller 的方法都会成为潜在的 route. 所有大写的方法, 和使用下划线的方法都会被路径选择器自动隐藏起来, 你也可以在 Mojolicious::Routes 中使用 "hide" 的方法来增加这个.

# Hide "create" method in all controllers $r->hide('create');

This has already been done for all attributes and methods from 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');

这样你直接写易于阅读的路径选择器和直接使用原生的正则表达式.

Under

多个嵌套的路由, 你可以使用 "under" in Mojolicious::Routes::Route 来共享代码, 不同于正常的嵌套路由的地方是 这个路由会在当他们匹配时会有额外的调度周期.

# /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;

  # Authenticated
  return 1 if $c->req->headers->header('X-Bender');

  # Not authenticated
  $c->render(text => "You're not Bender.");
  return undef;
});
$auth->get('/blackjack')->to('hideout#blackjack');

中断的调度链可以通过调用 "continue" in Mojolicious::Controller 方法继续. 这个例子中使用的是非阻塞的操作, 可以直到下一个调度周期之前.

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');

每一个路由匹配到的目的地址都有一个快照在一定的时间存储在 stash 中. 只有 format 值会被所有的共享. 这对于你 "match" in Mojolicious::Controller 用于自省自己的处理目的之前和之后非常有用.

# Action of the fourth dispatch cycle
my $action = $c->match->stack->[3]{action};

格式 (Formats)

对于请求过来的路径中象普通的文件扩展名 .html.txt 之类在路径选择结束时会自动的存到 stash 的值 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(controller => 'foo', action => 'bar');

这允许多个模板, 使用不同的格式, 但使用相同的代码来处理不同的模板. 限制性的占位符也可以被用来限制允许的格式.

# /foo.txt -> undef
# /foo.rss -> {controller => 'foo', action => 'bar', format => 'rss'}
# /foo.xml -> {controller => 'foo', action => 'bar', format => 'xml'}
$r->get('/foo' => [format => [qw(rss xml)]])->to('foo#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 => 'baz', action => 'yada', format => 'txt'}
# /baz.html -> {controller => 'baz', action => 'yada', format => 'html'}
# /baz.xml  -> undef
my $inactive = $r->under([format => 0]);
$inactive->get('/foo')->to('foo#bar');
$inactive->get('/baz' => [format => [qw(txt html)]])->to('baz#yada');

WebSockets

"websocket" in Mojolicious::Routes::Route 的方法可以限定握手, 这是标准的 GET 请求加一些额外的信息.

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

# Controller
package MyApp::Controller::Foo;
use Mojo::Base 'Mojolicious::Controller';

# Action
sub echo {
  my $self = shift;
  $self->on(message => sub {
    my ($self, $msg) = @_;
    $self->send("echo: $msg");
  });
}

1;

当连接上时 WebSocket 的握手请求会发送一个 101 的响应状态, 如果你定阅了 "on" in Mojolicious::Controller 或者发送 "send" in Mojolicious::Controller 的时候会自动的产生.

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=

捕捉全部路由 (Catch-all route)

由于路径匹配是根据定义的顺序的, 你可以通过可选通配符占位符在最后面, 来捕捉所有请求中不匹配的路由.

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

条件 (Conditions)

条件是根据 headers, agent 或者 host 来通过 Mojolicious::Plugin::HeaderCondition 来应用到 "over" in Mojolicious::Routes::Route 的方法上, 这可以实现非常强大的路由限制.

# / (Origin: http://mojolicio.us)
$r->get('/')->over(headers => {Origin => qr/mojolicio\.us/})->to('foo#bar');

# / (Firefox)
$r->get('/')->over(agent => qr/Firefox/)->to('browser-test#firefox');

# / (Internet Explorer)
$r->get('/')->over(agent => qr/Internet Explorer/)->to('browser-test#ie');

# http://mojolicio.us/perldoc
$r->get('/perldoc')->over(host => 'mojolicio.us')->to('perldoc#index');

要知道, 条件的路由缓存太复杂, 通常我们只加快重复请求, 因此这会降低性能.

Hooks

"hook" in Mojolicious 操作是主要用来扩展和共享你的代码, 使它更为强大, 让你可以方便的扩展 Mojolicious.

# Application
package MyApp;
use Mojo::Base 'Mojolicious';

sub startup {
  my $c = shift;

  # 对所有 "/test" 开头的请求进行检查
  $c->hook(before_dispatch => sub {
    my $c = shift;
    $c->render(text => 'This request did not reach the router.')
      if $c->req->url->path->contains('/test');
  });

  # 如果上面的 hook 响应了, 请求就不会到达这
  my $r = $c->routes;
  $r->get('/welcome')->to('foo#welcome');
  $r->post('/bye')->to('foo#bye');
}

1;

after_static 是工作在静态文件处理之后, 这个用于设置附加响应的头, 正常工作中这是一个非常普遍的使用.

# 让这个静态文件可以 Cache.
$c->hook(after_static => sub {
  my $c = shift;
  $c->res->headers->cache_control('max-age=3600, must-revalidate');
});

# 删除默认的 Header 
$app->hook(after_dispatch => sub {
  my $c = shift;
  $c->res->headers->remove('Server');
});

before_dispatch 是工作在请求进入调度分配之前, 预处理请求.

# 基于请求的 header 头修改传给模板的 variant 的值
$c->hook(before_dispatch => sub {
  my $c = shift;
  return unless my $agent = $c->req->headers->user_agent;
  $c->stash(variant => 'ie') if $agent =~ /Internet Explorer/;
});

after_dispatch 是工作在请求离开调度之后, 例子是使用更先进的扩展来添加监视你的应用程序

# 转发异常到你的 Web 服务
$c->hook(after_dispatch => sub {
  my $c = shift;
  return unless my $e = $c->stash('exception');
  $c->ua->post_form('https://kraih.com/bugs' => {exception => $e});
});

你也可以扩展核心的基础的功能.

# Make controller object available to actions as $_
$app->hook(around_action => sub {
  my ($next, $c, $action, $last) = @_;
  local $_ = $c;
  return $next->();
});

# Pass route name as argument to actions
$app->hook(around_action => sub {
  my ($next, $c, $action, $last) = @_;
  return $c->$action($c->current_route);
});

有关 hooks 的完整列表可以查看 "hook" in Mojolicious.

自省

Mojolicious::Command::routes 中可以使用命令行来对你的路径选择器进行自省, 你可以使用下面的方法来查看所有的路径选择的名字和相关的正则表达式来查看自己的设置是否对.

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

高级的

比较不常用的功能, 但非常强大的功能.

捷径

您还可以使用 "add_shortcut" in Mojolicious::Routes 添加自己的快捷方式, 使路径选择更富有表现.

# Simple "resource" shortcut
$r->add_shortcut(resource => sub {
  my ($r, $name) = @_;

  # Prefix for resource
  my $resource = $r->any("/$name")->to("$name#");

  # Render a list of resources
  $resource->get->to('#index')->name($name);

  # Render a form to create a new resource (submitted to "store")
  $resource->get('/create')->to('#create')->name("create_$name");

  # Store newly created resource (submitted by "create")
  $resource->post->to('#store')->name("store_$name");

  # Render a specific resource
  $resource->get('/:id')->to('#show')->name("show_$name");

  # Render a form to edit a resource (submitted to "update")
  $resource->get('/:id/edit')->to('#edit')->name("edit_$name");

  # Store updated resource (submitted by "edit")
  $resource->put('/:id')->to('#update')->name("update_$name");

  # Remove a resource
  $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');

重排 routes

就算第一个请求已经开始处理了, 全部的路径选择还是可以移动和删除的, 特别是通过插件需要重新排列的时候, 这个有时非常有用.

# 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;

你想看 find 路径选择的其它, 可以 "find" in Mojolicious::Routes::Route

添加条件

你可以通过 "add_condition" in Mojolicious::Routes 加入自己的方法, 使它变得更加强大的能力. 所有的基本路径插件会在每个请求到来时执行, 它需要返回真假来确认.

# A condition that randomly allows a route to match
$r->add_condition(
  random => sub {
    my ($route, $c, $captures, $num) = @_;

    # Loser
    return undef unless int rand $num;

    # Winner
    return undef;
  }
);

# /maybe (25% chance)
$r->get('/maybe')->over(random => 4)->to('foo#bar');

条件 plugins

你可以根据你的条件加复用你的插件.

# Plugin
package Mojolicious::Plugin::WerewolfCondition;
use Mojo::Base 'Mojolicious::Plugin';

use Astro::MoonPhase;

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

  # Add "werewolf" condition
  $app->routes->add_condition( werewolf => sub {
      my ($route, $c, $captures, $days) = @_;

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

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

1;

如果你的应用中使用条件插件, 你只需要加载就可以直接使用了.

# Application
package MyApp;
use Mojo::Base 'Mojolicious';

sub startup {
  my $self = shift;

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

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

1;

Mount applications

你可以非常容易的使用 Mojolicious::Plugin::Mount 加载其它人的应用到你的应用中, 你只需要提供一个安全可用的 undef 的前缀或者子域.

use Mojolicious::Lite;

# Whole application mounted under "/prefix"
plugin Mount => {'/prefix' => '/home/sri/myapp.pl'};

# Mount application with subdomain
plugin Mount => {'test.example.com' => '/home/sri/myapp2.pl'};

# Normal route
get '/' => sub { shift->render(text => 'Hello World!') };

app->start;

嵌入应用

您可以方便地给程序嵌入整个应用程序, 如果你只想简单地使用他们, 而想使用控制器. 这允许例如使用的 Mojolicious::Lite 域的 DSL 语言来替换 Mojolicious 的控制器的行.

# Controller
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;

你可以使用 "detour" in Mojolicious::Routes::Route 它是模拟 "to" in Mojolicious::Routes::Route, 你可以使用部分匹配用在这种应用程序的其它路径, 将前面的基本路径放到 path 的 stash 值中.

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

一个最简单的应用无非就是 Mojo 的子类, 从 Mojolicious::Controller 的对象中包含 handler 的方法.

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

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

1;

您也可以只使用 Mojolicious::Plugin::Mount 来根据前缀和域名加载独立的应用.

use Mojolicious::Lite;

# Whole application mounted under "/prefix"
plugin Mount => {'/prefix' => '/home/sri/myapp.pl'};

# Mount application with subdomain
plugin Mount => {'test.example.com' => '/home/sri/myapp2.pl'};

# Normal route
get '/' => sub { shift->render(text => 'Hello World!') };

app->start;

应用的 plugins

增强 Mojolicious 应用程序是很容易的, 只要作为一个自包含的可重复使用的插件.

# Plugin
package Mojolicious::Plugin::MyEmbeddedApp;
use Mojo::Base 'Mojolicious::Plugin';

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

  # Automatically add route
  $app->routes->any('/foo')->detour(app => EmbeddedApp::app());
}

package EmbeddedApp;
use Mojolicious::Lite;

get '/bar' => 'bar';

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

这个 app 中的 stash 的值, 并不会继承到嵌套的路径中去.所以你可以用于实例化的应用程序中, 你只需要加载插件就好了.

# Application
package MyApp;
use Mojo::Base 'Mojolicious';

sub startup {
  my $self = shift;

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

1;

更多

你可以看看 Mojolicious::Guides 有更多的 Mojolicious wiki, 它包含大量的文档和例子.

SUPPORT

If you have any questions the documentation might not yet answer, don't hesitate to ask on the mailing-list or the official IRC channel #mojo on irc.perl.org.