Django中间件源码阅读笔记
执行顺序
按照settings.MIDDLEWARE的顺序,先由上至下执行所有的process_request,然后由上至下执行所有的process_view,最后由下至上执行所有的process_response。如:
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
"apps.api.middleware_mgr.Middleware",
]
中间件的加载
settings.MIDDLEWARE存在的情况下,Django加载中间件的源码:
# django/core/handlers/base.py
class BaseHandler(object):
def __init__(self):
self._request_middleware = None
self._view_middleware = None
self._template_response_middleware = None
self._response_middleware = None
self._exception_middleware = None
self._middleware_chain = None
def load_middleware(self):
# _get_response是一个方法,它的功能是按顺序调用中间件的process_view方法。并且在最核心执行被层层包裹的实际视图
# This method is everything that happens inside the request/response middleware.
# convert_exception_to_response是一个装饰器,相当于是为_get_response加了一个在出错后仍然可以返回HttpResponse的功能
handler = convert_exception_to_response(self._get_response)
# 逆序访问 settings.MIDDLEWARE 中定义的中间件路径
for middleware_path in reversed(settings.MIDDLEWARE):
# 这个middleware是settings.MIDDLEWARE中定义的中间件类,如 <class \'apps.auth2.middleware_mgr.AdMiddleware\'>
middleware = import_string(middleware_path)
# mw_instance是中间件类的实例。
# 由于大多数中间件都继承自MiddlewareMixin,因此初始化中间件实例的时候需要提供一个get_response方法
# 而handler刚好就是我们需要的这个方法
# (见下文MiddlewareMixin的__init__方法)
mw_instance = middleware(handler)
if hasattr(mw_instance, \'process_view\'):
# self._view_middleware 是按照settings.MIDDLEWARE的顺序,由上至下生成的
self._view_middleware.insert(0, mw_instance.process_view)
if hasattr(mw_instance, \'process_template_response\'):
# self._template_response_middleware 是按照settings.MIDDLEWARE的顺序,由下至上生成的
self._template_response_middleware.append(mw_instance.process_template_response)
if hasattr(mw_instance, \'process_exception\'):
# self._exception_middleware 是按照settings.MIDDLEWARE的顺序,由下至上生成的
self._exception_middleware.append(mw_instance.process_exception)
# 思考:为什么不加载process_request和process_response?
# 一定要理解这个地方,其实middleware就像套娃的一层一样
# 它的实例初始化需要接受一个get_response方法,这个方法就相当于这层套娃的内部
# 然后,中间件的实例可以被当做函数调用,即执行中间件实例的__call__方法
# 那么这个中间件实例可以被视为另一个get_response方法,被外层的中间件使用
# 这个handler,就是将当前层中间件实例套上之后的套娃
handler = convert_exception_to_response(mw_instance)
# 只有当所有中间件加载完毕后,才会将_middleware_chain赋值为最终的handler(最外层中间件的实例)
# 这个变量被赋值就是初始化完成的标志
self._middleware_chain = handler
中间件的执行
整个中间件套娃被执行的入口是BaseHandler的get_response方法
# django/core/handlers/base.py
class BaseHandler(object):
def get_response(self, request):
"""Return an HttpResponse object for the given HttpRequest."""
# 根据上文,self._middleware_chain就是最外层中间件的实例,这里调用了最外层中间件的__call__方法
# 然后经过层层中间件抵达核心执行view,再层层递出response,这个response就是最终的结果
response = self._middleware_chain(request)
# This block is only needed for legacy MIDDLEWARE_CLASSES
# if MIDDLEWARE is used, self._response_middleware will be empty.
# 这里回答了load_middleware为什么不加载process_response的问题,self._response_middleware只会在这里用到
# 如果定义了MIDDLEWARE,self._response_middleware将会为空
try:
# Apply response middleware, regardless of the response
for middleware_method in self._response_middleware:
...
return response
看看最外层中间件的__call__
方法,中间件如果继承自MiddlewareMixin,则会执行父类的__call__
方法
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\'):
# 这里执行的是子类(最外层中间件)的process_request方法,即settings.MIDDLEWARE的第一个列出的中间件
# 因此process_request是按由上到下的顺序执行的,并且在load_middleware不需要被加载
# 它的执行顺序由套娃的结构维护
response = self.process_request(request)
# process_request发生错误后将返回一个HttpResponse对象,下层中间件将不会被执行
if not response:
# 没有发生错误,继续执行下层中间件。这个get_response方法实际上是下层中间件实例的__call__方法
response = self.get_response(request)
# response经过下层的一系列中间件返回,执行当前层的process_response,并返回响应
# 最外层的process_response会最后执行
# 因此process_response是按settings.MIDDLEWARE的顺序由下至上执行的,并且在load_middleware不需要被加载
# 它的执行顺序由套娃的结构维护
if hasattr(self, \'process_response\'):
response = self.process_response(request, response)
return response
中间件的最核心是_get_response
方法
def _get_response(self, request):
"""
Resolve and call the view, then apply view, exception, and
template_response middleware. This method is everything that happens
inside the request/response middleware.
"""
# 在这里按照settings.MIDDLEWARE的顺序依次执行中间件的process_view
for middleware_method in self._view_middleware:
# process_view处理失败或者出于一些其他的原因需要中止请求,就返回一个HttpResponse
# 正常情况下,应返回None
response = middleware_method(request, callback, callback_args, callback_kwargs)
if response:
break
if response is None:
# 执行实际的视图
wrapped_callback = self.make_view_atomic(callback)
response = wrapped_callback(request, *callback_args, **callback_kwargs)
# If the response supports deferred rendering, apply template
# response middleware and then render the response
# 如果有渲染的需求才会使用process_template_response方法
elif hasattr(response, \'render\') and callable(response.render):
for middleware_method in self._template_response_middleware:
response = middleware_method(request, response)
...
return response
自定义中间件
五大钩子函数
表头 | 表头 | 表头 | 表头 |
---|---|---|---|
process_request | 请求刚到来,执行视图之前 | 配置列表的正序 | None或者HttpResponse对象 |
process_response | 视图执行完毕,返回响应时 | 逆序 | HttpResponse对象 |
process_view | process_request之后,路由转发到视图,执行视图之前 | 正序 | None或者HttpResponse对象 |
process_exception | 视图执行中发生异常时 | 逆序 | None或者HttpResponse对象 |
process_template_response | 视图刚执行完毕,process_response之前 | 逆序 | 实现了render方法的响应对象 |
例子
注意middleware方法的输入参数都是固定的
# utils/my_middleware.py
from django.utils.deprecation import MiddlewareMixin
class MyMiddleware1(MiddlewareMixin):
def __str__(self):
return "MyMiddleware1"
def process_request(self, request):
print(self, " processing request...")
def process_view(self, request, view_func, view_args, view_kwargs):
print(self, " processing view...")
def process_response(self, request, response):
print(self, " processing response...")
return response
def process_exception(self, request, exception):
print(self, " processing exception...")
class MyMiddleware2(MiddlewareMixin):
def __str__(self):
return "MyMiddleware2"
def process_request(self, request):
print(self, " processing request...")
def process_view(self, request, view_func, view_args, view_kwargs):
print(self, " processing view...")
def process_response(self, request, response):
print(self, " processing response...")
return response
def process_exception(self, request, exception):
print(self, " processing exception...")
# settings.py
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\',
\'utils.my_middleware.MyMiddleware1\',
\'utils.my_middleware.MyMiddleware2\'
]
# urls.py
from django.conf.urls import url
from demo.views import mid_test
urlpatterns = [
url(r"^midtest/", mid_test),
]
# views.py
from django.http import HttpResponse
def mid_test(request):
print(\'in mid_test view\')
return HttpResponse(\'ok\')
访问 http://127.0.0.1:8000/midtest/ ,可在控制台得到这样的输出,验证了之前关于中间件执行顺序的理论
MyMiddleware1 processing request...
MyMiddleware2 processing request...
MyMiddleware1 processing view...
MyMiddleware2 processing view...
in mid_test view
MyMiddleware2 processing response...
MyMiddleware1 processing response...
[08/Jun/2020 17:32:54] "GET /midtest/ HTTP/1.1" 200 2
中间件的中断
如果process_request返回了一个HttpResponse对象
class MyMiddleware1(MiddlewareMixin):
def __str__(self):
return "MyMiddleware1"
def process_request(self, request):
print(self, " processing request...")
return HttpResponse("break")
下层中间件将不会被执行,response会按原路返回
MyMiddleware1 processing request...
MyMiddleware1 processing response...
[08/Jun/2020 18:08:21] "GET /midtest/ HTTP/1.1" 200 5
如果process_view返回了一个HttpResponse对象,同样
class MyMiddleware1(MiddlewareMixin):
def __str__(self):
return "MyMiddleware1"
def process_request(self, request):
print(self, " processing request...")
return HttpResponse("break")
MyMiddleware1 processing request...
MyMiddleware2 processing request...
MyMiddleware1 processing view...
MyMiddleware2 processing response...
MyMiddleware1 processing response...
[08/Jun/2020 18:11:00] "GET /midtest/ HTTP/1.1" 200 5
视图异常
如果视图引发了异常,如
from django.http import HttpResponse
def mid_test(request):
print(\'in mid_test view\')
1/0
return HttpResponse(\'ok\')
则Django会逆序调用中间件的process_exception方法,紧接着逆序调用process_response方法
MyMiddleware1 processing request...
MyMiddleware2 processing request...
MyMiddleware1 processing view...
MyMiddleware2 processing view...
in mid_test view
MyMiddleware2 processing exception...
MyMiddleware1 processing exception...
Internal Server Error: /midtest/
Traceback (most recent call last):
File "/home/youmi/code/V36DemoForPython/venv/lib/python3.6/site-packages/django/core/handlers/exception.py", line 41, in inner
response = get_response(request)
File "/home/youmi/code/V36DemoForPython/venv/lib/python3.6/site-packages/django/core/handlers/base.py", line 187, in _get_response
response = self.process_exception_by_middleware(e, request)
File "/home/youmi/code/V36DemoForPython/venv/lib/python3.6/site-packages/django/core/handlers/base.py", line 185, in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/home/youmi/code/V36DemoForPython/demo/views.py", line 6, in mid_test
1/0
ZeroDivisionError: division by zero
MyMiddleware2 processing response...
MyMiddleware1 processing response...
如果process_exception返回了一个HttpResponse对象
class MyMiddleware2(MiddlewareMixin):
def process_exception(self, request, exception):
print(self, " processing exception...")
return HttpResponse(\'not ok\')
页面会显示process_exception返回的内容,而不再使用默认异常处理。并且,此中间件之上的中间件类的process_exception方法不会被调用。但是process_response方法仍然会被逆序调用
MyMiddleware1 processing request...
MyMiddleware2 processing request...
MyMiddleware1 processing view...
MyMiddleware2 processing view...
in mid_test view
MyMiddleware2 processing exception...
MyMiddleware2 processing response...
MyMiddleware1 processing response...