tornado 如何实现异步装饰器

先来介绍下 装饰器的用途,为什么要使用装饰器

假如有这么个功能,来计算耗时:

def add():

    start_time = time.time()

    time.sleep(3)

    end_time = time.time()

    print("耗时:{}".format(end_time - start_time))

    return None

if __name__ == "__main ":

    add(1, 3)

疑问:会发现,如果我有多个应用需要计时,那就需要写很多的重复代码,这时候就可以使用装饰器来操作:

# 定义一个方法,接收一个函数,在python中一切皆对象,函数也可以用来做参数赋值等

# 需要理解python装饰器加载的过程和闭包

# 新建一个test.py

import time

def time_desc(func):

    print("装饰器")

    start_time = time.time()

    func()

    end_time = time.time()

    print("耗时:{}".format(end_time - start_time))

    # return a + b

@time_desc

def add():

    

    time.sleep(3)

    

    return None

python test.py 运行后,发现函数time_desc,执行了,控制台输入了日志

装饰器

耗时:3.000149965286255

耗时:3.000149965286255

我们并没有运行 time.desc 方法,那它为什么执行了呢? 

事实证明了,在执行或者调用了这个模块里有装饰器,那python的这个@语法糖就直接运行了这个函数,所以我们才需要把

装饰器,写成闭包的形式

改写成:

import time

def time_desc(func):

    def wrapper():

        print("装饰器")

        start_time = time.time()

        func()

        end_time = time.time()

        print("耗时:{}".format(end_time - start_time))

    return wrapper  # 注意 此处不是 wrapper() 不是调用 

@time_desc

def add():

    time.sleep(3)

    return None

还有需要注意:

1、经语法糖@装饰器后,返回的其实是wrapper,如果你调用 add() 其实是执行了装饰器 wrapper方法,

2、如果add(a,b) 有参数的话,那装饰器 wrapper(*args, **kwargs),来解决参数问题

代码大致:

import time

def time_desc(func):

    def wrapper(*args, **kwargs):

        print("装饰器")

        start_time = time.time()

        func(*args, **kwargs)

        end_time = time.time()

        print("耗时:{}".format(end_time - start_time))

    return wrapper  # 注意 此处不是 wrapper() 不是调用

@time_desc

def add(a, b):

    time.sleep(3)

    return a + b

if __name__ == "__main__":

    add(4, 5)

以上介绍大致装饰器的由来;

下面开始讲解,如何定义tornado异步装饰器:

大家可以先看看 from tornado.web import authenticated 的源码:

def authenticated(method):

    """Decorate methods with this to require that the user be logged in.

    If the user is not logged in, they will be redirected to the configured

    `login url `.

    If you configure a login url with a query parameter, Tornado will

    assume you know what you"re doing and use it as-is.  If not, it

    will add a `next` parameter so the login page knows where to send

    you once you"re logged in.

    """

    @functools.wraps(method)

    def wrapper(self, *args, **kwargs):

        if not self.current_user:

            if self.request.method in ("GET", "HEAD"):

                url = self.get_login_url()

                if "?" not in url:

                    if urlparse.urlsplit(url).scheme:

                        # if login url is absolute, make next absolute too

                        next_url = self.request.full_url()

                    else:

                        next_url = self.request.uri

                    url += "?" + urlencode(dict(next=next_url))

                self.redirect(url)

                return

            raise HTTPError(403)

        return method(self, *args, **kwargs)

    return wrapper

这个装饰器,会调用self.current_user这个方法,如果用户没有登录的情况下,它会去调用get_login_url(),查看源码可知,它会去检查我们的配置中

是否有login_url,如果没有则会抛出异常,如果用户没有登录,则会调用self.redirect(url),会去设置我们的status为301或302,所以说这个方法在我们前后端分离的项目中,这个装饰器并不使用的,更何况,self.current_user这个会调用self.get_current_user(),这个是同步的操作,并不适合我们tornado异步编程,我们也是需要用协程替代的;

下面是用jwt来做前后端分离的令牌

做好跨域和token的定义

from tornado.web import RequestHandler

import redis

class BaseHandler(RequestHandler):

    def set_default_headers(self):

        self.set_header("Access-Control-Allow-Origin", "*")

        self.set_header("Access-Control-Allow-Headers", "*")

        self.set_header("Access-Control-Max-Age", 1000)

        self.set_header("Content-type", "application/json")

        self.set_header("Access-Control-Allow-Methods", "POST, GET, DELETE, PUT, PATCH, OPTIONS")

        self.set_header("Access-Control-Allow-Headers",

                        "Content-Type, token, Access-Control-Allow-Origin, Access-Control-Allow-Headers, X-Requested-By, Access-Control-Allow-Methods")

    def options(self, *args, **kwargs):

        pass

class RedisHandler(BaseHandler):

    def __init__(self, application, request, **kwargs):

        super().__init__(application, request, **kwargs)

        self.redis_conn = redis.StrictRedis(**self.settings["redis"])

下面是装饰器模块:

import functools

import jwt

from apps.users.models import User

def authenticated_async(method):

    @functools.wraps(method)

    async def wrapper(self, *args, **kwargs):

        # 取出token

        tsessionid = self.request.headers.get("token", None)

        if tsessionid:

            try:

                send_data = jwt.decode(tsessionid, self.settings["secret_key"], leeway=self.settings["jwt_expire"], options={"verify_exp": True})

                user_id = send_data["id"]

                #从数据库中获取到user并设置给_current_user

                try:

                    user = await self.application.objects.get(User, id=user_id)

                    self._current_user = user

                    #此处很关键 返回的是协程对象 需要用await 来调用

                    await method(self, *args, **kwargs)

                except User.DoesNotExist as e:

                    self.set_status(401)

            except jwt.ExpiredSignatureError as e:

                self.set_status(401)

        else:

            self.set_status(401)

        self.finish({})

    return wrapper


            
            

本博客源码Github地址:

https://github.com/whisnos/myblog

请随手给个star,谢谢!

打赏

评论