中间件源码分析

中间件简介

中间件是一个用来处理Django的请求和响应的框架级别的钩子。它是一个轻量、低级别的插件系统,用于在全局范围内改变Django的输入和输出。每个中间件组件都负责做一些特定的功能。

但是由于其影响的是全局,所以需要谨慎使用,使用不当会影响性能。

中间件是帮助我们在视图函数执行之前和执行之后都可以做一些额外的操作,它本质上就是一个自定义类,类中定义了几个方法,Django框架会在请求的特定的时间去执行这些方法。

中间件中主要可以定义下面5个钩子函数来对请求的输入输出做处理:

  • process_request(self,request)
  • process_view(self, request, view_func, view_args, view_kwargs)
  • process_template_response(self,request,response)
  • process_exception(self, request, exception)
  • process_response(self, request, response)

它们的主要作用参见官方文档.

这5个钩子函数的触发时机可以见下面的图.

说明: 上面说的顺序都是中间件在settings文件中列表的注册顺序.

源码分析

我对中间件如何会做出上面的处理顺序, 比较好奇, 于是就去研究了下Django的源码.

首先. Django在启动初始化一系列环境配置, 包括wsgi协议的实现, 也包括中间件组件的初始化. 中间件的初始化入口函数在这.

进入到load_middleware函数, 可以看我们可以自定义的钩子函数都在这里了, 放在5个列表里面. 接下来判断settings里面的MIDDLEWARE配置项是否为空, 为空的话会去django.conf.global_settings.py里面的默认的配置文件加载中间件.默认的中间件只有下面两个.

# django.conf.global_settings.py
MIDDLEWARE_CLASSES = [
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
] MIDDLEWARE = None

一般我们都不会注释掉项目下的7个默认中间件, 所以上面的代码会走else分支, 这里的else分支是初始化中间件组件的核心逻辑.最后会把所有的中间件放到self._middleware_chain这个中间件处理链之中. 这里的设计思想我也是花了好一段时间才想明白.

接下来展开else代码块, 到了核心部分. 下面列出的else里面的源码部分.

        else:
# 这里是将handler赋初始值为self._get_response, 这个函数是用来匹配请求url与调用视图函数
# 并应用view, exception, template_response中间件.
handler = convert_exception_to_response(self._get_response)
# 接下来一段代码比较难理解, 但确是设计的精髓.
# 首先, 遍历我们配置的中间件列表, 只不过这里是逆序遍历, 至于为什么, 往下看就知道了
for middleware_path in reversed(settings.MIDDLEWARE):
# 这里是将我们配置的字符串形式的中间件类通过反射解析成类. 原理最后会简单分析
middleware = import_string(middleware_path)
try:
# 将中间件类实例化为一个对象, 这里把上一个handler当做参数
# 这也是能够将中间件通过一个初始化对象链式调用的精髓. 下面会有解释
mw_instance = middleware(handler)
except MiddlewareNotUsed as exc:
if settings.DEBUG:
if six.text_type(exc):
logger.debug('MiddlewareNotUsed(%r): %s', middleware_path, exc)
else:
logger.debug('MiddlewareNotUsed: %r', middleware_path)
continue # 实例化对象为None, 因为中间件还可以是函数形式
if mw_instance is None:
raise ImproperlyConfigured(
'Middleware factory %s returned None.' % middleware_path
)
# 将process_view方法添加到_view_middleware列表的开头
if hasattr(mw_instance, 'process_view'):
self._view_middleware.insert(0, mw_instance.process_view)
# 将process_template_response方法添加到_template_response_middleware列表的末尾
# 这里也能解释为什么处理模板以及下面的异常时逆序(按照注册顺序逆序)处理的
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变量
handler = convert_exception_to_response(mw_instance) # 最后这个handler指向的是中间件列表的第一个实例对象
self._middleware_chain = handler

这样看完之后上面的分析之后应该还是难以理解思路, 这需要看这个中间件实例化对象的定义形式, 看下面这个中间件类定义部分;

