一个Tornado网络应用的结构

Tornado Web应用程序通常由一个或多个RequestHandler子类,一个将请求路由到处理程序(handlers)的Application对象和一个用于启动服务器的main()函数组成。

最小的“hello world”示例如下所示:

import tornado.ioloop
import tornado.web

class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("Hello, world")

def make_app():
    return tornado.web.Application([
        (r"/", MainHandler),
    ])

if __name__ == "__main__":
    app = make_app()
    app.listen(8888)
    tornado.ioloop.IOLoop.current().start()
Application对象

Application对象负责全局配置,包括将请求映射到处理程序的路由表。

路由表是URLSpec对象(或元组)的列表,每个对象包含(至少)正则表达式和处理程序类。 顺序很重要: 最终生效的是第一个匹配规则。如果正则表达式包含匹配分组,这些分组会作为路径参数传递给处理器的HTTP方法。如果一个字典作为URLSpec的第三方元素被传递进来,它支持initialization参数,这个参数会传递给RequestHandler.initializa方法。最后,URLSpec可能还会有一个名字,这将使它能够被RequestHandler.reverse_url方法使用。

例如,在此片段中,根URL /映射到MainHandler,表单/story/后跟数字的URL映射到StoryHandler。 该数字(作为字符串)传递给StoryHandler.get

class MainHandler(RequestHandler):
    def get(self):
        self.write('<a href="%s">link to story 1</a>' %
                   self.reverse_url("story", "1"))

class StoryHandler(RequestHandler):
    def initialize(self, db):
        self.db = db

    def get(self, story_id):
        self.write("this is story %s" % story_id)

app = Application([
    url(r"/", MainHandler),
    url(r"/story/([0-9]+)", StoryHandler, dict(db=db), name="story")
    ])

Application构造器接受许多能用来指定应用行为的关键字参数,并且允许可选特性;完整列表请参看Application.settings

基类化RequestHandler

Tornado Web应用程序的大部分工作都是在RequestHandler的子类中完成的。处理器子类的主要入口是处理对应的HTTP方法:get(),post()等等。每个处理程序可以定义一个或多个这些方法来处理不同的HTTP操作。如上所述,这些方法将会加上路径中匹配到的参数来被调用。

在处理程序中,调用RequestHandler.renderRequestHandler.write等方法来生成响应。 render()按名称加载模板,并使用给定的参数呈现它。 write()用于非基于模板的输出;它接受strings,bytes和字典(dicts将被编码为JSON)。

RequestHandler中的许多方法都设计为在子类中重写,并可以在整个应用程序中使用。通常定义一个BaseHandler类来重写诸如write_errorget_current_user之类的方法,然后把你的BaseHandler代替RequestHandler作为所有处理器的基类。

处理请求输入

请求处理程序可以使用self.request访问表示当前请求的对象。有关完整的属性列表,请参阅HTTPServerRequest的类定义。

HTML表单提交的请求数据将会为你分析好,并且可以在一些方法像get_query_argumentget_body_argument中可用。

