web app

一个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()
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容