class MiddlewareMixin(object):
def __init__(self, get_response=None):
self.get_response = get_response
super(MiddlewareMixin, self).__init__() def __call__(self, request):
response = None
if hasattr(self, 'process_request'):
response = self.process_request(request)
if not response:
response = self.get_response(request)
if hasattr(self, 'process_response'):
response = self.process_response(request, response)
return response

每个中间件类都有两个基本方法, 初始化时会保存下一个get_response对象, 最后调用中间件实例化对象就能够不停的调用存储的get_response对象, 这个是能够实现链式调用的关键. 上面的思想看下图演示.

这时候再来看上面的代码, 起始的handler首先指向最里层的get_response方法, 然后从列表最后的中间件开始遍历, 把handler(此时是get_response)当做参数, 生成一个中间件对象CommonMiddleware, 此时handler指向了这个新的对象, 然后依次循环, 重复上面的操作, 相当于一层包着一层.

最后handler会指向最外层的中间件对象. 然后赋值给self._middleware_chain这个变量.

当我们调用self._middleware_chain(request)方法的时候, 就会触发这个中间件的__call__方法. 这个时候从最外层中间件进行, 执行process_request方法, 只要不产生response, 就会一直调用内层的中间件变量, 触发__call__方法, 一直到最里层, 开始处理视图相关的功能. 在url匹配之后, 调用视图函数之前, 会遍历所有中间件的process_view方法. 如果返回的结果为None, 则去调用我们书写的视图函数, 如果触发异常, 则会遍历处理所有process_exception方法, 如果没有则去调用符合条件的process_template_response方法. 触发异常同样会触发process_exception方法. 最后会把结果返回回去. 而这时候会从最里层一层层往外返回. 这就能够解释中间件钩子函数的触发顺序.

这里再放一个最里层的处理逻辑, 有一些删减

    # django/core/handlers/base.py
def _get_response(self, request):
response = None
# 路由匹配
if hasattr(request, 'urlconf'):
urlconf = request.urlconf
set_urlconf(urlconf)
resolver = get_resolver(urlconf)
else:
resolver = get_resolver()
resolver_match = resolver.resolve(request.path_info)
# 这个callback就是我们的视图函数, 后两个是视图函数可能需要的参数
callback, callback_args, callback_kwargs = resolver_match
request.resolver_match = resolver_match # 应用 view middleware 中间件
for middleware_method in self._view_middleware:
response = middleware_method(request, callback, callback_args, callback_kwargs)
# 只要有response返回, 就立刻停止遍历
if response:
break if response is None:
# 给视图函数包装一层
wrapped_callback = self.make_view_atomic(callback)
try:
# 这里是调用视图函数
response = wrapped_callback(request, *callback_args, **callback_kwargs)
except Exception as e:
# 有异常就进入exception view处理
response = self.process_exception_by_middleware(e, request) # 这个不常用的process_template_response功能, 看源码可以清楚的知道为什么
# 返回的结果为啥需要有render方法了
elif hasattr(response, 'render') and callable(response.render):
for middleware_method in self._template_response_middleware:
response = middleware_method(request, response)
# ...
try:
response = response.render()
except Exception as e:
response = self.process_exception_by_middleware(e, request)
return response

小结

有了上面的部分源码分析, 最后可以明白中间件为什么会是以这样的顺序处理请求和响应的. Django这种设计思想是我重来没接触过的, 学习完之后感触非常深, 只能感慨别人程序的设计之精妙, 自己还要好好学习.

