Tornado路由

来源:互联网 发布:linux集群与自动化运维 编辑:程序博客网 时间:2024/06/06 17:14

url路由

tornado代码文档中提到,“A collection of request handlers that make up a web application“。其实更加detail一点的说法应该是,”A collection of request handlers and a url route talbe that make up a web application”。一个web应用是由一个路由表和请求处理器集合组成。

我们注意到,概念上的抽象,web application的底层才是http server(另外一个著名的python开发框架django也采取同样的概念,命令”python manage.py startapp blog”,生成一个web application)。

其实路由表具有非常重要的作用,卸耦了http server层和web application层

在web框架中,路由表中的任意一项是一个元组,每个元组包含pattern(模式)和handler(处理器)。当httpserver接收到一个http请求,server从接收到的请求中解析出url path(http协议start line中),然后顺序遍历路由表,如果发现url path可以匹配某个pattern,则将此http request交给web应用中对应的handler去处理。

由于有了url路由机制,web应用开发者不必和复杂的http server层代码打交道,只需要写好web应用层的逻辑(handler)即可。

Application

Application类位于web.py,一个Application类的实例相当于一个web应用。前面提到一个web应用是由路由表和处理器(hanlders)组成。当然存储路由表最合适的地方是Application类的实例。

<code class="language-python hljs  has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;">application = tornado.web.Application([        (<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">r"/"</span>, MainHandler),    ])    http_server = tornado.httpserver.HTTPServer(application)    http_server.listen(options.port)    tornado.ioloop.IOLoop.current().start()</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li></ul>

上面的代码,把一个路由表(一个列表)作为参数,传递给Application类的构造函数,创建了一个实例,然后再把这个实例传递给http_server。那么当客户端发起”get /”请求的时候,http server接收到这个请求,在理由表中匹配url pattern,最后交给MainHandler去处理。

路由表项

在tornado中,一个路由表项可以是一个元组(元组最终会转换成URLSpec对象)或者一个URLSpec对象。

<code class="language-python hljs  has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;"><span class="hljs-class" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">class</span> <span class="hljs-title" style="box-sizing: border-box; color: rgb(102, 0, 102);">URLSpec</span>:</span>  <span class="hljs-function" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">def</span> <span class="hljs-title" style="box-sizing: border-box;">__init__</span><span class="hljs-params" style="color: rgb(102, 0, 102); box-sizing: border-box;">(self, pattern, handler, kwargs=None, name=None)</span>:</span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li></ul>

一个路由表项,最少含有两个元素pattern(一个正则表达式字符串)和handler(RequestHandler子类类型)。

值得一提的是还可以有两个可选元素,kwargsname

pattern

我们在浏览博客的时候,常常会看到这样的url,如下:

<code class="language-html hljs  has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;">http://blog.csdn.net/wyx819/article/details/45652713</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul>

当然,在浏览器的地址栏输入这个url,会看到我的博客《tornaodo模板引擎原理》。我们完成可以想象,这个过程在csdn的博客http server中是如何进行的。csdn的httpserver解析这个url,拿到我的昵称”wyx819”和博客的postid”45652713”,然后交给handler,根据昵称和postid到数据库或者其他存储中取到我的《tornaodo模板引擎原理》这篇博客,经过render后,返回给客户端。

如果用tornado开发,假设web应用开发人员已经写好了,下面的handler。

<code class="language-python hljs  has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;"><span class="hljs-function" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">def</span> <span class="hljs-title" style="box-sizing: border-box;">DisplayHandler</span><span class="hljs-params" style="color: rgb(102, 0, 102); box-sizing: border-box;">(RequestHandler)</span>:</span>    <span class="hljs-function" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">def</span> <span class="hljs-title" style="box-sizing: border-box;">get</span><span class="hljs-params" style="color: rgb(102, 0, 102); box-sizing: border-box;">(self, nickname, postid)</span>:</span>        entry = self.db.get(<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"SELECT * FROM entries WHERE nickname = %s and postid = %d"</span>, slug, postid)        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">not</span> entry: <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">raise</span> tornado.web.HTTPError(<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">404</span>)        self.render(<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"entry.html"</span>, entry=entry)</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li></ul>

我们该如何写url路由内? 
实际上所谓的url pattern在tornado中,是实实在在的正则表达式,因此需要一个正则表达式能够从http://blog.csdn.net/wyx819/article/details/45652713中提取出,”wyx819”和”45652713”。

在python标准库中,正则表达式主要由re模块处理。re模块的学习和使用,请参考这里

标准的正则表达式,本来提供分组(group)的语法,如”gr(a|e)y”,如果有子字符串匹配到元字符’(‘和’)’中的正则表达式,该子字符串会被作为一个孤立的group提取出来,在re模块中可以有Match.group方法,访问到该字符串。

在python的正则表达式re模块,进一步扩充了group语法,增加”named group”,如“(P?pattern)”,可以为括号中的正则表达式pattern命名(在尖括号里),最后通过Match.groupdict()方法返回一个字典,group name是该字典的key。

所以,我们可以根据python正则表达式的语法,写出如下的url pattern

<code class="language-html hljs  has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;">application = tornado.web.Application([        (r"/(P?<span class="hljs-tag" style="color: rgb(0, 102, 102); box-sizing: border-box;"><<span class="hljs-title" style="box-sizing: border-box; color: rgb(0, 0, 136);">nickname</span>></span>.*)/article/details/(P?<span class="hljs-tag" style="color: rgb(0, 102, 102); box-sizing: border-box;"><<span class="hljs-title" style="box-sizing: border-box; color: rgb(0, 0, 136);">postid</span>></span>.*)", MainHandler),    ])</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li></ul>

正则表达式 
在tornado框架中,m.groupdict()[‘nickname’]与m.groupdict()[‘postid’],会作为命名参数传递给DisplayHandler实例get方法。

handler(RequestHandler)

handler是路由表项中的第二个必要元素,如同MVC中的控制器,web应用开发者可以继承RequestHandler,实现RequestHandler中提供的接口方法(“get()”,“put()”等等。

kwargs(handler constructor’s arguments)

kwargs是路由表项的第三个可选元素,是一个字典,主要和RequestHandler中的initialize方法有关。initialize方法是RequestHandler函数中的钩子,RequestHandler子类,可以重写这个钩子函数:

<code class="language-python hljs  has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;"><span class="hljs-class" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">class</span> <span class="hljs-title" style="box-sizing: border-box; color: rgb(102, 0, 102);">ProfileHandler</span><span class="hljs-params" style="color: rgb(102, 0, 102); box-sizing: border-box;">(RequestHandler)</span>:</span>    <span class="hljs-function" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">def</span> <span class="hljs-title" style="box-sizing: border-box;">initialize</span><span class="hljs-params" style="color: rgb(102, 0, 102); box-sizing: border-box;">(self, database)</span>:</span>        self.database = database    <span class="hljs-function" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">def</span> <span class="hljs-title" style="box-sizing: border-box;">get</span><span class="hljs-params" style="color: rgb(102, 0, 102); box-sizing: border-box;">(self, username)</span>:</span>        ...app = Application([ (<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">r'/user/(.*)'</span>, ProfileHandler, dict(database=database)),])</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li></ul>

kwargs字典中每一项,会作为RequestHandler子类的成员变量,将子类初始化。

题外话

如上面的例子,要使ProfileHandler类中的方法可以访问到database 实例,还有其他方法,其中一个是Application子类化。

<code class="hljs python has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;"><span class="hljs-class" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">class</span> <span class="hljs-title" style="box-sizing: border-box; color: rgb(102, 0, 102);">RequestHandler</span><span class="hljs-params" style="color: rgb(102, 0, 102); box-sizing: border-box;">(object)</span>:</span>    <span class="hljs-function" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">def</span> <span class="hljs-title" style="box-sizing: border-box;">__init__</span><span class="hljs-params" style="color: rgb(102, 0, 102); box-sizing: border-box;">(self, application, request, **kwargs)</span>:</span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li></ul>

从RequestHandler的构造函数中,我们可以看到它里面的方法是可以访问到Application实例的,因此我们子类Application,在其中添加自定义的成员变量和函数,也算是应用级别的一种数据,方法共享。

name(handler’s name)

name是路由表项中第四个可选元素,主要为一个Handler命名,以便在模板中,可以通过”{{ reverse_url(name)}}”获得实际的url,那么在模板中,就不必硬编码url地址(在前面博文模板引擎原理中提到)。

模板中使用的reverse_url函数调用的就是RequestHandler中的reverse_url函数,实际上最终调用的是Application中的reverse_url函数

<code class="language-python hljs  has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;"><span class="hljs-class" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">class</span> <span class="hljs-title" style="box-sizing: border-box; color: rgb(102, 0, 102);">Application</span>:</span>    <span class="hljs-function" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">def</span> <span class="hljs-title" style="box-sizing: border-box;">add_handlers</span><span class="hljs-params" style="color: rgb(102, 0, 102); box-sizing: border-box;">(self, host_pattern, host_handlers)</span>:</span>        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">not</span> host_pattern.endswith(<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"$"</span>):            host_pattern += <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"$"</span>        handlers = []        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> self.handlers <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">and</span> self.handlers[-<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1</span>][<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>].pattern == <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">'.*$'</span>:            self.handlers.insert(-<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1</span>, (re.compile(host_pattern), handlers))        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">else</span>:            self.handlers.append((re.compile(host_pattern), handlers))        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">for</span> spec <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">in</span> host_handlers:            <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> isinstance(spec, (tuple, list)):                <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">assert</span> len(spec) <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">in</span> (<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">2</span>, <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">3</span>, <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">4</span>)                spec = URLSpec(*spec)            handlers.append(spec)            <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> spec.name:                <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> spec.name <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">in</span> self.named_handlers:                    app_log.warning(                        <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"Multiple handlers named %s; replacing previous value"</span>,                        spec.name)                self.named_handlers[spec.name] = spec</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li><li style="box-sizing: border-box; padding: 0px 5px;">21</li></ul>

Application类在add_handlers方法中,如果发现一个URLSpec对象name成员不为None,那么把它加入字典named_handlers中,因此Application类是有能力根据name反过来获取实际的url。

url匹配流程源码分析

整个url匹配和分配handler的过程主要体现在_RequestDispatcher类的_find_handler方法和execute方法中。

<code class="language-python hljs  has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;"><span class="hljs-function" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">def</span> <span class="hljs-title" style="box-sizing: border-box;">_find_handler</span><span class="hljs-params" style="color: rgb(102, 0, 102); box-sizing: border-box;">(self)</span>:</span>        app = self.application        handlers = app._get_host_handlers(self.request)        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">not</span> handlers:            self.handler_class = RedirectHandler            self.handler_kwargs = dict(url=<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"%s://%s/"</span> % (self.request.protocol, app.default_host))            <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span>        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">for</span> spec <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">in</span> handlers:            match = spec.regex.match(self.request.path)            <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> match:                self.handler_class = spec.handler_class                self.handler_kwargs = spec.kwargs                <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> spec.regex.groups:                    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> spec.regex.groupindex:                        self.path_kwargs = dict(                            (str(k), _unquote_or_none(v))                            <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">for</span> (k, v) <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">in</span> match.groupdict().items())                    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">else</span>:                        self.path_args = [_unquote_or_none(s)                                          <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">for</span> s <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">in</span> match.groups()]                <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span>        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> app.settings.get(<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">'default_handler_class'</span>):            self.handler_class = app.settings[<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">'default_handler_class'</span>]            self.handler_kwargs = app.settings.get(                <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">'default_handler_args'</span>, {})        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">else</span>:            self.handler_class = ErrorHandler            self.handler_kwargs = dict(status_code=<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">404</span>)</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li><li style="box-sizing: border-box; padding: 0px 5px;">21</li><li style="box-sizing: border-box; padding: 0px 5px;">22</li><li style="box-sizing: border-box; padding: 0px 5px;">23</li><li style="box-sizing: border-box; padding: 0px 5px;">24</li><li style="box-sizing: border-box; padding: 0px 5px;">25</li><li style="box-sizing: border-box; padding: 0px 5px;">26</li><li style="box-sizing: border-box; padding: 0px 5px;">27</li><li style="box-sizing: border-box; padding: 0px 5px;">28</li></ul>

从_find_handler方法中,我们可以看到,tornado按照顺序遍历路由表,拿request.path去匹配路由表中的url pattern。如果匹配上,将路由表项handler类(路由表第二个元素)保存到handler_class,将kwargs(路由表中的第三个元素)保存到handler_kwargs中,如果match对象中存在group,那么提取path参数,保存在path_kwargs或者path_args中。

如果遍历完路由表,都没有匹配上url pattern,看settings中有没配置”default_handler_class”。

如果没有”default_handler_class”,最后交给ErrorHandler处理,客户端会看到一个404的页面。

<code class="language-python hljs  has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;"><span class="hljs-function" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">def</span> <span class="hljs-title" style="box-sizing: border-box;">execute</span><span class="hljs-params" style="color: rgb(102, 0, 102); box-sizing: border-box;">(self)</span>:</span>        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">not</span> self.application.settings.get(<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"compiled_template_cache"</span>, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">True</span>):            <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">with</span> RequestHandler._template_loader_lock:                <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">for</span> loader <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">in</span> RequestHandler._template_loaders.values():                    loader.reset()        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">not</span> self.application.settings.get(<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">'static_hash_cache'</span>, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">True</span>):            StaticFileHandler.reset()        self.handler = self.handler_class(self.application, self.request,                                          **self.handler_kwargs)        transforms = [t(self.request) <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">for</span> t <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">in</span> self.application.transforms]        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> self.stream_request_body:            self.handler._prepared_future = Future()        f = self.handler._execute(transforms, *self.path_args, **self.path_kwargs)        f.add_done_callback(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">lambda</span> f: f.exception())        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> self.handler._prepared_future</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li></ul>

在execute方法中,我们看到以下代码:

<code class="language-python hljs  has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;">f = self.handler._execute(transforms, *self.path_args, **self.path_kwargs)</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul>

前面_find_handler取到的path参数path_args和path_kwargs,最终交给RequestHander类的_execute方法去处理。 
在RequestHandler中的_execute方法,我们可以看到以下代码:

<code class="hljs oxygene has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">result</span> = <span class="hljs-function" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">method</span><span class="hljs-params" style="color: rgb(102, 0, 102); box-sizing: border-box;">(*<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">self</span>.path_args, **<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">self</span>.path_kwargs)</span></span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul>

拿前面csdn博客的例子来说,最后调用到DisplayHandler的get方法,并且得到nickname和postid这两个path参数。

重定向

关于重定向,一个非常典型的例子是当用户访问一些需要授权才能访问的页面的时候,这个时候网站的反应应该是重定向到登陆页面。而这一点在tornado中,是如何做的?

需要给授权页面对应的Handler中的http方法,加入authenticated函数装饰器。

<code class="language-python hljs  has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;"><span class="hljs-function" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">def</span> <span class="hljs-title" style="box-sizing: border-box;">authenticated</span><span class="hljs-params" style="color: rgb(102, 0, 102); box-sizing: border-box;">(method)</span>:</span>    <span class="hljs-decorator" style="color: rgb(0, 102, 102); box-sizing: border-box;">@functools.wraps(method)</span>    <span class="hljs-function" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">def</span> <span class="hljs-title" style="box-sizing: border-box;">wrapper</span><span class="hljs-params" style="color: rgb(102, 0, 102); box-sizing: border-box;">(self, *args, **kwargs)</span>:</span>        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">not</span> self.current_user:            <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> self.request.method <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">in</span> (<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"GET"</span>, <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"HEAD"</span>):                url = self.get_login_url()                <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"?"</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">not</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">in</span> url:                    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> urlparse.urlsplit(url).scheme:                        <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># if login url is absolute, make next absolute too</span>                        next_url = self.request.full_url()                    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">else</span>:                        next_url = self.request.uri                    url += <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"?"</span> + urlencode(dict(next=next_url))                self.redirect(url)                <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span>            <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">raise</span> HTTPError(<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">403</span>)        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> method(self, *args, **kwargs)    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> wrapper</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li></ul>

如果发现用户没有登陆,会调用RequestHandler.redirect(url),这个要求用户得首先设置login_url。这个设计上,我觉得不应该直接设置url,而应该设置login_url对应的Handler的名字(name),因为url可能会经常改变,而Handler的名字不会经常改变,tornado中应该提供这样的一个函数,根据Handler的name重定向,当然首先调用reverse_url,再调用redirect方法就可以。

如果在路由表中,我们需要直接设置重定向,该怎么办? 
tornado中也为我们处理好了,框架中内置了一个RedirectHandler

HTTP方法限制

在web应用开发过程,我们通常希望限制Handler只能处理某些http方法。 
个人觉得应该在路由表中加入第五个元素:

<code class="hljs ini has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;"><span class="hljs-setting" style="box-sizing: border-box;">restrict_method=<span class="hljs-value" style="box-sizing: border-box;">[<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"POST"</span>]</span></span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul>

当然这个在tornado中是只有支持,

<code class="language-python hljs  has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;"><span class="hljs-class" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">class</span> <span class="hljs-title" style="box-sizing: border-box; color: rgb(102, 0, 102);">RequestHandler</span><span class="hljs-params" style="color: rgb(102, 0, 102); box-sizing: border-box;">(object)</span>:</span>    SUPPORTED_METHODS = (<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"GET"</span>, <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"HEAD"</span>, <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"POST"</span>, <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"DELETE"</span>, <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"PATCH"</span>, <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"PUT"</span>,                         <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"OPTIONS"</span>)</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li></ul>

通过RequestHandler的SUPPORTED_METHODS列表,子类中可以重写。

<code class="language-python hljs  has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;"><span class="hljs-class" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">class</span> <span class="hljs-title" style="box-sizing: border-box; color: rgb(102, 0, 102);">RequestHandler</span>    <span class="hljs-title" style="box-sizing: border-box; color: rgb(102, 0, 102);">def</span> <span class="hljs-title" style="box-sizing: border-box; color: rgb(102, 0, 102);">_execute</span><span class="hljs-params" style="color: rgb(102, 0, 102); box-sizing: border-box;">(self, transforms, *args, **kwargs)</span>:</span>        <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"""Executes this request with the given output transforms."""</span>        self._transforms = transforms        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">try</span>:            <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> self.request.method <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">not</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">in</span> self.SUPPORTED_METHODS:                <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">raise</span> HTTPError(<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">405</span>)        ...</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li></ul>

如果发现http请求的方法在Handler中不支持,Handler不再做处理,提早返回一个405页面(资源禁止访问)。

路由机制待改善地方

其实路由规则可以非常灵活,可以让我们diy,满足更加多的需求,比如特殊条件限制(限制某些网段的ip才可以访问某个url)。只不过tornado框架,它并不是作为一个非常完善的web开发框架,也许这个框架的重点就不在这里。作为学习的角度来说,我们也应该对此多做了解。路由规则方面可以参考这里,rubyonrails的路由规则。

0 0
原创粉丝点击