一个Tornado 网站应用通常由一个或多个RequestHanlde的子类、一个负责将请求路由至handlers的Application以及一个启动服务器的main()函数等组成。

一个最小的“hello world”示例:

from tornado.ioloop import IOLoop
from tornado.web import RequestHandler, Application, url class HelloHandler(RequestHandler):
def get(self):
self.write("Hello, world") def make_app():
return Application([
url(r"/", HelloHandler),
]) def main():
app = make_app()
app.listen(8888)
IOLoop.current().start()

Application对象

Application这个对象负责全局的配置,包括把请求映射到Handlers的路由表(Route Table)。

路由表URLSpec对象集合(或者元组),包含一个正则表达式和Handler类。如果正则表达式中包含捕获组,这些组将会是路径参数(path arguments),而且这些捕获的组的值会传递hanlder的Http方法的参数中。如果将字典对象作为第三个参数传递的话,这个参数的值将会传递给RequestHandler的initialize的参数。最后,URLSpec可以有个名称,可以通过Request.reverse_url得到。

示例:

# -*- coding: utf-8 -*-
from tornado.ioloop import IOLoop
from tornado.web import Application, url, RequestHandler __author__ = 'Administrator' class MainHanlder(RequestHandler):
def get(self):
self.write('<a href="%s">link to story 1</a>' %
self.reverse_url("story", "")) class StoryHanlder(RequestHandler):
def initialize(self, db):
self.db = db def get(self, story_id):
self.write("this is story %s" % story_id) db = 1 app = Application([
url(r"/", MainHanlder),
url(r"/story/([0-9]+)", StoryHanlder, dict(db=db), name="story") ]) def main():
app.listen(8888)
IOLoop.current().start() if __name__ == "__main__":
main()

根路径被映射至MainHanlder.而“/story/数字”格式的请求则被映射成StoryHandler.这个数字会当成是字符串传递给StoryHanlder的get方法。

在游览器中输入http://localhost:8888/

单击“link to story 1”之后,

Application 构造函数有很多关键字参数,用来自定义应用程序的行为。具体请参考Application.settings查看完整的设置参数列表。

RequestHanlder的子类

Tornado web应用程序大部分工作都是由RequestHandler的子类来完成。主要由这个类的方法来处理,比如get(),post()等。每个handler都是定义一个或者多个这样的方法来处理不同的HTTP请求。正如上面所说,这些方法参数的值,会根据路由正则表达式匹配后的值传递进来。

在hanlder中,调用RequestHanlder.render或者RequestHanlder.write等方法来产生响应。render() 方法通过名称加载一个模板,并渲染模板。write() 方法而是针对针对非模板的输出,接收字符串、字节和字典(字典必须编码成JSON格式)等参数。

在RequestHandler类中,定义了很多可以被子类重载的方法。所以在开发自身的应用程序时,通常会定义一个BaseHanlder类去重写write_error 和 get_current_user等方法,然后子类继承BaseHanlder而非RequeseHanlder。

处理请求输入

请求的hanlder能够通过调用self.request属性访问代表当前请求的对象。请查看HttpServerRequest类的详细属性定义。

示例

class MyFormHandler(RequestHandler):
def get(self):
self.write('<html><body><form action="/myform" method="POST">'
'<input type="text" name="message">'
'<input type="submit" value="Submit">'
'</form></body></html>') def post(self):
self.set_header("Content-Type", "text/plain")
self.write("You wrote " + self.get_body_argument("message"))

用HTML表单格式的请求数据会被解析,而且通过调用方法可以获取,比如get_query_argument和get_body_argument方法。

而如果表单中字段有多个值,可以调用get_query_arguments和get_body_argumens方法。

表单的上传的文件可以通过self.request.files获取。每个每件对象是一个字典,格式为{“filename”:..,”content_type”:..,”body”:…}.只有当文件通过form wrapper方式(比如Content-type的类型为multipart/form)上传时,files属性值才存在。如采用form wrapper格式的话,上传的数据可以通过self.request.body属性获取。默认情况下,上传的文件会缓存在内存中。如果你处理的文件太大以至于不能很好地保存在内存中时,请查看stream_request_body类装饰器。

由于HTML表单编码的怪异性,Tornado不会将不同类型的输入参数组合一起。在特定情况下,我们不会去解析JSON请求体。应用如果喜欢用JSON去替代表单编码(form-encoding)的话,可以去重写prepare方法去解析请求。如下:

def prepare(self):
if self.request.headers["Content-Type"].startswith("application/json"):
self.json_args = json.loads(self.request.body)
else:
self.json_args = None

重写RequestHanlder方法

除了get()/post()等方法外,在必要的时候,其他方法也可以被子类重写。在每一个请求中,会执行以下的调用顺序:

1. 一个新的RequestHandler对象被创建

2. Initialize()被调用,参数的值从Application对象的配置中获取。Initialize一般只保存这些参数至成员变量中,不会产生任何输出或者调用方法比如send_error.

3. prepare() 被调用。这个的实现最好共享放置在基础类中,供其他子类继承,因为不管任何的HTTP方法被使用,prepare方法都会被调用;prepare可以会产生输出,如果调用了finish(或者redirect等)方法,请求处理过程结束。

