.. currentmodule:: tornado.web

.. testsetup::

import tornado.web

Tornado web应用的结构

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

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

.. testcode::

  1. import tornado.ioloop
  2. import tornado.web
  3. class MainHandler(tornado.web.RequestHandler):
  4. def get(self):
  5. self.write("Hello, world")
  6. def make_app():
  7. return tornado.web.Application([
  8. (r"/", MainHandler),
  9. ])
  10. if __name__ == "__main__":
  11. app = make_app()
  12. app.listen(8888)
  13. tornado.ioloop.IOLoop.current().start()

.. testoutput:: :hide:

Application 对象

  1. `.Application` 对象是负责全局配置的, 包括映射请求转发给处理程序的路由表.
  2. 路由表是 `.URLSpec` 对象(或元组)的列表, 其中每个都包含(至少)一个正则
  3. 表达式和一个处理类. 顺序问题; 第一个匹配的规则会被使用. 如果正则表达
  4. 式包含捕获组, 这些组会被作为 *路径参数* 传递给处理函数的HTTP方法.
  5. 如果一个字典作为 `.URLSpec` 的第三个参数被传递, 它会作为 *初始参数*
  6. 传递给 `.RequestHandler.initialize`. 最后 `.URLSpec` 可能有一个名字
  7. , 这将允许它被 `.RequestHandler.reverse_url` 使用.
  8. 例如, 在这个片段中根URL ``/`` 映射到了
  9. ``MainHandler`` , ``/story/`` 后跟着一个数字这种形式的URL被映射到了
  10. ``StoryHandler``. 这个数字被传递(作为字符串)给
  11. ``StoryHandler.get``.
  12. ::
  13. class MainHandler(RequestHandler):
  14. def get(self):
  15. self.write('<a href="%s">link to story 1</a>' %
  16. self.reverse_url("story", "1"))
  17. class StoryHandler(RequestHandler):
  18. def initialize(self, db):
  19. self.db = db
  20. def get(self, story_id):
  21. self.write("this is story %s" % story_id)
  22. app = Application([
  23. url(r"/", MainHandler),
  24. url(r"/story/([0-9]+)", StoryHandler, dict(db=db), name="story")
  25. ])
  26. `.Application` 构造函数有很多关键字参数可以用于自定义应用程序的行为
  27. 和使用某些特性(或者功能); 完整列表请查看 `.Application.settings` .
  28. ``RequestHandler`` 子类
  29. ~~~~

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

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

.RequestHandler 中的很多方法的设计是为了在子类中复写和在整个应用 中使用. 常用的方法是定义一个 BaseHandler 类, 复写一些方法例如 ~.RequestHandler.write_error~.RequestHandler.get_current_user 然后子类继承使用你自己的 BaseHandler 而不是 .RequestHandler 在你所有具体的处理程序中.