Django中间件部分源码分析的更多相关文章

  1. Django中间件CsrfViewMiddleware源码分析

    Django Documentation csrf保护基于以下: 1, 一个CSRF cookie基于一个随机生成的值,其他网站无法得到,次cookie有CsrfViewMiddleware产生.它与 ...

  2. django中间件CsrfViewMiddleware源码分析,探究csrf实现

    Django Documentation csrf保护基于以下: 1. 一个CSRF cookie 基于一个随机生成的值,其他网站无法得到.此cookie由CsrfViewMiddleware产生.它 ...

  3. Django搭建及源码分析(三)---+uWSGI+nginx

    每个框架或者应用都是为了解决某些问题才出现旦生的,没有一个事物是可以解决所有问题的.如果觉得某个框架或者应用使用很不方便,那么很有可能就是你没有将其使用到正确的地方,没有按开发者的设计初衷来使用它,当 ...

  4. Django如何启动源码分析

    Django如何启动源码分析 启动 我们启动Django是通过python manage.py runsever的命令 解决 这句话就是执行manage.py文件,并在命令行发送一个runsever字 ...

  5. Django之DRF源码分析(二)---数据校验部分

    Django之DRF源码分析(二)---数据校验部分 is_valid() 源码 def is_valid(self, raise_exception=False): assert not hasat ...

  6. 开源分布式数据库中间件MyCat源码分析系列

    MyCat是当下很火的开源分布式数据库中间件,特意花费了一些精力研究其实现方式与内部机制,在此针对某些较为重要的源码进行粗浅的分析,希望与感兴趣的朋友交流探讨. 本源码分析系列主要针对代码实现,配置. ...

  7. Django rest framework源码分析(3)----节流

    目录 Django rest framework(1)----认证 Django rest framework(2)----权限 Django rest framework(3)----节流 Djan ...

  8. Django rest framework源码分析(1)----认证

    目录 Django rest framework(1)----认证 Django rest framework(2)----权限 Django rest framework(3)----节流 Djan ...

  9. Django rest framework 源码分析 (1)----认证

    一.基础 django 2.0官方文档 https://docs.djangoproject.com/en/2.0/ 安装 pip3 install djangorestframework 假如我们想 ...

随机推荐

  1. SpringBootCLI 命令行工具

    Spring Boot CLI 是用于快速开发 Spring 应用的命令行工具.用来运行 Groovy (与 Java 风格类似)脚本. spring-cli 似乎不是可以各种diy spring-b ...

  2. bit(比特)与Byte(字节)的区别与关系

    1.bit:位 (小写b) 也称比特 是英文 binary digit的缩写 二进制数系统中,每个0或1就是一个位(bit)位是数据存储(计算机中信息)的最小单位计算机中的CPU位数指的是CPU一次能 ...

  3. 第三十四章 POSIX消息队列

    POSIX消息队列相关函数 mq_open 功能: 用来创建和访问一个消息队列 原型: mqd_t mq_open(const char *name, int oflag); //只能用来打开消息队列 ...

  4. NOIP模拟 4

    T1没开longlong T2忘了有向... T3是个好题,可以说将复杂度从N^2优化到NlogN是一个质的飞跃 考虑分治(要想出log可不就要分治么!(segtree也行 但我不会) 对于一个分治区 ...

  5. 使用Typescript重构axios(三十一)——添加axios.all和axios.spread方法

    0. 系列文章 1.使用Typescript重构axios(一)--写在最前面 2.使用Typescript重构axios(二)--项目起手,跑通流程 3.使用Typescript重构axios(三) ...

  6. Apache服务部署静态网站

    Web网络服务也叫WWW(World Wide Web),一般是指能够让用户通过浏览器访问到互联网中文档等资源的服务. 目前提供WEB网络服务的程序有Apache.Nginx或IIS等等,Web网站服 ...

  7. PTA刷题记录(1)

    团队天梯赛-------(2)分值:20 题目要求:你写个程序把给定的符号打印成沙漏的形状.例如给定17个“*”,要求按下列格式打印 ***** ***   *  *** ***** 所谓“沙漏形状” ...

  8. 关于Python中的yield的理解

    生成器:yield表达式构成的函数就是生成器:每一个生成器都是一个迭代器(但是迭代器不一定是生成器).return就是迭代器: yield的功能类似于return,不同之处在于它返回的是生成器. 什么 ...

  9. DHCP动态管理主机地址

    步骤一:搭建环境 需要Windows 2008 R2 系统  (DHCP服务端)以及 CentOS7 系统客户机(DHCP客户机) 安装DHCP服务程序(这里提示读者,一般安装好CentOS系统之后, ...

  10. C++程序员学Python

    目录 C++程序员学Python 第二章.变量和数据类型 1.注释语句前用#: 2.常用于大小写函数: 第三章.列表 1.列表简述 2.修改,增加,插入,删除列表元素 第四章操作列表 1.遍历 2.创 ...