django之中间件(middleware)

在之前一篇博文中,有关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)  # 多态的思想,因为大家的这个功能一致,所以方法名也该一致

自定义中间件

在上一小节已经介绍过自定义中间件的大致方法,这里简单罗列一下:

  1. 首先,我们需要一个py文件存放程序,这个位置最好相对固定
  2. 其次,编写中间件的类,导入MiddlewareMixin继承,写好基础的类体函数
  3. 最后,将编写好的类的路径及类名按格式注册到MIDDLEWARE的列表中。

其中第二步的类体函数,其实对应有很多的中间件方法,这里将会介绍5个中间件方法。

两个常见的自定义方法

process_request

  • 这个方法是用于处理请求的,在网关处理好request之后,路由层处理路由之前,依注册顺序执行。
  • 需要定义request形参接收请求,对请求做一系列判断。
  • 如果process_request返回HttpResponse对象,则不会再执行后续的中间件以及路由等程序,直接将返回值传入process_response。

process_response

  • 这个方法是用于处理响应的,在视图层返回HttpResponse对象的响应后,网关层处理响应之前,由各中间件依注册顺序自下而上执行。
  • 需要定义request、response形参接收请求和响应,对响应做一定的处理。
  • 返回值一定要将response传递走或者返回其他的HttpResponse对象。

三个了解的自定义方法

  1. process_view
    路由匹配成功之后执行视图函数/类之前自动触发(顺序同process_request)
  2. process_exception
    视图函数/类执行报错自动触发(顺序同process_response)
  3. process_template_response
    视图函数/类返回的HttpResponse对象含有render并且对应一个方法的时候自动触发(顺序同process_response)

这三个方法的使用频率十分的低,不过我们需要注意到中间件的职能并不局限于网关和django程序之间了,还有可能是路由与视图层之间,处理错误的时候,视图层与模板层之间。

通过这些我们信息我们也可以绘制一张新的django请求生命流程图。

django请求生命流程图(完整版)