第三章 掌握Controller 第二节 action详解
时间:2007-04-11 来源:xiaoshengcaicai
- path类型 (包含 Local, Path, Global)
- regex类型 (包含 LocalRegex, Regex)
- 自定义的private属性的action
- Catalyst内置的private属性的action (这些action包括auto, begin, end, index, default)
- 其他(暂时不在本教程讨论范围内,比如新版本的Catalyst里面的Chained属性)
1. action所处的Controller的namespace
2. action本身(包括属性的定义,也可能包括action的名字)
1. path类型 (包含 Local, Path, Global)
先看下面的例子:
package X::Controller::A::B; sub list : Local { } sub g_list : Global { } sub p_list : path('blist') { } sub p2_list : path('/alist') { } sub p3_list : path('blist/clist') { } 1; |
在上面的例子里面,Controller的namespace = 'a/b'.
Local属性的action是最为常见的,例子里面的list能匹配的URL为:
'a/b' + '/' + 'list' = 'a/b/list'
Global属性的action跟Local属性的类似,只是它无视所在Controller的namespace而已,
例子里面的g_list可以匹配的URL为:
'/' + 'g_list' = '/g_list'
所谓path属性, 是Local属性跟Global属性的结合而已,
当path里面的参数第一个字符是 / 时,表示它是一个Global类型的, 否则,它就是一个Local类型的.
Path属性的action所能匹配的URL跟action的名字无关,而是跟path里面的参数有关.
例子里面:
p_list这个action其实 等同于: sub blist : Local , 所以它能匹配的URL为:
'a/b' + '/' + 'blist' = 'a/b/blist'
p2_list这个action其实 等同于: sub alist : Global, 所以它能匹配的URL为:
'/' + 'alist' = '/alist'
p3_list这个action能匹配的URL为:
'a/b' + '/' + 'blist/clist' = 'a/b/blist/clist'
这时候可能你有疑问, 如果我们定义了多个path类型的action, 这些action都可以映射同一个URL,那么究竟最后会调用哪个action呢?
首先,在实际开发的时候, 一定要尽量避免这种情况的发生, 出现这种情况的话,可以说就是BUG.
因为在这种情况下, 只有最后定义的那个path类型的action可以映射URL, 其他action都白定义了.看这个例子:
package X::Controller::A::B; sub list : Local { } sub p_list : path('list') { } sub p2_list : path('list') { } 1; |
这3个action都可以映射URL: a/b/list, 实际上只有最后一个action: p2_list可以实际映射, 前面定义的2个action(list, p_list)会被无情的抛弃.
注意:
path类型的action, 比如上面例子里面的list : Local, 我们说它可以匹配 'a/b/list', 并不是说它只能匹配a/b/list, 它是有可能会匹配到a/b/list/xiao/sheng这样的url的。 等所有类型的action都讲完后,我会把如何根据一个URL找到一个action来跟它匹配剖析给大家看。 |
2. regex类型 (包含 LocalRegex, Regex)
LocalRegex('...') 类似于 Path('..'),只是 LocalRegex更为灵活, 它的参数可以是一个正则表达式.
Regex('...') 类似于 Path('/..'),只是 Regex更为灵活, 它的参数可以是一个正则表达式.
比如
package X::Controller::A::B; sub a : Path('list'){ } sub b : LocalRegex('^list') { } sub a2 : Path('/list2') { } sub b2 : Regex('^list2') { } sub x : Regex('xiao') { } 1; |
a这个action它可以匹配的URL为:
a/b/list
b这个action它可以匹配的URL为:
a/b/^list
也就是 a/b/list*,即URL中以a/b/list开头
a2这个action它可以匹配的URL为:
list
b2这个action它可以匹配的URL为:
list2
也就是 list2* , 即URL中以list2开头
x这个action它可以匹配的URL为:
xiao
也就是 *xiao* , 即URL中含有xiao字符串
需要注意, 在为一个URL寻找一个action来进行分发时, path类型的action的优先级高于regex类型的action.
关于regex属性的action,你可以从$c->req->captures 里面得到正则表达式里面匹配到的$1,$2..比如 Regex('^list(\d+)') 可以匹配 list5这样的url,匹配到的5 保存在$c->req->captures->[0]里面。3. 自定义的private属性的action
private属性的action不能直接映射为URL, 只能用于action内部的调用.
一般来说,为了处理客户端请求, 编写path类型或者regex类型的action已经足够了, 但是在某些情况下,编写private 属性的action, 可以实现代码重用,见下面的例子:
package X::Controller::A::B; sub c1 : Local{ my ($self, $c) = @_; $c->forward('init_data'); #continue $c->res->body("continute"); } sub c2 : Local { my ($self, $c) = @_; $c->detach('init_data'); #not return $c->res->body("last"); } sub init_data : Private { my ($self, $c) = @_; #init data code } 1; |
c1, c2是2个可以对外映射URL的action, 而init_data则不可以,
但是c1, c2内部可以通过forward或者detach来调用到init_data,
forward就相当于函数调用,(只不过这个函数调用外部包了一个eval,所以action内部就算die掉也只是抛出一个异常, 不会导致整个系统的崩溃), c1内部执行完init_data这个action后,会继续返回c1继续执行c1下面的代码,
detach跟forward唯一的区别就是detach执行完不再返回, c2执行完detach之后不返回, 也就是说
$c->res->body("last"); 这行代码不会被执行到.
private 属性的action, 我认为它的最大优点在于它可以跨组件调用, 也就是Controller A可以调用Controller B里面的一个private属性的action.这跟我们以前写的cgi不一样, cgi里面是不可以直接调用另外一个cgi文件里面定义的函数的.
从这点上, Catalyst提倡的 donot repeat yourself 可见一斑.
这种跨组件调用可以见下面例子
package X::Controller::A; sub init_data : Private { my ($self, $c) = @_; #init data code } 1; package X::Controller::B; sub c1 : Local{ my ($self, $c) = @_; $c->forward('/a/init_data'); #continue $c->res->body("continute"); } 1; |
关于forward跟detach,你会在后面章节看到他们更详细的说明.
4. Catalyst内置的private属性的action (这些action包括auto, begin, end, index, default)
先来谈谈 begin 跟end.
假设我们现在有这样的Controller结构:
X::Controller::Root; ( namespace 为'')
X::Controller::A; ( namespace 为默认的'a')
X::Controller::B; ( namespace 为默认的'b')
X::Controller::A:A2; ( namespace 为默认的'a/a2')
X::Controller::A:A2::A3; ( namespace 为默认的'a/a2/a3').
假设现在客户端请求的URL为 a/a2/a3/list
刚好X::Controller::A:A2::A3里面定义了一个名为list,属性为Local的action,
那么Catalyst就会把URL分发给X::Controller::A:A2::A3::list这个action进行处理,
在list这个action开始执行之前, Catalyst会检查有没有在a/a2/a3这个namespace下定义过
名 字为begin, 属性为private的action,如果定义过,那么先执行a/a2/a3/begin这个action,之后再执行list这个action,如果没 有定义过, 那么会沿着a/a2/a3这个命名空间往上一层去找, 也就是检查有没有定义过a/a2/begin这个action,如果有则执行, 然后再执行list,如果没有则继续往上找,直到找到或者namespace为空也没找到.
也就是说它按照下面的顺序来查找有没有定义过begin, 有则先执行begin再执行list:
a/a2/a3/begin
a/a2/begin
a/begin
begin
用一句话来概括就是:
根据URL找到可以匹配的action后, 在执行该action之前,会在该action所处的命名空间的每个层次上,从下往上寻找一个名为begin,属性为private的action,找到则执行,执行完不再往上找, 接下来执行匹配的action.
在上面例子里面, 如果同时定义了a/a2/a3/begin跟a/begin, 在执行a/a2/a3/begin之后, 接下来就去执行list了, 不会继续往上找到a/begin来执行.
在执行完list这个action之后, Catalyst会去寻找和执行一个名为end,属性为private的action, 它寻找的顺序是这样的:
a/a2/a3/end
a/a2/end
a/end
end
看得出来寻找end跟寻找begin是一样的,只是一个是在执行list之后,一个是在执行list之前。对于end, 用一句话概括就是:
根据URL找到可以匹配的action后, 在执行该action之后,会在该action所处的命名空间的每个层次上,从下往上寻找一个名为end,属性为private的action,找到则执行,执行完不再往上找.
实际上, 在上面的例子中, 当执行完begin之后,在执行list之前, catalyst还会去寻找一种名为auto,属性为private的action,在上面的例子中,它寻找的auto的顺序是:
auto
a/auto
a/a2/auto
a/a2/a3/auto
当寻找一个auto之后,catalyst会执行它,然后判断auto这个action的返回值, 如果auto返回真值,那么继续往下寻找auto, 继续执行,直到所有的auto都执行完毕且返回真值后, 才会去执行list 这个action。如果当中有某个auto返回了假值, 那么就不再往下寻找auto,也不会再去执行list,就会直接跳到寻找end的过程。
用一句话来概括的话就是:
根 据URL找到可以匹配的action后, 在执行begin之后,在执行该action之前,会在该action所处的命名空间的每个层次上,从上到下寻找一个名为auto,属性为private 的action,找到则执行,如果auto返回真值就继续往下寻找执行auto,当所有的auto都返回真值时,接下来才会去执行list,否则则直接跳 到寻找执行end的过程。
所以在找到list这个action后, catalyst实际上执行了一系列的action,这堆action就是所谓的反应链,在本例中,该反应链为:
a/a2/a3/begin or a/a2/begin or a/begin or begin
auto
a/auto
a/a2/auto
a/a2/a3/auto
a/a2/a3/list
a/a2/a3/end or a/a2/end or a/end or end
剩下还有2个很特殊的private属性的action: index跟default.
我们之前讲到, 在为一个URL寻找一个action来进行分发时, path类型的action的优 先级高于regex类型的action. 那么,是不是path类型的action的优先级是最高的呢? 不是的,有一种比path类型的action优先级更高,它就是private属性的index。下面例子:
package X::Controller::A;
package X::Controller::A::B; |
X::Controller::A::B::index 可以而且只能匹配的url是a/b,
我们之前讲到X::Controller::A::b不仅可以匹配a/b这个url,当没有定义a/b/c这个action时,它可能可以匹配a/b/c这个URL。
而index这种action它只能匹配所在Controller的namespace,也就是a/b。
当有客户请求a/b时, 优先考虑的是X::Controller::A::B::index 这个action,如果这个action没有定义,才会匹配到X::Controller::A::b。
package X::Controller::A; |
从这里可以看到,如果客户端访问 a/*的URL时,如果没有定义过相应的a/*这个action的话, d这个action就会派上用场。
那么其实, d这个action可以改成名为default, 属性为private的action,如下:
package X::Controller::A; |
这样的话,default这个action起到的作用跟d的作用是一样,主要用来当找不到可用的action来匹配URL时,就会调用这样 一个默认的action来进行处理,一般是显示一个错误页面,提示用户访问了一不存在的页面。 default这个action跟d的区别在于, 当客户端访问a/xiaosheng时, default内部得到的$c->req->arguments并不是['xiaosheng'], 而是['a','xiaosheng']。当然还有一个区别,那就是default这种action的优先级是最低的,比regex类型的还低。
从这里我们可以知道, X::Controller::Root::default这个action里面可以设置一个全站点的一个默认页面。
5. action的继续剖析
客户端访问a/b/c时,
决定选择哪一个action来处理这个请求的过程如下:
1. $namespace=a/b/c, 到2
2. 在$namespace这个命名空间下, 有没有定义过index这种action,有则选之,无则到3
3. 在$namespace这个命名空间下, 有没有定义过path类型的action可以处理a/b/c这个URL,有则选之,无则到4
4. 在$namespace这个命名空间下, 有没有定义过regex类型的action可以处理a/b/c这个URL,有则选之,无则到5
5. 在$namespace这个命名空间下, 有没有定义过default action可以处理a/b/c这个URL,有则选之,无则到6
6. 如果$namespace已经不可再往上,那么报错,退出,否则$namespace= $namespace往上一层(比如a/b/c往上一层则是a/b),到2
由此可见,如果没有定义Root::default,那么将可能导致无法找到一个action来处理而导致出错。