处理输入请求

  1. 处理请求的程序(request handler)可以使用 ``self.request`` 访问代表当
  2. 前请求的对象. 通过
  3. `~tornado.httputil.HTTPServerRequest` 的类定义查看完整的属性列表.
  4. 使用HTML表单格式请求的数据会被解析并且可以在一些方法中使用, 例如
  5. `~.RequestHandler.get_query_argument`
  6. `~.RequestHandler.get_body_argument`.
  7. .. testcode::
  8. class MyFormHandler(tornado.web.RequestHandler):
  9. def get(self):
  10. self.write('<html><body><form action="/myform" method="POST">'
  11. '<input type="text" name="message">'
  12. '<input type="submit" value="Submit">'
  13. '</form></body></html>')
  14. def post(self):
  15. self.set_header("Content-Type", "text/plain")
  16. self.write("You wrote " + self.get_body_argument("message"))
  17. .. testoutput::
  18. :hide:
  19. 由于HTLM表单编码不确定一个标签的参数是单一值还是一个列表,
  20. `.RequestHandler` 有明确的方法来允许应用程序表明是否它期望接收一个列表.
  21. 对于列表, 使用
  22. `~.RequestHandler.get_query_arguments`
  23. `~.RequestHandler.get_body_arguments` 而不是它们的单数形式.
  24. 通过一个表单上传的文件可以使用 ``self.request.files``,
  25. 它遍历名字(HTML 标签 ``<input type="file">`` name)到一个文件列表.
  26. 每个文件都是一个字典的形式
  27. ``{"filename":..., "content_type":..., "body":...}``. ``files``
  28. 对象是当前唯一的如果文件上传是通过一个表单包装
  29. (i.e. a ``multipart/form-data`` Content-Type); 如果没用这种格式,
  30. 原生上传的数据可以调用 ``self.request.body`` 使用.
  31. 默认上传的文件是完全缓存在内存中的; 如果你需要处理占用内存太大的文件
  32. 可以看看 `.stream_request_body` 类装饰器.
  33. 由于HTML表单编码格式的怪异 (e.g. 在单数和复数参数的含糊不清), Tornado
  34. 不会试图统一表单参数和其他输入类型的参数. 特别是, 我们不解析JSON请求体.
  35. 应用程序希望使用JSON代替表单编码可以复写 `~.RequestHandler.prepare`
  36. 来解析它们的请求::
  37. def prepare(self):
  38. if self.request.headers["Content-Type"].startswith("application/json"):
  39. self.json_args = json.loads(self.request.body)
  40. else:
  41. self.json_args = None
  42. 复写RequestHandler的方法
  43. ~~~~~~~~~~~

除了 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 - 参考 :ref:user-authentication
  • ~.RequestHandler.get_user_locale - 返回 .Locale 对象给当前 用户使用
  • ~.RequestHandler.set_default_headers - 可以被用来设置额外的响应 头(例如自定义的 Server 头)

错误处理

  1. 如果一个处理程序抛出一个异常, Tornado会调用
  2. `.RequestHandler.write_error` 来生成一个错误页.
  3. `tornado.web.HTTPError` 可以被用来生成一个指定的状态码; 所有其他的异常
  4. 都会返回一个500状态.
  5. 默认的错误页面包含一个debug模式下的调用栈和另外一行错误描述
  6. (e.g. "500: Internal Server Error"). 为了创建自定义的错误页面, 复写
  7. `RequestHandler.write_error` (可能在一个所有处理程序共享的一个基类里面).
  8. 这个方法可能产生输出通常通过一些方法, 例如 `~RequestHandler.write`
  9. `~RequestHandler.render`. 如果错误是由异常引起的, 一个 ``exc_info``
  10. 将作为一个关键字参数传递(注意这个异常不能保证是 `sys.exc_info` 当前的
  11. 异常, 所以 ``write_error`` 必须使用 e.g. `traceback.format_exception` 代替
  12. `traceback.format_exc`).
  13. 也可以在常规的处理方法中调用 `~.RequestHandler.set_status` 代替
  14. ``write_error`` 返回一个(自定义)响应来生成一个错误页面. 特殊的例外
  15. `tornado.web.Finish` 在直接返回不方便的情况下能够在不调用 ``write_error``
  16. 前结束处理程序.
  17. 对于404错误, 使用 ``default_handler_class`` `Application setting
  18. <.Application.settings>`. 这个处理程序会复写
  19. `~.RequestHandler.prepare` 而不是一个更具体的方法, 例如 ``get()``
  20. 所以它可以在任何HTTP方法下工作. 它应该会产生如上所说的错误页面: 要么raise
  21. 一个 ``HTTPError(404)`` 要么复写 ``write_error``, 或者调用
  22. ``self.set_status(404)`` 或者在 ``prepare()`` 中直接生成响应.
  23. 重定向
  24. ~~~~~~~~~~~
  25. 这里有两种主要的方式让你可以在Tornado中重定向请求:
  26. `.RequestHandler.redirect` 和使用 `.RedirectHandler`.
  27. 你可以在一个 `.RequestHandler` 的方法中使用 ``self.redirect()`` 把用
  28. 户重定向到其他地方. 还有一个可选参数 ``permanent`` 你可以使用它来表明这个
  29. 重定向被认为是永久的. ``permanent`` 的默认值是 ``False``, 这会生成一个
  30. ``302 Found`` HTTP响应状态码, 适合类似在用户的 ``POST`` 请求成功后的重定向.
  31. 如果 ``permanent`` true, 会使用 ``301 Moved
  32. Permanently`` HTTP响应, 更适合
  33. e.g. SEO友好的方法中把一个页面重定向到一个权威的URL.
  34. `.RedirectHandler` 让你直接在你 `.Application` 路由表中配置. 例如, 配置一个
  35. 静态重定向::
  36. app = tornado.web.Application([
  37. url(r"/app", tornado.web.RedirectHandler,
  38. dict(url="http://itunes.apple.com/my-app-id")),
  39. ])
  40. `.RedirectHandler` 也支持正则表达式替换. 下面的规则重定向所有以 ``/pictures/``
  41. 开始的请求用 ``/photos/`` 前缀代替::
  42. app = tornado.web.Application([
  43. url(r"/photos/(.*)", MyPhotoHandler),
  44. url(r"/pictures/(.*)", tornado.web.RedirectHandler,
  45. dict(url=r"/photos/\1")),
  46. ])
  47. 不像 `.RequestHandler.redirect`, `.RedirectHandler` 默认使用永久重定向.
  48. 这是因为路由表在运行时不会改变, 而且被认为是永久的.
  49. 当在处理程序中发现重定向的时候, 可能是其他可能改变的逻辑的结果.
  50. `.RedirectHandler` 发送临时重定向, 需要添加 ``permanent=False``
  51. `.RedirectHandler` 的初始化参数.
  52. 异步处理
  53. ~~~~~~~