4. get(),post(),put()等任一一个方法被调用,如果URL正则表达式包含了捕获组,这些捕获的组的值都会传递给这些方法的参数;

5. 当请求处理结束后,on_finish()方法被调用。对于同步的handler,当get()(举个例子)方法返回后,被立即调用on_finish方法。

通常情况下,重写最多的方法包括:

  • l write_error . 给用户输出错误页面的HTML;
  • l On_connection_close 当用户客户端断开连接是调用。应用可以选择去监听断开,然后去停止一些处理过程。、
  • l get_current_user. 获得当前用户。
  • l get_user_locale .针对当前用户,返回Locale对象
  • l set_default_headers .在响应中添加额外的头部。(比如自定义的服务头部)

错误处理

如果hanlder发生了异常,Tornado将会调用Request.write_error去生成错误页面。tornado.web.HTTPError 可以用来产生一个具体的错误状态码,所有的其他异常返回500的状态码。

在调试模式下,默认的错误页面包括堆栈跟踪(stack trace),在其他情况下返回一行错误的描述(比如:“500:Internal Server Error”)。为了产生自定义的错误页面,重写request.write_error方法(可能放在供其他handlers继承的基类中)。当被异常导致错误时,exc_info 被作为关键字参数传递(注意,这个异常不能保证是当前在sys.exc_info的异常,所以write_error必须使用比如traceback.format_exception 替代traceback.format_exc)。

另外一种产生错误页面的方式为调用set_status方法,而不是write_error,然后写响应以及返回。特殊的tornado.web.Finish异常被触发会终止hanlder,而不 会调用write_error。

针对404错误,使用在Application setting的default_handler_class来处理。这个handler必须重写prepare方法。根据上面的描述,这将会产生一个错误页面,要么触发HTTPError(404)错误以及重写write_error,要么调用self.set_status(404)然后在prepare中直接产生响应。

重定向

Tornado存在两种主要的重定向请求的方式,一种是调用RequestHanlder.redirect方法,另外一种是直接使用RedirectHandler类。

在实现的hanlder中使用self.redirect方法可以重定向到任何地方。这个方法还有一个参数permanent,用来指示这个重定向是否是永久的。这个参数的默认值为False,会产生302 Found状态编号的响应,而且特别适合像POST()请求处理成功后的重定向。如果permanent这个参数值为True, 301 Moved Permanetly状态编码会返回给用户。

RedirectHandler ,允许用户可以在Application路由表中配置重定向的链接。比如下面一个例子,配置了单个静态重定向。

app = tornado.web.Application([
url(r"/app", tornado.web.RedirectHandler,
dict(url="http://itunes.apple.com/my-app-id")),
])

RedirectHanlder 同样支持正则表达式的情形。下面的规则定义,可以使以/pictures/开头的请求重定向至以/photos/为前缀的请求。

app = tornado.web.Application([
url(r"/photos/(.*)", MyPhotoHandler),
url(r"/pictures/(.*)", tornado.web.RedirectHandler,
dict(url=r"/photos/\1")),
])

不像RequestHanlder.redirect方法,RedirectHandler默认采用永久的重定向,即permanent属性为True.这是应为路由表在运行时不能改变,而在hanlder中使用的重定向地址在逻辑上可以会改变。如果要用RedirectHandler发送一个临时的重定向,在RedirectHandler初始化参数中将permanent设置成False.

异步Handler

默认情况下,hanlders是同步的。当get()/post()方法返回时,请求才认为结束,然后再发送响应。当handler正在运行时,其他请求将会被阻塞。任何一个长时间运行的hanlder必须设置为异步的,这样操作就不会阻塞。

使hanlder成为异步的最简单的方式就是使用coroutine装饰器。使用yield关键字可以执行非阻塞的I/O操作,直到coroutine已经返回后,才能将响应返回给用户端。

在有些情况下,coroutines没有采用回调的方式方便,这种回调方式是采用tornado.web.asynchrounous装饰器。当使用这个装饰器时,响应不会自动发送,请求会保持打开的状态直到回调函数调用了RequestHandler.finish方法。应用必须保证这个方法被调用,不然用户的游览器将会简单地暂停。

下面是个例子,使用内置的 AsyncHttpClient调用FriendFeed Api。

class MainHandler(tornado.web.RequestHandler):
@tornado.web.asynchronous
def get(self):
http = tornado.httpclient.AsyncHTTPClient()
http.fetch("http://friendfeed-api.com/v2/feed/bret",
callback=self.on_response) def on_response(self, response):
if response.error: raise tornado.web.HTTPError(500)
json = tornado.escape.json_decode(response.body)
self.write("Fetched " + str(len(json["entries"])) + " entries "
"from the FriendFeed API")
self.finish()

当get()方法返回时,这个请求没有结束。当最后调用on_response 方法之前,这个请求一直都是打开的,当调用了self.finish方法后,响应最后才会发送给客户端。

为了对比,下面用coroutine实现的方式。

