本文首发于公众号:Hunter后端
原文链接:Flask笔记六之中间件操作
与 Django 一样,Flask 也提供了中间件的使用,用于在处理请求之前和之后执行一些公共逻辑
本篇笔记的代码都已经提交到 github 上,可使用下面的操作获取代码:
git clone https://github.com/x1204604036/flask_backend.git
在 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 都可以在一个中间件里操作完成
在 Flask
中,中间件通过装饰器来使用,被分为两部分,一个是请求前,用 @app.before_request
来操作,一个是请求后,用 @app.after_request
来操作。
接下来介绍一下中间件从注册到使用的操作。
这里我们定义两个测试用的中间件,我们在 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 中注册之后才可以使用。
要使用中间件,则需要进行引入注册操作,在这里,即为调用上面的 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 对象
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
这里给两个中间件使用的实例,一个中间件做登录验证操作,一个做接口的日志记录
分别在 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() 函数,用于记录开始时间,方便后面计算总时长。
这里有一点需要注意下,所有被 @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()