class MyFormHandler(tornado.web.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表单编码对于一个参数是单值还是列表是模糊不清的,RequestHandler对方法进行了区分,使得应用能够表明是否希望获得列表,对列表来说,使用get_query_argumentsget_body_arguments代替单值的方法。

通过表单上传文件可以通过self.request.files来获得,映射名字(input的name属性)到文件列表。每个文件都是{"filename":…, "content_type":…, "body":…}格式的字典。files对象文件仅在使用表单包装器上传文件时才存在(例如multipart/form-data Content-Type);如果没有使用这种格式,原生的上传数据会存放在self.request.boby中。默认情况下上传的文件是全部缓存在内存中的;如果你需要处理一些很大的文件,不方便放在内存中,查看stream_request_body装饰器。

在demos文件夹中(tornado源码中有一个demos文件夹,存放了几个小的例子),file_reciver.py展示了这两种方法的来接收上传文件。

由于HTML表单编码的问题(单值和多值的模糊性),Tornado并不打算用其他类型的输入来统一表单参数。尤其我们不会去分析JSON请求体。希望使用JSON来代替form-encoding的应用可能会重写prepare方法来解析它们的请求:

def prepare(self):
    if self.request.headers.get("Content-Type", "").startswith("application/json"):
        self.json_args = json.loads(self.request.body)
    else:
        self.json_args = None
重写RequestHandler中的方法

除了get()post()方法,在RequestHandler还有一些其他方法在必要时也可以被子类重写。在每个请求中,都会发生以下一系列的调用:

  1. 每个请求中都会新建一个RequestHandler对象。
  2. 如果有从Application配置中获得初始参数的话initialize()函数会被调用,initialize函数应该只保存传递给成员变量的参数;它可能不会产生任何输出或调用类似send_error一样的方法。
  3. 调用prepare方法。这在一个基类中是最有用的,基类由你的处理器子类所共享,因为不论使用哪种HTTP方法prepare函数都会被调用。prepare可能会产生输出;如果它调用了finish(或者redirect方法等),进程就会在此结束。
  4. 某一个HTTP方法被调用:get()post()put()等等。如果URL正则表达式包含了捕捉分组参数(capturing groups),这些参数也会传递到此方法中。
  5. 当请求结束时,on_finish会被调用,对于大多数处理程序,这一步在get()(或其他方法)return后就会立即执行;对于使用tornado.web.asynchronous装饰器的处理程序,它发生在调用finish()之后。

正如在RequestHandler文档中提到的一样,所有方法都是可以重写的。 一些最常被重写的方法包括:

  • write_error- 输出html的错误页面。
  • on_connection_close当客户端断开连接的时候调用;应用可能会选择检测这种情况然后停止更深层的处理,注意,不能保证一个已关闭的连接也能被及时检测到。 请注意,无法保证可以立即检测到已关闭的连接。
  • get_current_user- 请参阅用户身份验证(User authentication)。
  • get_user_locale- 为当前用户返回一个locale对象。
  • set_default_headers- 可用于在response上设置其他的响应头(例如自定义Serverheader)
错误处理

如果处理器抛出一个异常,tornado会调用RequestHandler.write_error来生成一个错误页面。tornado.web.HTTPError可以被用来产生一个特定的状态码;其他所有异常都返回500状态码。

默认的错误页面包括一个堆栈路径(debug模式下)另外还有一个错误的线上描述(move brand_model.txt to project)。为了生成一个自己的错误页面,重写RequestHandler.write_error(可能是在一个由你的所有处理器所共享的基类中)这个方法可以用过writerender等方法产生正常的输出。如果错误是由一个异常导致的,一个exc_info会作为一个关键字参数被传递进来(注意,此异常不保证是sys.exc_info中当前的异常,因此write_error必须使用traceback.format_exception来代替traceback.format_exc)。

通过调用set_status,写一个响应或return等方法从常规的处理器方法(而不是write_error)中产生一个错误页面也是可能的。tornado.web.Finish这个特殊的异常可能会被抛出以终止处理器,在简单地返回不方便的情况下并不调用write_error方法。

对于404错误来说,使用default_handler_class``````Application setting。这个处理器用改重写prepare方法,而不是更详细的如get()方法,以便在任何HTTP方法中都能使用。它应该产生一个如上所述的错误页面:或者通过抛出一个HTTPError(404)并重写为write_error,或者调用self.set_status(404)并在prepare()中直接产生响应。

重定向

在Tornado中有两种方式可以重定一个请求:RequestHandler.redirectRedirectHandler

您可以在RequestHandler方法中使用self.redirect()将用户重定向到其他位置。还有一个可选参数permanent,可用于表明该重定向为永久重定向。 permanent的默认值为False,它生成302 FoundHTTP响应代码,适用于成功POST请求后重定向用户等事项。 如果permanenttrue301 Moved PermanentlyHTTP响应码会被使用,在下面这中情况下是有用的:重定向到一个权威的URL来采用一种SEO友好的方式获取页面。

RedirectHandler可以让你在你的Application路由表中直接配置重定向。例如,配置一个静态重定向:

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

RedirectHandler还支持正则表达式替换。以下规则将以/pictures/开头的所有请求重定向到前缀/photos/

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

不像RequestHandler.redirectRedirectHandler默认使用永久重定向。这是因为路由表在运行过程中不会改变并且是永久的,尽管在处理器中的重定向很可能是其他可能回改变的逻辑所导致的。如果想使用RedirectHandler发起一个临时重定向,只需要把permanent=False参数加到RedirectHandler的初始化参数中。

异步处理器

某些处理程序方法(包括prepare()和HTTP请求方法get()post()等)可以作为协程重写,以实现异步化。

Tornado还支持使用tornado.web.asynchronous装饰器那样基于回调的异步处理程序样式,但这种样式已弃用,将在Tornado 6.0中删除。新的应用程序应该使用协程代替旧的写法。

下面是一个简单的使用协程的handler示例:

class MainHandler(tornado.web.RequestHandler):
    async def get(self):
        http = tornado.httpclient.AsyncHTTPClient()
        response = await 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")

有关更高级的异步示例,请查看聊天示例应用程序,该应用程序使用长轮询实现AJAX聊天室。 长轮询的用户可能希望在客户端关闭连接后重写on_connection_close()以进行清理操作(但要注意的是,请参见该方法的docstring)。