2.5、User’s guide (Structure of a

2018-03-29  本文已影响21人  宝宝家的隔壁老王

Structure of a Tornado web application

tornado web 应用通常包含一个或者更多的 RequestHandler 子类,一个 Applications 对象将输入路由到处理对象,一个 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 对象负责全局配置,包括映射请求到处理对象的路由表。

路由表是一组 list(或 tuple) URLSpec 对象,每一个对象包含至少一个正则表达式和一个处理类。

顺序问题:第一个匹配的路由被使用。如果正则表达式包含捕获组,这些组作为路径参数并且将传递给处理对象的 HTTP 方法。

如果一个字典需要作为 URLSpec 的参数传递,他提供初始化参数传递给 RequestHandler.initialize 。最后, URLSpec 可能有一个名字,可以在 RequestHandler.reverse_url 中使用。
在下面例子中,跟路由 / 匹配 MainHandler 并且 /story/ 拼接一个数字会匹配 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.setting 查看完整的列表。

Subclassing RequestHandler

tornado 的 web 应用工作在 RequestHandler 的子类中。处理入口是对应的 HTTP 方法对应的方法,get(), post()。

每一个处理对象可能定义一个或多个方法去处理不同的 HTTP 动作。

如上所述,这些方法将会被路由匹配到的组和参数调用。
在处理对象中,调用 RequestHandler.render 或者 RequestHandler.write 方法产生响应。render()调用给定名字的模板,并使用给定参数渲染。write()用在非模板输出,接受字符串,字节,字典(字典被编码成 JSON 对象)
RequestHandler 中一些方法被设计成在子类覆盖,并且在整个应用中使用。通常情况下会定义一个 BaseHandler 类并且覆盖如 write_error 以及 get_current_user 然后继承所写的 BaseHandler 而不是 RequestHandler 类方法用以做特定的处理。

Handling request input

请求处理类可以通过 self.request 获取当前的请求对象。

通过 HTTPServerRequest 类的定义查看完整的属性列表。
get_query_argument() 和 get_body_qrgument() 方法可以获取 HTML 表单类型的请求数据。
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_qrguments, 反之使用 get_body_qrguments.
文件上传的 form 可通过 self.request.files 获取,匹配了文件列表。

每个文件是一个字典表单 {'filename':'', 'content_type':'', 'body':''} ,只有当文件是以表单包装(i.e. a multipart/form-data Content-Type)时才会存在文件对象。

如果没有使用这种格式,上传的原始数据可以通过 self.request.body 获取。上传文件默认会在内存中缓存,如果您的文件太大以至于无法在内存中缓存,可以移步 stream_request_body 类装饰器。
demo 文件里,file_receiver.py 文件展示了获取上传文件的两种方式。
由于怪异的 HTML 表单编码(如单数和复数参数的歧义),tornado 并不试图统一表单的其他输入类型的参数。

特别是,不会解析 JSON 请求体。

应用程序如果期望使用 JSON 代替 form-encoding,可以重写 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() ,RequestHandler 中其他的一些方法在必要的时候也要被子类重写。

每一个请求的调用过程如下:

1、新的 RequestHandler 对象被创建

2、使用 Application 配置的初始化参数调用 initialize()。初始化通常是将参数传递到成员变量;他将不会有任何输出或者调用类似 send_error 方法。

3、调用 prepare()。这个方法在基子类中是最有用的,可以为任何 HTTP 方法做准备。prepare 可能会有输出,如果他调用 finish 或者 redirect 等,流程到此结束。

4、HTTP 对应的方法被调用:get(), post(), put() 等。如果 URL 正则表达式包含捕获组,他们作为参数传递给该方法。

5、请求完成后,on_finish() 被调用。对于同步处理,在 get()(举例) return 后调用。对于异步处理,在 finish() 后调用。 
所有设计被重写的方法都可在 RequestHandler 文档中找到。最常重写的方法如下:

· write_error 错误页面的 HTML

· on_connection_close 客户端断开连接的调用;应用程序可以在这种情况下做进一步的处理。不能保证一个关闭的连接被探测到。

· get_current_user 阅读 User authentication 文档

· get_user_locale 返回当前用户的地点

· set_default_headers 用来设置额外的响应头 (如自定义服务器头)

Error Handling

如果处理对象提出了一个异常,tornado 会调用 RequestHandler.write_error 生成一个错误的页面。

tornado.web.HTTPError 可用来生成一个指定的状态代码,其他的异常返回 500 状态码。
默认的错误页面在 debug 模式下包含堆栈跟踪信息以及一条错误描述信息 (e.g. '500:Internal Server Error')。产生一个自定义的错误页面,重写 RequestHandler.write_error(在基类中重写),这个方法可以通过 write 和 render 进行输出。如果错误是由异常导致的,一个三重的 exc_info 被作为关键字参数传递 (注意,异常不能保证是 sys,exc_info 中的当前异常,所以 write_error 必须使用 traceback.format_exception 代替 traceback.format_exc)
也可以在常规处理方法中通过 set_status, writing 响应 然后 return 生成错误页面来替代 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 值是 True,使用 301 Moved Permanently HTTP 响应码,在页面 URL 的重定向使用 SEO-friendly 方式是非常有用的。
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

tornado 的处理默认是同步的,当 get() 和 post() 方法 return 时,认为请求完成了并且响应也发送了。当一个处理在运行时,所有其他的请求被阻塞了,任何运行耗时的处理应该是异步的,所以可以用非阻塞的方式调用缓慢的操作。

更详细的讨论在 Asynchronous 和 non-Blocking I/O。这部分是 RequestHandler 子类的异步技术的细节。
最简单的异步处理是使用 coroutine 去装饰或者 async def。

这允许您使用 yield 或 await 关键字去执行非阻塞 I/O 并且知道协程 return 后请求才会有响应。查阅 Coroutines 获取更详细细节。
在某些情况下,协程可能比回调的方式更不方便,这种情况下使用 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 client 最终调用 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() 在客户端连接关闭后做清理(但是方法的文档有警告). 

上一篇: 2.4、User’s guide (Coroutines)
下一篇: 2.6、User’s guide (Authentication and security)

上一篇 下一篇

猜你喜欢

热点阅读