python tornado中如何给每个服务器请求动态加上装饰器?

tornado是一个服务器框架,,里面有get、put、post、delete请求接口,如何给这些接口动态加上装饰器,比如针对每次调用,进行如:用户是否登陆的校验?如果自己手动进行增减,容易遗漏

阅读 6.1k
2 个回答

tornado在RequestHandler中提供了 prepare和finish这样的方法。prepare在框架调用get/post/...这些方法之前调用。finish在最终写回response时由框架调用。所以可以利用这个实现类似 django中middleware的功能。比如登录检查,权限验证,修改response的结果都可以在middleware中完成。

class Middleware(object):
    def process_request(self, handler):
        pass
        
    def process_response(self, handler):
        pass
        

def prepare(self):
    for middle in self.application.middleware:
        middle.process_request(self)
    super(MyRequestHandler, self).prepare()            

def finish(self):
    for middle in self.application.middleware:
        middle.process_response(self)
        
        
                                                                    

必须是元类,用来控制类的创建。

示例代码,假设登录后get_user能返回用户名,即类B的是没有登录的:

# -*- coding: utf-8 -*-

import functools


class HttpError(Exception):
    pass


def logined(func):
    @functools.wraps(func)
    def _wrapper(self, *args, **kwargs):
        if self.user:
            return func(self, *args, **kwargs)
        # Or, redirect.
        raise HttpError(403)
    return _wrapper


class MetaWrapWithLogin(type):

    def __new__(cls, name, bases, attrs):
        need_login = list()
        if 'need_login' in attrs:
            need_login.extend(list(attrs['need_login']))
        for method in need_login:
            if method in attrs:
                attrs[method] = logined(attrs[method])
        return type.__new__(cls, name, bases, attrs)


class Base(object):

    @property
    def user(self):
        return self.get_user()

    def get_user(self):
        pass

    def get(self):
        raise HttpError(404)

    def post(self):
        raise HttpError(404)


class A(Base):
    __metaclass__ = MetaWrapWithLogin
    need_login = ['get', 'post']

    def get_user(self):
        return 'foo'

    def get(self):
        print('{0} get'.format(self.__class__.__name__))

    def post(self):
        print('{0} post'.format(self.__class__.__name__))


class B(Base):
    __metaclass__ = MetaWrapWithLogin
    need_login = ['post']

    def get(self):
        print('{0} get'.format(self.__class__.__name__))

    def post(self):
        print('{0} post'.format(self.__class__.__name__))


if __name__ == '__main__':
    a = A()
    a.get()
    a.post()

    print('\n{0}\n'.format('-' * 10))

    b = B()
    b.get()
    b.post()

输出:

A get
A post

----------

B get
Traceback (most recent call last):
  File "test.py", line 88, in <module>
    b.post()
  File "test.py", line 16, in _wrapper
    raise HttpError(403)
__main__.HttpError: 403

然后按照这个思路自己改造吧。


装饰器版:

# -*- coding: utf-8 -*-

import functools


def login_decorator(func):
    @functools.wraps(func)
    def _wrapper(self, *args, **kwargs):
        if hasattr(self, 'user') and self.user:
            return func(self, *args, **kwargs)
        # Or, redirect.
        raise HttpError(403)
    return _wrapper


def add_to_methods(decorator, *methods):
    def wrap_method(cls):
        for method in methods:
            if hasattr(cls, method):
                setattr(cls, method, decorator(getattr(cls, method)))
        return cls
    return wrap_method


class HttpError(Exception):
    pass


@add_to_methods(login_decorator, 'post')
class A(object):

    def get(self):
        print('{0} get'.format(self.__class__.__name__))

    def post(self):
        print('{1} post'.format(self.__class__.__name__))


if __name__ == '__main__':
    a = A()
    a.get()
    a.post()

输出:

A get
Traceback (most recent call last):
  File "need.py", line 42, in <module>
    a.post()
  File "need.py", line 12, in _wrapper
    raise HttpError(403)
__main__.HttpError: 403
推荐问题
宣传栏