Tornado Web应用程序的结构
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()
Application对象
该Application对象负责全局配置,包括将请求映射到处理程序的路由表。
路由表是URLSpec
对象(或元组)的列表,每个对象(至少)包含一个正则表达式和一个处理程序类。订单事项;使用第一个匹配规则。如果正则表达式包含捕获组,这些组是路径参数,并将传递给处理程序的 HTTP 方法。如果字典作为 的第三个元素传递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的完整列表。
RequestHandler子类
Tornado Web 应用程序的大部分工作都是在RequestHandler. 处理程序子类的主要入口点是一个以正在处理的 HTTP 方法命名的方法:get()
、 post()
等。每个处理程序可以定义一个或多个这些方法来处理不同的 HTTP 操作。如上所述,将使用与匹配的路由规则的捕获组对应的参数调用这些方法。
在处理程序中,调用RequestHandler.render或RequestHandler.write等方法以生成响应。render()
按名称加载Template,并用给定的参数呈现它。write()
用于非基于模板的输出;它接受字符串、字节和字典(dict将被编码为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
中可用,它将名称(HTML <input type="file">
元素的名称)映射到文件列表。每个文件都是{"filename":...,"content_type":...,"body":...}
格式的字典。files
对象仅在使用表单包装器(即multipart/form-data
内容类型)上载文件时存在;如果未使用此格式,则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
重写RequestHandler方法
除了get()
/post()
等,RequestHandler中的某些其他方法被设计为在必要时被子类重写。每次请求时,都会进行以下顺序的调用:
- 每个请求都会创建一个新的RequestHandler对象
- initialize()使用配置中的初始化参数调用Application。
initialize
通常应该只保存传递给成员变量的参数;它可能不会产生任何输出或调用方法,例如 send_error - 调用prepare()。这在所有处理程序子类共享的基类中最有用,因为无论使用哪个HTTP方法,都会调用
prepare
。prepare
可以产生输出;如果它调用finish或redirect
等等方法,处理将在此停止 - 其中一个HTTP方法被调用:
get()
、post()
、put()
等等。如果URL正则表达式包含捕获组,则将它们作为参数传递给此方法 - 当请求完成时,on_finish()被调用。这通常是在
get()
或另一个 HTTP 方法返回之后。
RequestHandler文件中记录了所有设计为覆盖的方法。一些最常用的重写方法包括:
- write_error- 输出HTML以在错误页面上使用
- on_connection_close- 当客户端断开连接时调用;应用程序可以选择检测这种情况并停止进一步处理。请注意,不能保证可以立即检测到关闭的连接
- get_current_user- 请参阅用户身份验证
- get_user_locale- 返回Locale当前用户使用的对象
- set_default_headers- 可用于在响应上设置附加表头(例如自定义
Server
表头)
错误处理
如果处理程序引发异常,Tornado将调用RequestHandler.write_error生成错误页面。tornado.web.HTTPError可用于生成指定的状态代码;所有其他异常都返回500状态。
默认错误页面包括调试模式下的堆栈跟踪和错误的单行描述(例如“500:内部服务器错误”)。要生成自定义错误页,请重写RequestHandler.write_error(可能在所有处理程序共享的基类中)。这种方法通常可以通过write和render等方法产生输出。如果错误是由异常引起的,则exc_info
将作为关键字参数传递(请注意,此异常不能保证是sys.exc_info中的当前异常,因此write_error
必须使用例如traceback.format_exception而不是traceback.format_exc)
也可以从常规处理程序方法生成错误页面,而不是write_error
通过调用 set_status、编写响应和返回。在简单返回不方便的情况下,tornado.web.Finish可能会引发特殊异常以终止处理程序而不调用write_error
对于404错误,使用default_handler_class
应用程序设置。这个处理程序应该覆盖prepare,而不是像get()
这样更具体的方法,这样它就可以与任何HTTP方法一起工作。它应该如上所述生成错误页面:通过引发HTTPError(404)
并覆盖write_error
,或者调用self.set_status(404)
并直接在prepare()
中生成响应。
重定向
在Tornado中,有两种主要的重定向请求的方法:RequestHandler.redirect和RedirectHandler
可以在self.redirect()
方法中使用RequestHandler将用户重定向到其他地方。还有一个可选参数permanent
,可用于指示重定向被视为永久性的。permanent
的默认值为False
,这将生成一个302 Found
的HTTP响应代码,适用于在成功的POST
请求后重定向用户。如果permanent
为True
,则使用301 Moved permanually
HTTP响应代码,这有助于以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/{0}")),
])
与RequestHandler.redirect不同,RedirectHandler默认使用永久重定向。这是因为路由表在运行时不会更改,并且被认为是永久的,而在处理程序中找到的重定向可能是其他可能更改的逻辑的结果。要使用RedirectHandler发送临时重定向,请在RedirectHandler初始化参数中添加permanent=False
异步处理程序
某些处理程序方法(包括prepare()
和HTTP的get()
/post()
方法等)可能会被重写为协同路由,以使处理程序异步。
例如,下面是一个使用协同程序的简单处理程序:
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")