Structure of a Tornado web application

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

The Application object

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

路由表是URLSpec对象(或元组)的列表, URLSpec对象都(至少)包含一个正则表达式和一个处理程序类. 订单事项; 使用第一个匹配规则. 如果正则表达式包含捕获组,则这些组是路径参数 ,并将传递给处理程序的HTTP方法. 如果将字典作为URLSpec的第三个元素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 .

Subclassing RequestHandler

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

在处理程序中,调用诸如RequestHandler.renderRequestHandler.write以产生响应. render()按名称加载Template ,并使用给定的参数进行渲染. write()用于基于非模板的输出; 它接受字符串,字节和字典(字典将被编码为JSON).

RequestHandler中的许多方法被设计为在子类中重写,并在整个应用程序中使用. 定义一个BaseHandler类来覆盖诸如write_errorget_current_user类的方法是很常见的,然后为所有特定的处理程序创建您自己的BaseHandler而不是RequestHandler子类.

Handling request input

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

HTML表单使用的格式的请求数据将为您解析,并可以在诸如get_query_argumentget_body_argument类的方法中get_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":...}形式的字典. 仅当文件是使用表单包装程序(即multipart/form-data Content-Type)上传的时, files对象才存在. 如果未使用此格式,则原始上传的数据可在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

Overriding RequestHandler methods

除了get() / post() / etc外, RequestHandler中的某些其他方法还设计为在必要时被子类覆盖. 在每个请求上,都会按以下顺序进行呼叫:

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

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

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

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

  5. 请求完成后,将调用on_finish() . 通常是在get()或另一个HTTP方法返回之后.

All methods designed to be overridden are noted as such in the RequestHandler documentation. Some of the most commonly overridden methods include:

Error Handling

如果处理程序引发异常,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 ).

It is also possible to generate an error page from regular handler methods instead of write_error by calling set_status, writing a response, and returning. The special exception tornado.web.Finish may be raised to terminate the handler without calling write_error in situations where simply returning is not convenient.

对于404错误,请使用default_handler_class Application setting . 该处理程序应该重写prepare而不是像get()这样的更具体的方法,因此它可以与任何HTTP方法一起使用. 它应该如上所述产生其错误页面:通过引发HTTPError(404)并重写write_error ,或调用self.set_status(404)并直接在prepare()产生响应.

Redirection

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

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

RedirectHandler lets you configure redirects directly in your Application routing table. For example, to configure a single static redirect:

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() / etc)可以作为协程重写,以使处理程序异步.

例如,以下是使用协程的简单处理程序:

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

对于更高级的异步示例,请查看chat示例应用程序 ,该应用程序使用long polling实现AJAX聊天室. 长时间轮询的用户可能希望在客户端关闭连接后重写on_connection_close()进行清理(但请注意该方法的文档字符串以进行警告).