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...

参考资料

Django中间件 --刘江

Middleware¶

posted @ 2020-12-11 17:01  luozx207  阅读(334)  评论(0编辑  收藏  举报