Tornado 4.3文档翻译: 用户指南-web应用的结构

译者说

Tornado 4.3于2015年11月6日发布,该版本正式支持Python3.5async/await关键字,而且用旧版本CPython编译Tornado一样可使用这两个关键字,这无疑是一种进步。其次,这是最后一个支持Python2.6Python3.2的版本了,在后续的版本了会移除对它们的兼容。如今网络上尚未Tornado4.3的中文文档,因此为了让更多的朋友能接触并学习到它,我开始了这个翻译项目,但愿感兴趣的小伙伴能够一块儿参与翻译,项目地址是tornado-zh on Github,翻译好的文档在Read the Docs上直接能够看到。欢迎Issues or PR。html

Tornado web应用的结构

一般一个Tornado web应用包括一个或者多个RequestHandler 子类,一个能够将收到的请求路由到对应handler的Application 对象,和一个启动服务的 main() 函数.python

一个最小的"hello world"例子就像下面这样:git

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对象是负责全局配置的,包括映射请求转发给处理程序的路由表.github

路由表是URLSpec对象(或元组)的列表, 其中每一个都包含(至少)一个正则表达式和一个处理类. 顺序问题; 第一个匹配的规则会被使用. 若是正则表达式包含捕获组, 这些组会被做为 路径参数 传递给处理函数的HTTP方法.若是一个字典做为 URLSpec 的第三个参数被传递, 它会做为 初始参数传递给 RequestHandler.initialize. 最后 URLSpec 可能有一个名字(name), 这将容许它被 RequestHandler.reverse_url 使用.web

例如, 在这个片断中根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 .json

RequestHandler 子类

Tornado web 应用程序的大部分工做是在RequestHandler子类下完成的.处理子类的主入口点是一个命名为处理HTTP方法的函数: get(),post(), 等等. 每一个处理程序能够定义一个或者多个这种方法来处理不一样的HTTP动做. 如上所述, 这些方法将被匹配路由规则的捕获组对应的参数调用.api

在处理程序中, 调用方法如RequestHandler.render 或者RequestHandler.write 产生一个响应. render() 经过名字加载一个Template 并使用给定的参数渲染它. write() 被用于非模板基础的输出; 它接受字符串, 字节, 和字典(字典会被编码成JSON).浏览器

RequestHandler 中的不少方法的设计是为了在子类中复写和在整个应用中使用. 经常使用的方法是定义一个 BaseHandler 类, 复写一些方法例如RequestHandler.write_errorRequestHandler.get_current_user而后子类继承使用你本身的 BaseHandler 而不是RequestHandler在你全部具体的处理程序中.缓存

处理输入请求

处理请求的程序(request handler)可使用 self.request 访问表明当前请求的对象. 经过tornado.httputil.HTTPServerRequest 的类定义查看完整的属性列表.

使用HTML表单格式请求的数据会被解析而且能够在一些方法中使用, 例如RequestHandler.get_query_argumentRequestHandler.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"))

因为HTLM表单编码不肯定一个标签的参数是单一值仍是一个列表,RequestHandler 有明确的方法来容许应用程序代表是否它指望接收一个列表.对于列表, 使用RequestHandler.get_query_argumentsRequestHandler.get_body_arguments 而不是它们的单数形式.

经过一个表单上传的文件可使用 self.request.files,它遍历名字(HTML 标签 <input type="file"> 的name)到一个文件列表.每一个文件都是一个字典的形式{"filename":..., "content_type":..., "body":...}. files对象是当前惟一的若是文件上传是经过一个表单包装(i.e. a multipart/form-data Content-Type); 若是没用这种格式,原生上传的数据能够调用 self.request.body 使用.默认上传的文件是彻底缓存在内存中的; 若是你须要处理占用内存太大的文件能够看看 stream_request_body 类装饰器.

因为HTML表单编码格式的怪异 (e.g. 在单数和复数参数的含糊不清), Tornado不会试图统一表单参数和其余输入类型的参数. 特别是, 咱们不解析JSON请求体.应用程序但愿使用JSON代替表单编码能够复写 RequestHandler.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()/等, 在 .RequestHandler 中的某些其余方法被设计成了在必要的时候让子类重写. 在每一个请求中, 会发生下面的调用序列:

  1. 在每次请求时生成一个新的 RequestHandler 对象

  2. RequestHandler.initialize()Application 配置中的初始化参数被调用. initialize 一般应该只保存成员变量传递的参数; 它不可能产生任何输出或者调用方法, 例如RequestHandler.send_error.

  3. RequestHandler.prepare() 被调用. 这在你全部处理子类共享的基类中是最有用的, 不管是使用哪一种HTTP方法, prepare 都会被调用.prepare 可能会产生输出; 若是它调用 RequestHandler.finish(或者 redirect, 等), 处理会在这里结束.

  4. 其中一种HTTP方法被调用: get(), post(), put(),等. 若是URL的正则表达式包含捕获组, 它们会被做为参数传递给这个方法.

  5. 当请求结束, RequestHandler.on_finish() 方法被调用. 对于同步处理程序会在 get() (等)后当即返回; 对于异步处理程序,会在调用RequestHandler.finish() 后返回.

