django之中间件

中间件是什么

中间件是django框架提供的一套钩子机制,用于在请求和响应处理过程中插入自定义逻辑。它可以在全局范围内修改 Django 的输入或输出。

没有中间件的处理流程:http请求 ---> view处理函数 ---> http响应。

加上中间件钩子后的处理流程:http请求 --->中间件(process_request)---> view处理函数 --->中间件(process_response)---> http响应。

在一些场景,如记录日志、限制访问IP等操作,就可以用中间件钩子去解决。我们只需要把记录日志的动作封装成一个中间件,就可以在请求到达view之前记录日志了。

TIPs: 中间件是django框架的机制,DRF中并没有做改写。

中间件如何配置

通过settings.py文件里的MIDDLEWARE列表。

我们看一下新建一个django项目默认的配置。

python 复制代码
# 每一行就是一个中间件,具体作用可以自行百度
# 可以根据需要,增加或删除需要使用的中间件
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

自定义中间件

定义中间件有多重方法,可以基于函数,也可以基于类,下面只介绍一种推荐的方式。

python 复制代码
# 基于类的混入方式
from django.utils.deprecation import MiddlewareMixin

# 自定义中间件名字
# 代码文件:myproject/middleware.py
class MyXyzMiddleware(MiddlewareMixin):

    # 正常情况下返回None
    # 异常情况返回response实例
    # 比如ip白名单校验,通过则return,不通过返回response实例
    def process_request(self, request):
        print("Processing request", id(request))

    # 返回response实例
    def process_response(self, request, response):
        print("Processing response", id(request))
        return response

    # 正常情况下返回None
    # 异常情况可以返回response实例
    def process_view(self, request, view_func, view_args, view_kwargs):
        print("Processing view", view_func.__name__)

    def process_exception(self, request, exception):
        print("Processing exception", exception)
        return HttpResponse("An error occurred")

    def process_template_response(self, request, response):
        print("process_template_response")
        return response


# 将自定义中间件注册到settings文件中
MIDDLEWARE = [
...... 省略默认的
'myproject.middleware.MyXyzMiddleware'
]

在自定义中间件MyXyzMiddleware类里定义了5个函数,其实是中间件支持的5个钩子位置。实际开发中可以根据需要使用一个或者任意个函数,不需要的函数直接删掉就行。最常用的就是process_request和process_response。

中间件的执行顺序

先只考虑只有process_request和process_response的情况。

建设setttings文件里的配置如下:

python 复制代码
MIDDLEWARE = [
'mw1',
'mw2',
'mw3'
]

那么正常情况下的请求处理逻辑是这样的.

可以看出中间件的执行是有顺序的,在请求阶段,按列表顺序从上到下执行,在响应阶段,是从下到上执行。因此对于中间件之间有依赖的,要在配置的时候注意顺序位置。

比如'django.contrib.auth.middleware.AuthenticationMiddleware'依赖'django.contrib.sessions.middleware.SessionMiddleware',就必需放在session的下面。

代码解析

上面关于中间件的说明都只是关注一些主要的流程。其实很多细节还是需要解析代码才可以理解,比如process_request和process_view的执行时机和退出时机,中间件这种执行顺序是如何进行编码设计的等等。

python 复制代码
# 中间件的加载
# django/core/handlers/base.py
class BaseHandler:
  # 列表,按顺序存储中间件里的process_view函数
  _view_middleware = None
  # 列表,按顺序存储中间件里的process_view函数
  _template_response_middleware = None
  # 列表,按顺序存储中间件里的process_view函数
  _exception_middleware = None
  # 函数,这个函数是一个层层嵌套的函数链条,一个函数包一个函数,组成了一个洋葱式的结构
  _middleware_chain = None

  # 下面的代码解析之保留了主要框架,去掉了关于异步的很多处理
  def load_middleware(self, is_async=False):
    # 初始的handler就是get_response函数
    handler = get_response
    # 对中间件列表先反转reversed,再循环遍历
    for middleware_path in reversed(settings.MIDDLEWARE):
      middleware = import_string(middleware_path)
      # 这里handler函数作为一个参数传给了中间件
      # 中间件这里的处理逻辑类似装饰函数,传入一个handler进行实例化
      # 具体逻辑见后面关于中间件类的解析
      mw_instance = middleware(handler)

      # 分别将中间件的三个处理钩子塞进列表
      if hasattr(mw_instance, "process_view"):
        self._view_middleware.insert(0, mw_instance.process_view)
      if hasattr(mw_instance, "process_template_response"):
        self._template_response_middleware.append(mw_instance.process_template_response)
      if hasattr(mw_instance, "process_exception"):
        self._exception_middleware.append(mw_instance.process_exception)

      # 将中间件的实例作为下一次循环的输入
      handler = mw_instance 
   # 将经过所有中间件包装后的处理函数绑定到_middleware_chain
   self._middleware_chain = handler


