头图

本文首发于公众号:Hunter后端

原文链接:Flask笔记六之中间件操作

与 Django 一样,Flask 也提供了中间件的使用,用于在处理请求之前和之后执行一些公共逻辑

本篇笔记的代码都已经提交到 github 上,可使用下面的操作获取代码:

git clone https://github.com/x1204604036/flask_backend.git

1、Django 中间件使用示例

在 Django 中,我们可以先定义一个中间件,然后在 settings.py 中注册,中间件的内容大致如下:

# huter/middleware.py


class SimpleMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):

        # 在请求进入视图函数前的可以执行一些操作,针对 request
        print(request.path)
        response = self.get_response(request)
        
        # 在处理完请求后,可以执行一些操作,针对 response
        # log_response_info()
        return response

在 Django 中,请求处理前的 request 参数和处理后返回的 response 都可以在一个中间件里操作完成

2、Flask 的中间件介绍

Flask 中,中间件通过装饰器来使用,被分为两部分,一个是请求前,用 @app.before_request 来操作,一个是请求后,用 @app.after_request 来操作。

接下来介绍一下中间件从注册到使用的操作。

1. 中间件定义

这里我们定义两个测试用的中间件,我们在 app/utils/ 文件夹下创建一个 middlewares 的文件夹,其下再创建一个文件 middlewares.py

# app/utils/middlewares/middlewares.py

def register_middleware(app):

    @app.before_request
    def before_request_test():
        print("before request")

    @app.after_request
    def after_request_test(response):
        print("after request")
        return response

@app.before_request 修饰的函数表示在请求处理前进行的操作,这里是简单的打印一条消息,除此之外,我们还可以在这里对登录进行验证。

这样的话,在前面的笔记里我们对接口进行的 @login_required 的装饰器就不需要了,就不要在每个接口前都进行这种装饰,可以使代码变得简洁,同样实现我们想要的功能

除此之外,还可以对请求的信息进行日志记录,比如请求的接口名称啊,请求的参数啊等等

@app.after_request 修饰的函数表示在请求处理完成后进行的操作,我们可以对返回的 response 的数据增加一些参数等,其中,一个 response 的内容我们打印出它的 response.__dict__ 可以看到它的数据结构如下所示:

# response.__dict__

{'_charset': 'utf-8', 'headers': Headers([('Content-Type', 'application/json'), ('Content-Length', '75')]), '_status': '200 OK', '_status_code': 200, 'direct_passthrough': False, '_on_close': [], 'response': [b'{\n  "code": 0,\n  "msg": "success",\n  "user_info": {\n    "user_id": 1\n  }\n}\n']}

当然,上面的操作还需要在 app 中注册之后才可以使用。

2. 中间件引入注册

要使用中间件,则需要进行引入注册操作,在这里,即为调用上面的 register_middleware() 函数,我们在 app/__init__.py 中操作如下:

# app/__init__.py

from app.utils.middlewares.middlewares import register_middleware

def create_app():
    app = Flask(__name__)
    register_middleware(app)
    return app

引入后重启服务,然后调用一个接口,就可以看到日志里会输出前面测试的两个中间件打印出的信息

接下来我们创建两个中间件,用于实现登录的校验和接口请求的日志记录,在此之前,先介绍一下 Flask 里的 g 对象

3、g 对象

Flask 里有一个 g 对象的概念,它可以用于在一个请求周期内存储共享的临时数据,但是仅限于一个请求周期。

比如客户端发起了两次请求,我们在第一个请求里保存到 g 对象里的数据就随着第一个请求的 response 的返回就销毁了,第二次请求是一个全新的 g 对象。

这个其实和 session 对象有相似之处,但是 session 是跨请求周期的,比如第一个接口登录,保存了 user_id 登录信息,第二个接口再次请求还可以读取到这个信息

介绍 g 对象是因为后面我们在中间件中可以用到一些信息,这些信息的传递就可以通过 g 对象的方式来操作。

以下是 g 对象的赋值与取值操作:


