中间件源码分析
中间件简介
中间件是一个用来处理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这种设计思想是我重来没接触过的, 学习完之后感触非常深, 只能感慨别人程序的设计之精妙, 自己还要好好学习.