对 Tornado 路由进行魔改

本文将试图实现一个装饰器形式的 Tornado 路由

根据蠢作者的浅薄经验,路由配置一般分为两种形式:一种是集中配置,比如在一个文件中写出 Web 应用的所有路由,这样比较直观;另一种则是分散配置,将路由分散到各个模块中去,这样使得路由和对应的处理代码紧凑到一起便于模块化开发

Tornado 便是集中配置的典型,在 Tornado 中我们是通过将路由与对应的 Handler 组成一个个元组作为 application 的初始化参数

import tornado.ioloop  
import tornado.web


class MainHandler(tornado.web.RequestHandler):

    def get(self):
        self.write('Hello World')

if __name__ == '__main__':  
    app = tornado.web.Application([
        (r'/', MainHandler),
    ])

    app.listen(8888)
    tornado.ioloop.IOLoop.current().start()

而轻量级框架 Flask 则是采用分散式配置(利用Blueprint)

# 目录结构
# .
# ├── app
# │   ├── api
# │   │   ├── __init__.py
# │   │   └── user.py
# │   └── __init__.py
# └── main.py

# main.py

from app import app

if __name__ == '__main__':  
    app.run()

# app/__init__.py

from flask import Flask  
from .api import api as api_blueprint

app = Flask(__name__)  
app.register_blueprint(api_blueprint)

# app/api/__init__.py

from flask import Blueprint

api = Blueprint('api', __name__)

from . import user

# app/api/user.py

from . import api

@api.route('/api')
def api_handler():  
    return 'api'

那下面进入正题,对 Tornado 的集中式路由配置进行魔改,首先增加装饰器形式的路由

import tornado.ioloop  
import tornado.web

app = tornado.web.Application()

class route(object):

    def __init__(self, url):
        self.url = url

    def __call__(self, cls):
        app.add_handlers(
            r'.*$', (tornado.web.url(self.url, cls, name=cls.__name__),)
        )
        return cls

@route(r'/')
class MainHandler(tornado.web.RequestHandler):

    def get(self):
        self.write('Hello World')

if __name__ == '__main__':  
    app.listen(8888)
    tornado.ioloop.IOLoop.current().start()

上面说白了是 app.add_handlers 的一层语法糖。这样虽然可以完成,但是由于 route 中使用了 app 导致必须将 app 的定义到第一次使用 route 的 __call__ 方法之前,令人不爽。那么现在模仿一下 Flask 的 Blueprint 来看看,在装饰器路由和 app.add_handlers 间添加一层降低耦合度。

# 目录结构
# .
# ├── app
# │   ├── api
# │   │   ├── handler.py
# │   │   └── __init__.py
# │   ├── auth
# │   │   ├── handler.py
# │   │   └── __init__.py
# │   └── __init__.py
# ├── patch.py
# └── main.py

# main.py

import tornado.ioloop  
import tornado.web  
from app import route


class RouteMixin(object):

    def register_handler(self, url_pattern, host_pattern=r'.*$'):
        self.add_handlers(host_pattern, url_pattern)


class Application(tornado.web.Application, RouteMixin):

    pass


if __name__ == '__main__':  
    app = Application()
    app.register_handler(route.handlers)
    app.listen(8888)
    tornado.ioloop.IOLoop.current().start()

# app/__init__.py

from .auth.handler import route as auth_route  
from .api.handler import route as api_route

assert (auth_route is api_route) # same object

route = auth_route

# app/api/handler.py

import tornado.web  
from patch import route

@route(r'/api')
class ApiHandler(tornado.web.RequestHandler):

    def get(self):
        self.write('api')

# app/auth目录代码相似

以上应该还有改进空间,有时间去看看 Flask 的 Blueprint 的实现