阅读(3370) (0)

Tornado Web应用程序的结构

2022-03-04 13:55:32 更新

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被映射到​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()​用于非基于模板的输出;它接受字符串、字节和字典(dict将被编码为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​中可用,它将名称(HTML ​<input type="file">​元素的名称)映射到文件列表。每个文件都是​{"filename":...,"content_type":...,"body":...}​格式的字典。​files​对象仅在使用表单包装器(即​multipart/form-data​内容类型)上载文件时存在;如果未使用此格式,则​self.request.body​中可获得原始上传数据。默认情况下,上传的文件在内存中完全缓冲;如果需要处理太大而无法轻松保存在内存中的文件,请参阅stream_request_body类装饰器。

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

由于HTML表单编码的特殊性(例如,单数和复数参数之间的模糊性),Tornado不尝试将表单参数与其他类型的输入统一起来。特别是,我们不解析JSON请求体。希望使用JSON而不是表单编码的应用程序可能会重写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. initialize()使用配置中的初始化参数调用Application。​initialize ​通常应该只保存传递给成员变量的参数;它可能不会产生任何输出或调用方法,例如 send_error
  3. 调用prepare()。这在所有处理程序子类共享的基类中最有用,因为无论使用哪个HTTP方法,都会调用​prepare​。​prepare​可以产生输出;如果它调用finish或​redirect​等等方法,处理将在此停止
  4. 其中一个HTTP方法被调用:​get()​、​post()​、​put()​等等。如果URL正则表达式包含捕获组,则将它们作为参数传递给此方法
  5. 当请求完成时,on_finish()被调用。这通常是在​get()​或另一个 HTTP 方法返回之后。

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

也可以从常规处理程序方法生成错误页面,而不是​write_error​通过调用 set_status、编写响应和返回。在简单返回不方便的情况下,tornado.web.Finish可能会引发特殊异常以终止处理程序而不调用​write_error

对于404错误,使用​default_handler_class应用程序设置。这个处理程序应该覆盖prepare,而不是像​​​get()​​​这样更具体的方法,这样它就可以与任何HTTP方法一起工作。它应该如上所述生成错误页面:通过引发​HTTPError(404)​并覆盖​write_error​,或者调用​self.set_status(404)​并直接在​prepare()​中生成响应。

重定向

在Tornado中,有两种主要的重定向请求的方法:RequestHandler.redirectRedirectHandler

可以在​self.redirect()​方法中使用RequestHandler将用户重定向到其他地方。还有一个可选参数​permanent​,可用于指示重定向被视为永久性的。​permanent​的默认值为​False​,这将生成一个​302 Found​的HTTP响应代码,适用于在成功的​POST​请求后重定向用户。如果​permanent​为​True​,则使用​301 Moved permanually​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/{0}")),
    ])

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

异步处理程序

某些处理程序方法(包括​prepare()​和HTTP的​get()​/​post()​方法等)可能会被重写为协同路由,以使处理程序异步。

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

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