Tornado 0105 - 用户指南: Tornado Web 应用程序的结构


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 对象(或元组)的列表,每个对象包含(至少)一个正则表达式和一个处理程序类(Handler)。 顺序很重要;第一个被匹配到的规则会被优先使用。 如果正则表达式包含捕获组,则这些组是“路径参数”,并将传递给处理程序的 HTTP 方法。 如果字典作为 URLSpec 的第三个元素传递,它将提供将传递给 RequestHandler.initialize 的初始化参数。 最后,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(Subclassing RequestHandler)

Tornado Web 应用程序的大部分工作都是在 RequestHandler 的子类中完成的。处理程序子类的主要入口点是在处理 HTTP 方法之后命名的方法:get(),post() 等。每个处理程序可以定义一个或多个这些方法来处理不同的 HTTP 操作。如上所述,将使用与匹配的路由规则的捕获组相对应的参数来调用这些方法。

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

RequestHandler 中的许多方法都设计为在子类中重写,并在整个应用程序中使用。通常定义一个 BaseHandler 类来覆盖诸如 write_errorget_current_user 之类的方法,然后为您的所有特定处理程序继承你自己的 BaseHandler 而不是 RequestHandler。

处理请求输入(Handling request input)

请求处理程序(Handler)可以使用 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_arguments 和 get_body_arguments,而不是它们的单数对应项。

通过表单上传的文件在 self.request.files 中可用,它将名称(HTML 元素的名称)映射到文件列表。每个文件都是 {“filename”: …, “content_type”: …, “body”: …} 形式的字典。只有在使用表单包装器上传文件时才会出现 files 对象(例如 Content-Type 是 multipart/form-data);如果未使用此格式,则可以在 self.request.body 中使用原始上载数据。默认情况下,上传的文件在内存中完全缓冲;如果你需要处理太大而不能轻松保存在内存中的文件,请参阅 stream_request_body 类装饰器。

在 demos 目录中,file_receiver.py 显示了接收文件上载的两种方法。

由于 HTML 表单编码的怪癖(例如,围绕单数与多数参数的歧义),Tornado 不会尝试将表单参数与其他类型的输入统一起来。特别是,我们不解析 JSON 请求体。希望使用 JSON 而不是表单编码的应用程序可能会覆盖准备解析其请求:

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 中的方法(Overriding RequestHandler methods)

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

  1. 每个请求都会创建一个新的 RequestHandler 对象

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

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

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

  5. 请求完成后,调用 on_finish()。对于大多数处理程序,这是在 get() 返回后立即执行的;对于使用 tornado.web.asynchronous 装饰器的处理程序,它是在调用 finish() 之后。

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

  • write_error - 输出 HTML 以在错误页面上使用。
  • on_connection_close - 在客户端断开连接时调用;应用程序可以选择检测此情况并停止进一步处理。请注意,无法保证可以立即检测到已关闭的连接。
  • get_current_user - 请参阅用户身份验证
  • get_user_locale - 返回用于当前用户的 Locale 对象
  • set_default_headers - 可用于在响应上设置其他标头(例如自定义服务器标头)

错误处理(Error Handling)

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

默认错误页面包括调试模式下的堆栈跟踪和错误的单行描述(例如:“500:内部服务器错误”)。要生成自定义错误页面,请覆盖 RequestHandler.write_error(可能在所有处理程序共享的基类中)。此方法通常可以通过诸如 write 和 render 之类的方法生成输出。如果错误是由异常引起的,则 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 应用程序设置。此处理程序应该覆盖 prepare而不是像 get() 这样的更具体的方法,因此它适用于任何 HTTP 方法。它应该产生如上所述的错误页面:通过引发 HTTPError(404) 并覆盖 write_error,或调用 self.set_status(404) 并直接在 prepare() 中生成响应。

重定向(Redirection)

您可以通过两种主要方式在 Tornado 中重定向请求:RequestHandler.redirect 和 RedirectHandler。

您可以在 RequestHandler 方法中使用 self.redirect() 将用户重定向到其他位置。 还有一个可选参数 permanent,可用于指示重定向被视为永久性。 permanent 的默认值为 False,它生成 302 Found HTTP 响应代码,适用于成功 POST 请求后重定向用户等事项。 如果为 permanent,则使用 301 Moved Permanently HTTP 响应代码,这对于例如以 SEO 友好的方式重定向到页面的规范 URL。

RedirectHandler 允许您直接在应用程序路由表中配置重定向。例如,要配置单个静态重定向:

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

异步处理程序(Asynchronous handlers)

某些处理程序方法(包括 prepare() 和 HTTP 谓词方法 get() / post() 等)可以作为协程进行重写,以使处理程序异步。

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

例如,这是一个使用协程的简单处理程序:

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() 以进行清理(但请参阅该方法的文档字符串以获取注意事项)。