from flask import g

g.user_id = 1

print(g.user_id)

在这里,我将所有需要给 g 对象赋值的数据都在一个函数里完成,这里,我创建了一个文件:app/utils/init_g_object.py

其中内容如下:

from flask import g, request, session

def init_g_object(app):

    @app.before_request
    def init_g_object_info():
        g.request_path = request.path
        g.user_id = session.get("user_id")
        g.ip_address = request.remote_addr

g 对象数据的初始化在 app/__init__.py 中:

# app/__init__.py

from app.utils.init_g_object import init_g_object


def create_app():
    app = Flask(__name__)
    init_g_object(app)
    return app

4、Flask 中间件使用实例

这里给两个中间件使用的实例,一个中间件做登录验证操作,一个做接口的日志记录

分别在 app/utils/middlewares/ 文件夹下创建两个文件,login_required_middleware.py 和 request_log_middleware.py

其内容如下:

# login_required_middleware.py

from flask import g
from app.utils.exception_handler import UserException

class LoginRequiredMiddleware:

    def check_login(self):
        return True if g.user_id is not None else False


    def check_login_essential(self):
        url_path = g.request_path
        outer_url_list = ["/user/login", "/user/register"]

        return True if url_path not in outer_url_list else False


    def check(self):
        need_check = self.check_login_essential()
        if need_check:
            if not self.check_login():
                raise UserException(code=-1, msg="not login", http_code=401)

在这个中间件中,我们先判断当前请求的路径是否是需要登录才可访问的,如果是,则判断 g 对象中是否有登录信息,也就是 user_id,这个在前面给 g 对象初始化信息的时候有写入

如果需要登录但是没有登录,则直接返回一个报错信息

请求的日志记录中间件内容如下:

# request_log_middleware.py

import logging
import time
from flask import g


logger = logging.getLogger()

class RequestLogMiddleware:

    def log_info(self):
        total_time = time.time() - g.start_time
        log_info = "request_path: {}, request_user: {}, spend_time: {}, ip_address: {}".format(
            g.request_path,
            g.user_id,
            total_time,
            g.ip_address,
        )
        logger.info(log_info)

这个中间件是在请求处理完成之后的逻辑,在这个中间件中,我们直接根据当前时间减去 g 对象的开始时间,这个字段下面会有定义,是在请求开始前写入的,然后将请求路径,请求用户,总耗时,和请求的 IP 地址写入日志。

如果有需要,还可以将请求参数和返回参数也写入日志,方便之后的查询

然后在 middlewares.py 中定义:

# app/utils/middlewares/middlewares.py

from .login_required_middleware import LoginRequiredMiddleware
from .request_log_middleware import RequestLogMiddleware
from flask import g
import time


def register_middleware(app):

    @app.before_request
    def record_start_time():
        g.start_time = time.time()


    @app.before_request
    def login_required():
        LoginRequiredMiddleware().check()


    @app.after_request
    def request_log_info(response):
        RequestLogMiddleware().log_info()
        return response

在中间件的注册中,因为后面的日志记录需要用到请求开始的时间,所以这里定义了一个 record_start_time() 函数,用于记录开始时间,方便后面计算总时长。

5、before_request 中的返回

这里有一点需要注意下,所有被 @app.before_request 装饰的函数,如果有返回值,那么则直接返回,不再接着往后面执行逻辑,比如下面的:

def register_middleware(app):


    @app.before_request
    def test_return():
        return {"msg": "test return"}

    @app.before_request
    def test2():
        pass

    @app.after_request
    def test3(response):
        return response

    @app.after_request
    def test4(response):
        return response

在这里,before_request 的顺序执行的,先执行 test_return() 再执行 test2(),但是因为 test_return() 有返回值,所以整个请求流程直接结束了

而对于 after_request 修饰的函数,它是逆序执行的,也就是先执行 test4(),再执行 test3()

如果想获取更多后端相关文章,可扫码关注阅读:


Hunter
27 声望12 粉丝