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

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

相关推荐
在未来等你14 分钟前
RabbitMQ面试精讲 Day 28:Docker与Kubernetes部署实践
中间件·面试·消息队列·rabbitmq
在未来等你21 小时前
RabbitMQ面试精讲 Day 29:版本升级与平滑迁移
中间件·面试·消息队列·rabbitmq
十五年专注C++开发2 天前
通信中间件 Fast DDS(二) :详细介绍
linux·c++·windows·中间件·fastdds
花妖大人2 天前
Python用法记录
python·sqlite
在未来等你2 天前
RabbitMQ面试精讲 Day 27:常见故障排查与分析
中间件·面试·消息队列·rabbitmq
dreams_dream2 天前
Django的Settings 配置文件详解
数据库·django·sqlite
风清再凯2 天前
DRF序列化器
django
夜雨听萧瑟3 天前
sqlite创建数据库,创建表,插入数据,查询数据的C++ demo
数据库·sqlite
dreams_dream3 天前
django错误记录
后端·python·django
摇滚侠3 天前
程序里的依赖和中间件的依赖冲突,怎么解决
中间件