一个web app包含一个或多个RequestHandler的子类,application则把进入的请求路由到handler中,并使用一个main函数来启动服务器。
application
负责全局的配置,包含路由表。
路由表
是一个URLSpec(tuple)的列表,每一个包含正则表达式和handler。是按照顺序匹配的,和django的类似。可以捕获arg,可以在url中给初始值(会在RequestHandler的initialize中使用),可以给个name在reverse_url中可以使用。
handler
和django类视图类似,通过构造get post方法处理。
在handler中,通过render和write来产生响应,render通过名称装载一个模板,并填入响应的参数;write则产生不需要模板的输出(可以输出str bytes dict(会自动被转换为json))。
在handler中可以通过self.request来获得当前的请求,并提供了get_query_argument和get_body_argument以及get_query_arguments和get_body_arguments。(查询字符串和表单名字查询)
文件可以通过self.request.files来获得,是文件的列表,每个文件则是:
{"filename":..., "content_type":..., "body":...}
当然,上传时需要加multipart/form-data Content-Type,也可以通过上传的原生数据self.request.body来处理。大文件的处理需另外看stream_request_body装饰器。
如果发送的是json数据,可以在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
这样在处理函数中即可获得request.json_args
处理流程
1、每个请求产生一个handler的实例
2、调用initialize来初始化从application中获得配置项
3、调用prepare,可以产生结果,在其中调用finish和redirect则处理结束
4、调用对应的http方法
5、处理结束则调用on_finish,同步handler是在http方法调用后立即调用,异步handler则是在finish调用后调用
另可重载的函数
[write_error] 错误时输出
[on_connection_close] 客户端连接关闭时调用 但是并不能保证可以及时发现连接断开
[get_current_user]
[get_user_locale]
[set_default_headers] 响应的HTTP头部信息
错误处理
在handler产生异常时,会调用write_error来产生响应的错误页面。抛出Finish异常则会跳过错误处理而终止请求。
当然没必要说非得使用write_error,完全可以使用正常的返回方式,只要设置错误响应头部即可,通过self.set_status(400)。
可在app的设置中放default_handler_class。
重定向
在普通handler中调用redirect或建立一个RedirectHandler。
异步
默认的handler是同步的,在get post等http方法返回时,该请求视为结束了,并发送了响应。所有的其他请求都会被阻塞,在这个请求被处理的时候。
使用协程来使得handler异步化最简单,通过yield来实行非阻塞IO,直到协程返回没有响应产生。有时相比回调模式不方便,但是在回调中需要显示调用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") # 通过yield来产生非阻塞IO
json = tornado.escape.json_decode(response.body)
self.write("Fetched " + str(len(json["entries"])) + " entries "
"from the FriendFeed API") # 直接就返回了
也可以使用asynchronous:
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() # 需要显示调用这个finish才能真正的响应给客户端
例子
# coding: utf-8
import logging
import tornado.escape
import tornado.ioloop
import tornado.web
import os.path
import uuid
from tornado.concurrent import Future
from tornado import gen
from tornado.options import define, options, parse_command_line
define("port", default=8888, help="run on the given port", type=int)
define("debug", default=False, help="run in debug mode")
class MessageBuffer(object):
def __init__(self):
self.waiters = set()
self.cache = []
self.cache_size = 200
def wait_for_messages(self, cursor=None):
# Construct a Future to return to our caller. This allows
# wait_for_messages to be yielded from a coroutine even though
# it is not a coroutine itself. We will set the result of the
# Future when results are available.
result_future = Future()
if cursor:
new_count = 0
for msg in reversed(self.cache):
if msg["id"] == cursor:
break
new_count += 1
if new_count:
result_future.set_result(self.cache[-new_count:])
return result_future
self.waiters.add(result_future)
return result_future
def cancel_wait(self, future):
self.waiters.remove(future)
# Set an empty result to unblock any coroutines waiting.
future.set_result([])
def new_messages(self, messages):
logging.info("Sending new message to %r listeners", len(self.waiters))
for future in self.waiters:
future.set_result(messages)
self.waiters = set()
self.cache.extend(messages)
if len(self.cache) > self.cache_size:
self.cache = self.cache[-self.cache_size:]
global_message_buffer = MessageBuffer()
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.render("index.html", messages=global_message_buffer.cache)
class MessageNewHandler(tornado.web.RequestHandler):
def post(self):
message = {
"id": str(uuid.uuid4()),
"body": self.get_argument("body"),
}
# to_basestring is necessary for Python 3's json encoder,
# which doesn't accept byte strings.
message["html"] = tornado.escape.to_basestring(
self.render_string("message.html", message=message))
if self.get_argument("next", None):
self.redirect(self.get_argument("next"))
else:
self.write(message)
global_message_buffer.new_messages([message])
class MessageUpdatesHandler(tornado.web.RequestHandler):
@gen.coroutine
def post(self):
cursor = self.get_argument("cursor", None)
# Save the future returned by wait_for_messages so we can cancel
# it in wait_for_messages
self.future = global_message_buffer.wait_for_messages(cursor=cursor)
messages = yield self.future
if self.request.connection.stream.closed():
return
self.write(dict(messages=messages))
def on_connection_close(self):
global_message_buffer.cancel_wait(self.future)
def main():
parse_command_line()
app = tornado.web.Application(
[
(r"/", MainHandler),
(r"/a/message/new", MessageNewHandler),
(r"/a/message/updates", MessageUpdatesHandler),
],
cookie_secret="__TODO:_GENERATE_YOUR_OWN_RANDOM_VALUE_HERE__",
template_path=os.path.join(os.path.dirname(__file__), "templates"),
static_path=os.path.join(os.path.dirname(__file__), "static"),
xsrf_cookies=True,
debug=options.debug,
)
app.listen(options.port)
tornado.ioloop.IOLoop.current().start()
if __name__ == "__main__":
main()