Django中间件部分源码分析
中间件源码分析
中间件简介
中间件是一个用来处理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中间件部分源码分析的更多相关文章
- Django中间件CsrfViewMiddleware源码分析
Django Documentation csrf保护基于以下: 1, 一个CSRF cookie基于一个随机生成的值,其他网站无法得到,次cookie有CsrfViewMiddleware产生.它与 ...
- django中间件CsrfViewMiddleware源码分析,探究csrf实现
Django Documentation csrf保护基于以下: 1. 一个CSRF cookie 基于一个随机生成的值,其他网站无法得到.此cookie由CsrfViewMiddleware产生.它 ...
- Django搭建及源码分析(三)---+uWSGI+nginx
每个框架或者应用都是为了解决某些问题才出现旦生的,没有一个事物是可以解决所有问题的.如果觉得某个框架或者应用使用很不方便,那么很有可能就是你没有将其使用到正确的地方,没有按开发者的设计初衷来使用它,当 ...
- Django如何启动源码分析
Django如何启动源码分析 启动 我们启动Django是通过python manage.py runsever的命令 解决 这句话就是执行manage.py文件,并在命令行发送一个runsever字 ...
- Django之DRF源码分析(二)---数据校验部分
Django之DRF源码分析(二)---数据校验部分 is_valid() 源码 def is_valid(self, raise_exception=False): assert not hasat ...
- 开源分布式数据库中间件MyCat源码分析系列
MyCat是当下很火的开源分布式数据库中间件,特意花费了一些精力研究其实现方式与内部机制,在此针对某些较为重要的源码进行粗浅的分析,希望与感兴趣的朋友交流探讨. 本源码分析系列主要针对代码实现,配置. ...
- Django rest framework源码分析(3)----节流
目录 Django rest framework(1)----认证 Django rest framework(2)----权限 Django rest framework(3)----节流 Djan ...
- Django rest framework源码分析(1)----认证
目录 Django rest framework(1)----认证 Django rest framework(2)----权限 Django rest framework(3)----节流 Djan ...
- Django rest framework 源码分析 (1)----认证
一.基础 django 2.0官方文档 https://docs.djangoproject.com/en/2.0/ 安装 pip3 install djangorestframework 假如我们想 ...
随机推荐
- Splash的使用
Splash Lua脚本http://localhost:8050 入口及返回值 function main(splash, args) splash:go("http://www.baid ...
- 学习笔记57_WCF基础
参考书籍<WCF揭秘> 参考博客园“xfrog” 1.做一个接口,例如: 2.使用一个类,例如:FirstSrvice这个类,来实现这个接口. 3.建立WCF的 宿主 程序: 4.配 ...
- android studio ndk 环境配置成eclipse模式
gradle.properties: android.useDeprecatedNdk=true build.gradle: android { ... sourceSets.main { jni.s ...
- [考试反思]0903NOIP模拟测试36:复始
因为多次被说颓博客时间太长于是 真香 恢复粘排行榜的传统. 大体上就是,T1A的前三,剩下的T2A的排名,再然后按照T3暴力得分排名. T1是个暴力.3个A的5个得分的.数据点极强爆零率极高. 我的思 ...
- 「刷题」Triple
正解是普通型母函数+FFT. 才学了多项式,做了一道比较好的题了. 首先有三个斧子被偷了. 我们考虑构造一种普通型母函数. 就是说一种多项式吧,我的理解. 系数是方案,下标,也就是所谓的元指数代表的是 ...
- js中的事件绑定的三种方式
1 直接在html标签中绑定 <button onclick = "show()"></button> 注意当你引用的js代码是包裹在window.onlo ...
- MongoDB自学------(1)MongoDB4.0安装
一.环境 操作系统 安装包 安装方式 Ubuntu18.04 mongodb4.0 apt安装 Ubuntu18.04 mongodb4.0 docker安装 二.apt安装 sudo apt-key ...
- 大数据之路week01--自学之集合_2(List)
在学习过了Collection之后,接下来我们将去学习List, 先看API文档: List集合的特有功能:(没有列出Collection也有的功能) A:添加功能 add(int index, E ...
- Java ------ 工厂模式、单例模式
工厂模式 简单工厂模式: 1.创建Car接口 public interface Car { public void drive(); } 2.创建两个实体类,分别实现Car接口 public clas ...
- 快速搭建Jenkins集群
关于Jenkins集群 在Jenkins上同时执行多个任务时,单机性能可能达到瓶颈,使用Jenkins集群可以有效的解决此问题,让多台机器同时处理这些任务可以将压力分散,对单机版Jenkins的单点故 ...