Tornado默认会同步处理: 当 get()/post() 方法返回, 请求被认为结束 并且返回响应. 因为当一个处理程序正在运行的时候其他所有请求都被阻塞, 任何需要长时间运行的处理都应该是异步的, 这样它就可以在非阻塞的方式中调用 它的慢操作了. 这个话题更详细的内容包含在 :doc:async 中; 这部分是关于在 .RequestHandler 子类中的异步技术的细节.

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

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

这里是一个使用Tornado’s 内置的 .AsyncHTTPClient 调用FriendFeed API的例 子:

.. testcode::

  1. class MainHandler(tornado.web.RequestHandler):
  2. @tornado.web.asynchronous
  3. def get(self):
  4. http = tornado.httpclient.AsyncHTTPClient()
  5. http.fetch("http://friendfeed-api.com/v2/feed/bret",
  6. callback=self.on_response)
  7. def on_response(self, response):
  8. if response.error: raise tornado.web.HTTPError(500)
  9. json = tornado.escape.json_decode(response.body)
  10. self.write("Fetched " + str(len(json["entries"])) + " entries "
  11. "from the FriendFeed API")
  12. self.finish()

.. testoutput:: :hide:

get() 返回, 请求还没有完成. 当HTTP客户端最终调用 on_response(), 这个请求仍然是开放的, 通过调用 self.finish(), 响应最终刷到客户端.

为了方便对比, 这里有一个使用协程的相同的例子:

.. testcode::

  1. class MainHandler(tornado.web.RequestHandler):
  2. @tornado.gen.coroutine
  3. def get(self):
  4. http = tornado.httpclient.AsyncHTTPClient()
  5. response = yield http.fetch("http://friendfeed-api.com/v2/feed/bret")
  6. json = tornado.escape.json_decode(response.body)
  7. self.write("Fetched " + str(len(json["entries"])) + " entries "
  8. "from the FriendFeed API")

.. testoutput:: :hide:

更多高级异步的示例, 请看 chat example application <https://github.com/tornadoweb/tornado/tree/stable/demos/chat>, 实现了一个 使用 长轮询(long polling) <http://en.wikipedia.org/wiki/Push_technology#Long_polling> 的AJAX聊天室. 长轮询的可能想要覆盖 on_connection_close() 来在客户端关闭连接之后进行清 理(注意看方法的文档来查看警告).