class MainHandler(tornado.web.RequestHandler):
@tornado.gen.coroutine
def get(self):
http = tornado.httpclient.AsyncHTTPClient()
response = yield http.fetch("http://friendfeed-api.com/v2/feed/bret")
json = tornado.escape.json_decode(response.body)
self.write("Fetched " + str(len(json["entries"])) + " entries "
"from the FriendFeed API")

tornado 学习笔记5 构建Tornado网站应用的更多相关文章

  1. tornado 学习笔记1 引言

    从事软件开发这行业也快5年啦,其实从事的工作也不完全是软件开发,软件开发只是我工作中的一部分.其中包括课题研究.信息化方案设计.软件开发.信息系统监理.项目管理等工作,比较杂乱.开发的软件比较多,但是 ...

  2. Tornado学习笔记(一) helloword/多进程/启动参数

    前言 当你觉得你过得很舒服的时候,你肯定没有在进步.所以我想学习新的东西,然后选择了Tornado.因为我觉得Tornado更匹配目前的我的综合素质. Tornado学习笔记系列主要参考<int ...

  3. tornado 学习笔记7 RequestHandler功能分析

           在第5部分讲到,构建一个tornado网站,必须包含一个或者多个handler,这些handler是RequestHandler的子类.每个请求都会被映射到handler中进行处理,处理 ...

  4. tornado学习笔记

    一.UIMOTHODS: 1.在项目目录创建uimothods.py文件(名称可以任意)内容: def test2(self): return ('hello uimothods') 2.tornad ...

  5. 【转】Ant学习笔记——自己构建Ant编译环境

    自从年初开始用NetBeans6.0,才接触到Ant. 这是今年6月份的一篇Ant学习笔记.安装 1.下载并构建环境.   去官网下载src包和bin包.解压缩它们到同一目录,运行build.bat, ...

  6. alfs学习笔记-自动化构建lfs系统

    我的邮箱地址:zytrenren@163.com欢迎大家交流学习纠错! 一名linux爱好者,记录构建Linux From Scratch的过程 经博客园-骏马金龙前辈介绍,开始接触学习lfs,用博客 ...

  7. tornado 学习笔记2 Python web主流框架

    2.1 Django 官方网址:https://www.djangoproject.com/ 简介:Django is a high-level Python Web framework that e ...

  8. Tornado学习笔记(三) 请求方式/状态码

    本章我们来学习 Tornado 支持的请求方式 请求方式 Tornado支持任何合法的HTTP请求(GET.POST.PUT.DELETE.HEAD.OPTIONS).你可以非常容易地定义上述任一种方 ...

  9. Tornado学习笔记(二) 路由/post/get传参

    本章我们学习 Tornado 的路由传参等问题 路由 路由的匹配 Tornado的路由匹配采用的是正则匹配 一般情况下不需要多复杂的正则,正则的基本规则如下(站长之家) 举个例子 (r'/sum/(\ ...

随机推荐

  1. Spring系列之AOP

    一.什么是AOPAOP(Aspect-OrientedProgramming,面向方面编程),可以说是OOP(Object-Oriented Programing,面向对象编程)的补充和完善.OOP引 ...

  2. List拆分成多个集合

    如果对一组大的集合进行操作,想分组进行,比如批量新增10000条数据,想100条分成一个集合分成100个集合,对集合进行操作100次,用C#如何编写,这里记录下代码如下 //构造被分隔的集合 List ...

  3. tyvj1192 迎春舞会之集体舞

    背景 HNSDFZ的同学们为了庆祝春节,准备排练一场舞会. 描述 表演者排成n排,构成一个向前的正三角形(在屏幕上,即向下).而就每个人,他有可能正面朝前(小的向前正三角形).或向后三角形(小的向后正 ...

  4. cf723d Lakes in Berland

    The map of Berland is a rectangle of the size n × m, which consists of cells of size 1 × 1. Each cel ...

  5. R笔记 单样本t检验 功效分析

    R data analysis examples 功效分析 power analysis for one-sample t-test单样本t检验 例1.一批电灯泡,标准寿命850小时,标准偏差50,4 ...

  6. 动态执行python脚本

    前言 存在许多独立的python脚本,这些脚本可能会增加,也可能会减少,现在需要按照某种顺序调度这些程序.在python的standard library中,有一个模块imp可以实现动态的调用ptho ...

  7. JavaScript方法

    1.hasOwnProperty:是用来判断一个对象是否有你给出名称的属性或对象.不过需要注意的是,此方法无法检查该对象的原型链中是否具有该属性,该属性必须是对象本身的一个成员.isPrototype ...

  8. Python dir

    1. 在python命令行交互环境下,可以用dir()函数查看当前的变量,比如: >>> dir()['__builtins__', '__doc__', '__loader__', ...

  9. webstormkey

    webStorm : UserName:William ===== LICENSE BEGIN ===== 45550-12042010 00001SzFN0n1bPII7FnAxnt0DDOPJA  ...

  10. MySQL Binlog Mixed模式记录成Row格式

    背景: 一个简单的主从结构,主的binlog format是Mixed模式,在执行一条简单的导入语句时,通过mysqlbinlog导出发现记录的Binlog全部变成了Row的格式(明明设置的是Mixe ...