# 中间件类django/utils/deprecation.py
class MiddlewareMixin:
  # 传入一个函数或可调用实例,把它绑定到self.get_response
  # 这里需要注意get_response只是一个名称,
  # 并不是django.core.handlers.base.py中的get_response函数
  # 在load_middleware中,get_response实际是中间件实例mw_instance
  def __init__(self, get_response):
    self.get_response = get_response
    super().__init__()

  # 此处定义call魔法函数,每次调用的时候执行此段逻辑。
  # 关于这段代码如何把一串中间件封装成洋葱式的函数,确实不太好语言表达
  # 只能自己仔细揣摩了
  def __call__(self, request):
    response = None
    if hasattr(self, "process_request"):
      response = self.process_request(request)
    # 这是一个短路写法,如果response为None,则继续执行get_response
    # 如果response不为None,则直接进入了中间件的process_response部分
    # 这就是如果一个中间件返回结果不是None,则直接跳过后面中间件和view视图处理,
    # 进入process_reponse环节
    response = response or self.get_response(request)
    if hasattr(self, "process_response"):
      response = self.process_response(request, response)
    return response

# 洋葱函数的调用
# django/core/handlers/base.py
def get_response(self, request):
  set_urlconf(settings.ROOT_URLCONF)
  # 此处调用洋葱函数,在洋葱函数的中间层是调用视图的函数_get_response
  response = self._middleware_chain(request)
  return response

# 视图处理函数
# 如果中间件的process_request都通过了,则进入试图处理函数,就是下面的_get_response
# django/core/handlers/base.py
def _get_response(self, request):
  response = None
  # 解析路由,找到对应的处理函数
  callback, callback_args, callback_kwargs = self.resolve_request(request)
  # 顺序执行中间件中的process_view函数
  for middleware_method in self._view_middleware:
    response = middleware_method(request, callback, callback_args, callback_kwargs)
    # 若process_view任何一个函数返回非None,则跳过后续中间件
    if response:
      break
  # 如果process_view中返回非None,也会跳过视图函数的执行
  if response is None:
    wrapped_callback = self.make_view_atomic(callback)
    # 执行视图函数
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  # 返回响应结果
  # 中间也有_template_response_middleware\process_exception_by_middleware处理的逻辑
  # 不展开说明
  return response

最后总结一张整体的流程处理图

相关推荐
xt19892882 小时前
测试开发:Python+Django实现接口测试工具
python·测试开发·django·自动化平台
每天的每一天4 小时前
分布式文件系统05-生产级中间件的Java网络通信技术深度优化
java·开发语言·中间件
白帽程序员猫哥4 小时前
漏洞全讲解之中间件与框架漏洞(数字基础设施的“阿喀琉斯之踵“)
网络·安全·web安全·中间件·密码学·渗透
每天的每一天5 小时前
分布式文件系统07-小文件系统的请求异步化高并发性能优化
中间件·架构
木易双人青6 小时前
Django事务支持
python·django
小王子10241 天前
DRF视图详解:从基础视图到通用视图实践指南
django·drf·视图·通用视图
重装小兔19c2572 天前
Tomcat,WebLogic等中间件漏洞实战解析
中间件
97zz2 天前
项目配置文件正确但是启动失败,报配置文件内容错误或中间件地址与实际不符
java·中间件·springboot