一个Tornado网络应用的结构
Tornado Web应用程序通常由一个或多个RequestHandler
子类,一个将请求路由到处理程序(handlers)的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
的第三方元素被传递进来,它支持initialization参数,这个参数会传递给RequestHandler.initializa
方法。最后,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.render
或RequestHandler.write
等方法来生成响应。 render()
按名称加载模板,并使用给定的参数呈现它。 write()
用于非基于模板的输出;它接受strings,bytes和字典(dicts将被编码为JSON)。
RequestHandler
中的许多方法都设计为在子类中重写,并可以在整个应用程序中使用。通常定义一个BaseHandler
类来重写诸如write_error
和get_current_user
之类的方法,然后把你的BaseHandler代替RequestHandler
作为所有处理器的基类。
处理请求输入
请求处理程序可以使用self.request
访问表示当前请求的对象。有关完整的属性列表,请参阅HTTPServerRequest
的类定义。
HTML表单提交的请求数据将会为你分析好,并且可以在一些方法像get_query_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_arguments
和get_body_arguments
代替单值的方法。
通过表单上传文件可以通过self.request.files
来获得,映射名字(input的name属性)到文件列表。每个文件都是{"filename":…, "content_type":…, "body":…}
格式的字典。files
对象文件仅在使用表单包装器上传文件时才存在(例如multipart/form-data Content-Type
);如果没有使用这种格式,原生的上传数据会存放在self.request.boby
中。默认情况下上传的文件是全部缓存在内存中的;如果你需要处理一些很大的文件,不方便放在内存中,查看stream_request_body
装饰器。
在demos文件夹中(tornado源码中有一个demos文件夹,存放了几个小的例子),file_reciver.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
重写RequestHandler中的方法
除了get()
和post()
方法,在RequestHandler
还有一些其他方法在必要时也可以被子类重写。在每个请求中,都会发生以下一系列的调用:
- 每个请求中都会新建一个
RequestHandler
对象。 - 如果有从
Application
配置中获得初始参数的话initialize()
函数会被调用,initialize
函数应该只保存传递给成员变量的参数;它可能不会产生任何输出或调用类似send_error
一样的方法。 - 调用
prepare
方法。这在一个基类中是最有用的,基类由你的处理器子类所共享,因为不论使用哪种HTTP方法prepare
函数都会被调用。prepare
可能会产生输出;如果它调用了finish
(或者redirect
方法等),进程就会在此结束。 - 某一个HTTP方法被调用:
get()
,post()
,put()
等等。如果URL正则表达式包含了捕捉分组参数(capturing groups),这些参数也会传递到此方法中。 - 当请求结束时,
on_finish
会被调用,对于大多数处理程序,这一步在get()
(或其他方法)return后就会立即执行;对于使用tornado.web.asynchronous
装饰器的处理程序,它发生在调用finish()
之后。
正如在RequestHandler
文档中提到的一样,所有方法都是可以重写的。 一些最常被重写的方法包括:
-
write_error
- 输出html的错误页面。 -
on_connection_close
当客户端断开连接的时候调用;应用可能会选择检测这种情况然后停止更深层的处理,注意,不能保证一个已关闭的连接也能被及时检测到。 请注意,无法保证可以立即检测到已关闭的连接。 -
get_current_user
- 请参阅用户身份验证(User authentication)。 -
get_user_locale
- 为当前用户返回一个locale
对象。 -
set_default_headers
- 可用于在response上设置其他的响应头(例如自定义Server
header)
错误处理
如果处理器抛出一个异常,tornado会调用RequestHandler.write_error
来生成一个错误页面。tornado.web.HTTPError
可以被用来产生一个特定的状态码;其他所有异常都返回500状态码。
默认的错误页面包括一个堆栈路径(debug模式下)另外还有一个错误的线上描述(move brand_model.txt to project)。为了生成一个自己的错误页面,重写RequestHandler.write_error
(可能是在一个由你的所有处理器所共享的基类中)这个方法可以用过write
和render
等方法产生正常的输出。如果错误是由一个异常导致的,一个exc_info
会作为一个关键字参数被传递进来(注意,此异常不保证是sys.exc_info
中当前的异常,因此write_error
必须使用traceback.format_exception
来代替traceback.format_exc
)。
通过调用set_status
,写一个响应或return等方法从常规的处理器方法(而不是write_error
)中产生一个错误页面也是可能的。tornado.web.Finish
这个特殊的异常可能会被抛出以终止处理器,在简单地返回不方便的情况下并不调用write_error
方法。
对于404错误来说,使用default_handler_class``````Application setting
。这个处理器用改重写prepare
方法,而不是更详细的如get()
方法,以便在任何HTTP方法中都能使用。它应该产生一个如上所述的错误页面:或者通过抛出一个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响应码会被使用,在下面这中情况下是有用的:重定向到一个权威的URL来采用一种SEO友好的方式获取页面。
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
发起一个临时重定向,只需要把permanent=False
参数加到RedirectHandler
的初始化参数中。
异步处理器
某些处理程序方法(包括prepare()
和HTTP请求方法get()
、post()
等)可以作为协程重写,以实现异步化。
Tornado还支持使用tornado.web.asynchronous
装饰器那样基于回调的异步处理程序样式,但这种样式已弃用,将在Tornado 6.0中删除。新的应用程序应该使用协程代替旧的写法。
下面是一个简单的使用协程的handler示例:
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")
有关更高级的异步示例,请查看聊天示例应用程序,该应用程序使用长轮询实现AJAX聊天室。 长轮询的用户可能希望在客户端关闭连接后重写on_connection_close
()以进行清理操作(但要注意的是,请参见该方法的docstring)。