全部这样设计被用来复写的方法被记录在了RequestHandler的文档中.其中最经常使用的一些被复写的方法包括:

  • RequestHandler.write_error - 输出对错误页面使用的HTML.

  • RequestHandler.on_connection_close - 当客户端断开时被调用;应用程序能够检测这种状况,并中断后续处理. 注意这不能保证一个关闭的链接及时被发现.

  • RequestHandler.get_current_user - 参考 user-authentication

  • RequestHandler.get_user_locale - 返回 .Locale 对象给当前
    用户使用

  • RequestHandler.set_default_headers - 能够被用来设置额外的响应
    头(例如自定义的 Server 头)

错误处理

若是一个处理程序抛出一个异常, Tornado会调用RequestHandler.write_error 来生成一个错误页.tornado.web.HTTPError 能够被用来生成一个指定的状态码; 全部其余的异常都会返回一个500状态.

默认的错误页面包含一个debug模式下的调用栈和另一行错误描述(e.g. "500: Internal Server Error"). 为了建立自定义的错误页面, 复写RequestHandler.write_error (可能在一个全部处理程序共享的一个基类里面).这个方法可能产生输出一般经过一些方法, 例如 RequestHandler.writeRequestHandler.render. 若是错误是由异常引发的, 一个 exc_info 将做为一个关键字参数传递(注意这个异常不能保证是 sys.exc_info 当前的异常, 因此 write_error 必须使用 e.g. traceback.format_exception 代替traceback.format_exc).

也能够在常规的处理方法中调用 RequestHandler.set_status 代替write_error 返回一个(自定义)响应来生成一个错误页面. 特殊的例外tornado.web.Finish 在直接返回不方便的状况下可以在不调用 write_error前结束处理程序.

对于404错误, 使用 default_handler_class Application setting. 这个处理程序会复写RequestHandler.prepare 而不是一个更具体的方法, 例如 get()因此它能够在任何HTTP方法下工做. 它应该会产生如上所说的错误页面: 要么raise一个 HTTPError(404) 要么复写 write_error, 或者调用self.set_status(404) 或者在 prepare() 中直接生成响应.

重定向

这里有两种主要的方式让你能够在Tornado中重定向请求:RequestHandler.redirect 和使用 RedirectHandler.

你能够在一个 RequestHandler 的方法中使用 self.redirect() 把用户重定向到其余地方. 还有一个可选参数 permanent 你可使用它来代表这个重定向被认为是永久的. permanent 的默认值是 False, 这会生成一个302 Found HTTP响应状态码, 适合相似在用户的 POST 请求成功后的重定向.若是 permanent 是true, 会使用 301 Moved Permanently HTTP响应, 更适合e.g. 在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() 方法返回, 请求被认为结束而且返回响应. 由于当一个处理程序正在运行的时候其余全部请求都被阻塞,任何须要长时间运行的处理都应该是异步的, 这样它就能够在非阻塞的方式中调用它的慢操做了. 这个话题更详细的内容包含在async 中; 这部分是关于在 RequestHandler 子类中的异步技术的细节.

使用 coroutine 装饰器是作异步最简单的方式. 这容许你使用 yield 关键字执行非阻塞I/O, 而且直到协程返回才发送响应. 查看 coroutines了解更多细节.

在某些状况下, 协程不如回调为主的风格方便, 在这种状况下tornado.web.asynchronous 装饰器能够用来代替. 当使用这个装饰器的时候,响应不会自动发送; 而请求将一直保持开放直到callback调用RequestHandler.finish. 这须要应用程序确保这个方法被调用或者其余用户的浏览器简单的挂起.

这里是一个使用Tornado's 内置的 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")

更多高级异步的示例, 请看chat example application, 实现了一个使用 长轮询(long polling)的AJAX聊天室.使用长轮询的用户可能想要覆盖 on_connection_close() 来在客户端关闭链接以后进行清理(注意看方法的文档来查看警告).

相关文章
相关标签/搜索