无知的 TonySeek

Yet Another Seeker

【碎片】tornado 用上装饰器路由

貌似很多人是零配置控啊,喜欢类似 Flask 一类的零配置路由,不需要在 run.py 中写一个长列表,只需要在接受请求的函数 or 类前面加一个装饰器,正如:

from bottle import route, run

@route('/:name')
def index(name='World'):
    return '<strong>Hello %s!</strong>' % name

run(host='localhost', port=8080)

对比 web.py 的集中配置:

import web

urls = ("/.*", "hello")
app = web.application(urls, globals())

class hello:
    def GET(self):
        return 'Hello, world!'

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

貌似二者没神马区别嘛,只不过把集中的配置分散了。当然对于这个问题也是很多不同看法,有人觉得分散了配置不利统一管理,有人觉得把 url 和请求处理者放在一起有利于模块的无缝装卸。当然最理想的是两种选择都存在。在豆瓣上已经看到有人提出 给 web.py 添加装饰器路由的方式 <http://www.douban.com/group/topic/12998784/> _ 了,我自己也写了个 tornado 版的。

于是,本代码碎片诞生。作为小白,python 实在写的很少,求拍砖求拍砖……

base.py
 # coding:utf-8
 """
 基础模块, 扩展了 Tornado Web 服务器

 @author: tonyseek
 """
 import tornado.web
 import types

 class Application(tornado.web.Application):
     """
     Tornado 应用实例
     """
     def load_module(self, module, **options):
         """
         加载 RequestHandler 模块
         """
         assert type(module) is types.ModuleType
         host_pattern = options.get('host_pattern', '.*$')

         # 处理加载 RequestHandler 和路由规则
         cls_valid = lambda cls: type(cls) is types.TypeType \
                     and issubclass(cls, RequestHandler) # 是否有效
         url_valid = lambda cls: hasattr(cls, 'url_pattern') \
                     and cls.url_pattern # 是否拥有 url 规则
         mod_attrs = (getattr(module, i) for i in dir(module) \
                      if not i.startswith('_'))
         valid_handlers = ((i.url_pattern, i) for i in mod_attrs \
                           if cls_valid(i) and url_valid(i))
         # 处理完毕载入
         self.add_handlers(host_pattern, valid_handlers)

     def _get_host_handlers(self, request):
         """
         覆盖父类方法, 一次获取所有可匹配的结果. 父类中该方法一次匹配
         成功就返回, 忽略后续匹配结果. 现通过使用生成器, 如果一次匹配
         的结果不能使用可以继续匹配.
         """
         host = request.host.lower().split(':')[0]
         # 使用生成器表达式而非列表推导式, 减少性能折扣
         handlers = (i for p, h in self.handlers for i in h \
                     if p.match(host))

         if not handlers and "X-Real-Ip" not in request.headers:
             handlers = [i for p, h in self.handlers for i in h \
                         if p.match(self.default_host)]
         return handlers

 class RequestHandler(tornado.web.RequestHandler):
     url_pattern = None

 def route(url_pattern):
     """
     路由装饰器, 只能装饰 RequestHandler 子类
     """
     def handler_wapper(cls):
         assert(issubclass(cls, RequestHandler))
         cls.url_pattern = url_pattern
         return cls
     return handler_wapper

下面是一个 RequestHandler 集合,也就是“可装卸”的那货:

main/handlers.py
 @base.route('/')
 class Main(base.RequestHandler):
     def get(self):
         self.write("Hello World")

 @base.route('/(.*)')
 class Person(base.RequestHandler):
     def get(self, name):
         self.write(name)

下面 load_handler_module 就是装载模块的方法:

run.py
 import base
 import main.handlers

 application = base.Application()
 application.load_module(main.handlers)

 if __name__ == "__main__":
     application.listen(8080)
     tornado.ioloop.IOLoop.instance().start()

Comments