tornado 学习笔记5 构建Tornado网站应用

时间:2021-07-27 15:46:25

一个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/

tornado 学习笔记5 构建Tornado网站应用

单击“link to story 1”之后,

tornado 学习笔记5 构建Tornado网站应用

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")