在之前一篇博文中,有关django的请求流程中,我们关于中间件这一层并没有详细的介绍,在这张图中,我们将中间层定义为django网关层和路由层的过渡层,那么具体会中间件会做什么事呢。
django默认中间件
在配置文件中,有一个MIDDLEWARE列表,里面存放了七个字符串,实际上这就是七个中间件程序,其基础作用就是处理我们的请求和响应,比如第四个中间件就经常妨碍我们发送简单的POST请求,所以初学阶段经常需要将其注释掉不让其生效:
这七个中间件程序会在请求来时自上而下依次的处理request请求,在响应走时自下而上依次处理response响应。
django中间件原理
中间件程序结构
这七个字符串实际上对应的是中间件程序存储的路径,我们可以顺着路径进去看一下它们的结构:
这七个程序都遵循着这样的结构:
from django.utils.deprecation import MiddlewareMixin
自定义中间件
在知道以上结构后我们可以自己自定义中间件程序:
from django.utils.deprecation import MiddlewareMixin
class MidWare01(MiddlewareMixin):
def process_request(self, request):
pass
def process_response(self, request, response):
return response
中间件程序执行顺序
关于process_request和process_response是中间件最常用的函数,我们重点研究这个函数的顺序即可。在自定义中间件中加一点小程序测试一下:
当我们从网页向网址发送请求(无论有没有对应路由),后端会打印:
我们可以看出一些结论:
- 请求会经过中间件程序,按注册顺序自上而下的运行每个中间件process_request程序
- 响应会经过中间件程序,按注册顺序自下而上的运行每个中间件的process_response程序。
我们还可以做进一步的测试,如将process_request设置一个httpResponse返回值和将process_response的response替换成其他响应。
直接给出结论:
- 当中间件的process_request函数返回httpResponse对象,那么就之间去执行当前中间件的process_response函数,这个返回值传入response参数
- 中间件的process_response的返回值是传递性的,中途可以返回其他的response,返回什么,下一轮的response就接收什么,最终传递给用户浏览器。
我们可以用图解来解释这几个结论:
ps:中间件请求函数返回httpResponse对象,在django中是直接执行当前层中间件的process_response,而在flask中,是从最底层的中间件程序的响应函数开始执行的
字符串列表注册原理
如何通过字符串列表来实现中间件程序的注册的,我们需要两个知识点的前置学习:
importlib模块
importlib可以帮助我们用字符串导入模块,拿到一个模块名的字符串,它可以搜索模块,并执行导入操作:
import importlib # 导入importlib模块
s1 = "aaa"
res = importlib.import_module(s1) # 相当于执行import aaa 并将模块名设置为res
res.aaa_attr # 可以通过res点出aaa模块的属性
s2 = "pack.md"
ret = importlib.import_module(s2) # 相当于执行from pack import md 并将模块名设置为ret
ret.md_func() # 可以通过ret点出md模块的属性方法
import_module方法可以对句点隔开的字符串进行识别,但注意,最终导入的最小单位是py文件,不能导入py文件中的变量
模块对象的反射
在python中一切皆对象,模块也是一种对象,模块的名称空间中的名字就是模块对象的属性。
所以对于一个模块,我们可以通过反射的方式取到其中的类、函数、变量等名字:
import aaa
attr = getattr(aaa, "func") # 寻找aaa模块中的func属性
attr() # 直接加扩号进行执行
仿django配置文件注册
知道以上两个知识点,我们也可以让仿中间件的注册过程,写好类后,用字符串保存路径,然后顺序执行。核心代码就是将字符串识别成可以执行的类程序:
# MyMiddleWare文件夹midware01.py中
class MidWare01(MiddlewareMixin):
def process_request(self, request):
print("from MidWare01 request")
# MyMiddleWare文件夹midware02.py中
class MidWare02(MiddlewareMixin):
def process_request(self, request):
print("from MidWare01 request")
# 注册配置
MIDWARE_LIST = [
"MyMiddleWare.midware01.MidWare01",
"MyMiddleWare.midware02.MidWare02",
]
# 按照注册顺序,执行每个类中的process_request方法
import importlib
def all_exec(request):
for full_path in MIDWARE_LIST:
# 将字符串切割为模块路径和类名
path, cls_name = full_path.rsplit(".", max_split=1)
# 通过模块路径字符串拿到模块名
module_name = importlib.import_module(path)
# 通过模块名反射拿到类名
cls = getattr(module_name, cls_name)
# 通过类产生对象
obj = cls()
# 通过对象使用方法
obj.process_request(request) # 多态的思想,因为大家的这个功能一致,所以方法名也该一致
自定义中间件
在上一小节已经介绍过自定义中间件的大致方法,这里简单罗列一下:
- 首先,我们需要一个py文件存放程序,这个位置最好相对固定
- 其次,编写中间件的类,导入MiddlewareMixin继承,写好基础的类体函数
- 最后,将编写好的类的路径及类名按格式注册到MIDDLEWARE的列表中。
其中第二步的类体函数,其实对应有很多的中间件方法,这里将会介绍5个中间件方法。
两个常见的自定义方法
process_request
- 这个方法是用于处理请求的,在网关处理好request之后,路由层处理路由之前,依注册顺序执行。
- 需要定义request形参接收请求,对请求做一系列判断。
- 如果process_request返回HttpResponse对象,则不会再执行后续的中间件以及路由等程序,直接将返回值传入process_response。
process_response
- 这个方法是用于处理响应的,在视图层返回HttpResponse对象的响应后,网关层处理响应之前,由各中间件依注册顺序自下而上执行。
- 需要定义request、response形参接收请求和响应,对响应做一定的处理。
- 返回值一定要将response传递走或者返回其他的HttpResponse对象。
三个了解的自定义方法
- process_view
路由匹配成功之后执行视图函数/类之前自动触发(顺序同process_request) - process_exception
视图函数/类执行报错自动触发(顺序同process_response) - process_template_response
视图函数/类返回的HttpResponse对象含有render并且对应一个方法的时候自动触发(顺序同process_response)
这三个方法的使用频率十分的低,不过我们需要注意到中间件的职能并不局限于网关和django程序之间了,还有可能是路由与视图层之间,处理错误的时候,视图层与模板层之间。
通过这些我们信息我们也可以绘制一张新的django请求生命流程图。