Skip to main content

Tornado Web应用程序的结构

Tornado Web应用程序通常包括一个或多个 RequestHandler 子类,将入局请求路由到处理程序的 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 的第三个元素传递,它将提供将传递给 RequestHandler.initialize初始化参数。最后,URLSpec 可以具有名称,这将允许其与 RequestHandler.reverse_url 一起使用。

例如,在这个片段中,根URL / 被映射到 MainHandler,并且以 /story/ 的形式的URL之后是数字的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() 按名称加载 Template 并使用给定的参数进行渲染。 write() 用于非基于模板的输出;它接受字符串,字节和字典(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 中可用,self.request.files 将名称(HTML <input type="file"> 元素的名称)映射到文件列表。每个文件是 {"filename":..., "content_type":..., "body":...} 形式的字典。 files 对象仅在使用表单封装程序(即 multipart/form-data Content-Type)上传文件时才存在;如果未使用此格式,则原始上传的数据在 self.request.body 中可用。默认情况下,上传的文件完全缓存在内存中;如果你需要处理太大的文件,以舒适地保持在内存中看到 stream_request_body 类装饰器。

由于HTML表单编码的奇怪(例如,围绕单数和多个参数的模糊性),Tornado不会尝试将表单参数与其他类型的输入进行统一。特别是,我们不解析JSON请求正文。希望使用JSON而不是表单编码的应用程序可以覆盖 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

覆盖RequestHandler方法

除了 get()/post()/etc,RequestHandler 中的某些其他方法被设计为在必要时被子类覆盖。在每个请求时,发生以下呼叫序列:

  1. 将在每个请求上创建一个新的 RequestHandler 对象

  2. 使用来自 Application 配置的初始化参数调用 initialize()initialize 通常应该保存传递给成员变量的参数;它可能不会产生任何输出或调用方法,如 send_error

  3. prepare() 被调用。这在所有处理程序子类共享的基类中最有用,因为无论使用哪种HTTP方法,都会调用 prepareprepare 可能产生输出;如果它调用 finish (或 redirect 等),处理停止在这里。

  4. 其中一个HTTP方法称为:get()post()put() 等。如果URL正则表达式包含捕获组,它们将作为参数传递到此方法。

  5. 当请求完成时,调用 on_finish()。对于同步处理程序,这是立即在 get() (等)返回后;对于异步处理程序,它是在调用 finish() 之后。

所有被重写的方法都在 RequestHandler 文档中注明。一些最常被覆盖的方法包括:

错误处理

如果处理程序引发异常,Tornado将调用 RequestHandler.write_error 来生成错误页面。 tornado.web.HTTPError 可用于生成指定的状态代码;所有其他异常返回500状态。

默认错误页面包括调试模式下的堆栈跟踪和错误的单行描述(例如“500:内部服务器错误”)。要生成自定义错误页面,重写 RequestHandler.write_error (可能在所有处理程序共享的基类中)。该方法可以通过诸如 writerender 的方法来产生输出。如果错误是由异常引起的,则 exc_info 三元组将作为关键字参数传递(注意,该异常不能保证是 sys.exc_info 中的当前异常,因此 write_error 必须使用例如 traceback.format_exception 而不是 traceback.format_exc)。

也可以通过调用 set_status,编写响应并返回,从常规处理程序方法而不是 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 Found HTTP响应代码,适合于在成功 POST 请求之后重定向用户的情况。如果 permanent 为真,则使用 301 Moved Permanently HTTP响应代码,这对于例如。以SEO友好的方式重定向到页面的规范URL。

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/\1")),
    ])

RequestHandler.redirect 不同,RedirectHandler 默认使用永久重定向。这是因为路由表在运行时不会改变,并且被假定为永久性的,而处理程序中发现的重定向可能是可能更改的其他逻辑的结果。要使用 RedirectHandler 发送临时重定向,请将 permanent=False 添加到 RedirectHandler 初始化参数。

异步处理程序

默认情况下,Tornado处理程序是同步的:当 get()/post() 方法返回时,请求被认为已完成,并且响应被发送。因为所有其他请求在一个处理程序运行时被阻塞,所以任何长时间运行的处理程序都应该是异步的,因此它可以以非阻塞的方式调用其慢速操作。本主题在 异步和非阻塞I/O 中有更详细的介绍;这一节是关于 RequestHandler 子类中异步技术的细节。

使处理程序异步的最简单的方法是使用 coroutine 装饰器。这允许您使用 yield 关键字执行非阻塞I/O,并且在协程返回之前不会发送任何响应。有关详细信息,请参阅 协同

在一些情况下,协同程序可能不如回调导向的样式方便,在这种情况下,可以改为使用 tornado.web.asynchronous 装饰器。当使用此装饰器时,不会自动发送响应;而是请求将保持打开,直到一些回调调用 RequestHandler.finish。它是由应用程序来确保此方法被调用,否则用户的浏览器将简单地挂起。

下面是一个使用Tornado的内置 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() 返回时,请求尚未完成。当HTTP客户端最终调用 on_response() 时,请求仍然打开,并且响应最终通过调用 self.finish() 刷新到客户端。

为了比较,下面是使用协同程序的相同示例:

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

对于更高级的异步示例,请查看 聊天示例应用程序,它使用 长轮询 实现了一个AJAX聊天室。长轮询的用户可能想要覆盖 on_connection_close() 以在客户端关闭连接后清理(但请参阅该方法的docstring以了解警告)。