django middleware

参考:
Django 源码学习(3)——中间件
Django Middleware官方文档
Django==2.0.4源码

一、相关代码分布

middleware加载:
django.core.handlers.base.BaseHandlerload_middleware()
运行时:
django.utils.deprecation.MiddlewareMixin
django.core.handlers.exception.convert_exception_to_response
django.core.handlers.base.BaseHandlerget_response()

二、源码分析

中间件的操作对象是BaseHandler_middleware_chain属性。

    def load_middleware(self):
        """
        Populate middleware lists from settings.MIDDLEWARE.

        Must be called after the environment is fixed (see __call__ in subclasses).
        """
        self._request_middleware = []
        self._view_middleware = []
        self._template_response_middleware = []
        self._response_middleware = []
        self._exception_middleware = []

        handler = convert_exception_to_response(self._get_response)
        for middleware_path in reversed(settings.MIDDLEWARE):
            middleware = import_string(middleware_path)
            try:
                mw_instance = middleware(handler)
            except MiddlewareNotUsed as exc:
                if settings.DEBUG:
                    if str(exc):
                        logger.debug('MiddlewareNotUsed(%r): %s', middleware_path, exc)
                    else:
                        logger.debug('MiddlewareNotUsed: %r', middleware_path)
                continue

            if mw_instance is None:
                raise ImproperlyConfigured(
                    'Middleware factory %s returned None.' % middleware_path
                )

            if hasattr(mw_instance, 'process_view'):
                self._view_middleware.insert(0, mw_instance.process_view)
            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 = convert_exception_to_response(mw_instance)

        # We only assign to this when initialization is complete as it is used
        # as a flag for initialization being complete.
        self._middleware_chain = handler

1、 convert_exception_to_response的作用

convert_exception_to_response(get_response)函数可用作装饰器,不过,在方法load_middleware()中是直接调用。
该函数接受一个可调用对象get_response作为入参,主要作用是包裹可调用对象get_response,当调用get_response抛出异常时,将异常转换为response,该response的status_code为4xx或500。
该函数自动作用于每个middleware(见上述代码中的两次调用),确保每个middleware都不会抛出异常,从而使得栈中的上一个middleware收到的是一个response对象(为什么是上一个见下文),而不是exception对象。
上述内容大体上是该函数的help文档,如下:

def convert_exception_to_response(get_response):
    """
    Wrap the given get_response callable in exception-to-response conversion.

    All exceptions will be converted. All known 4xx exceptions (Http404,
    PermissionDenied, MultiPartParserError, SuspiciousOperation) will be
    converted to the appropriate response, and all other exceptions will be
    converted to 500 responses.

    This decorator is automatically applied to all middleware to ensure that
    no middleware leaks an exception and that the next middleware in the stack
    can rely on getting a response instead of an exception.
    """
    @wraps(get_response)
    def inner(request):
        try:
            response = get_response(request)
        except Exception as exc:
            response = response_for_exception(request, exc)
        return response
    return inner

2、 _middleware_chain的实质

接着分析load_middleware()方法,其大体思路是使用convert_exception_to_response将各个middleware包裹起来,然后通过中间变量handler来串联,最后赋值给_middleware_chain属性。
所以该属性名副其实。
变量handler初始化使用的是方法_get_response

3、 排序

在方法load_middleware中,对三类middleware作了处理,分别是:_view_middleware_template_response_middleware_exception_middleware
其中,_view_middleware是按照settings.MIDDLEWARE的正序排列,其余二者为逆序。这些列表里,都被插入了middleware相对应的方法对象。
_middleware_chain属性由于是经过一次次包裹而来,按照代码逻辑,也是正序。

另外,_request_middleware_response_middleware应该是已被废弃。

那么,这些属性间有什么关联?

4、当一个Request进入Django后

当一个Request进入Django后,会调用BaseHandlerget_response(request)方法。
该方法调用_middleware_chain属性:
response = self._middleware_chain(request)
middleware依照顺序挨个调用,如果之前的middleware一直没有返回response对象,那么就会进入最后被包裹的可调用对象:BaseHandler_get_response(request)
在该方法中,会依次尝试调用_view_middleware中的middleware的方法,_template_response_middleware的处理也类似。如果在其中某一步抛出了异常,那么调用 _exception_middleware中的middleware的方法。

从上述可见,如果没有返回response的话,每个middleware都会被调用。如果能够走到BaseHandlerget_response(request)方法,才会有机会使用_view_middleware_template_response_middleware_exception_middleware
由于上述三个列表里面是相应middleware的方法,所以只有拥有对应方法的middleware才会走到。同时,一旦有返回对象,立即停止继续调用下一个列表中的方法,即“短路”。

三、middleware的定义

按理来说应该先明白middleware才好理解上述的内容,但是我个人基本上是按照这样一个过程了解过来的,所以这部分就到最后了。

大体上学习官方文档。

1、什么是middleware

middleware是Django的一套置于Request/Response之间的钩子框架。
middleware是可调用的(callable),传入一个Request,返回一个Response,和view一样。

middleware可以通过工厂函数生成,也可以定义为一个callable Class

class SimpleMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response
        # One-time configuration and initialization.

    def __call__(self, request):
        # Code to be executed for each request before
        # the view (and later middleware) are called.

        response = self.get_response(request)

        # Code to be executed for each request/response after
        # the view is called.

        return response

middleware__init__(self, get_response)方法必须有一个入参get_response,该入参是同样是可调用的。由之前的分析可知,其实就是下一个包裹好的middleware,这样才能将各个middleware关联起来。具体代码是load_middleware()方法中的这段:

        handler = convert_exception_to_response(self._get_response)
        for middleware_path in reversed(settings.MIDDLEWARE):
            middleware = import_string(middleware_path)
            try:
                mw_instance = middleware(handler)

middleware__call__(self, request)方法必须有一个入参request。在该方法中,通过使用self.get_response(request)来调用下一个被包裹的middleware,这样调用时才会依次调用。

2、Django提供的middleware Mixin

django.utils.deprecation中:

class MiddlewareMixin:
    def __init__(self, get_response=None):
        self.get_response = get_response
        super().__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

django.contrib.auth.middleware.AuthenticationMiddleware就是使用的它(Django自带的基本都用它),而whitenoise.middleware.WhiteNoiseMiddleware就是自己定义的。

大体的过程是,先调用process_request方法尝试获取response,如果没有则通过get_response调用链上的下一个middleware,如果其中一个middleware返回了response,开始调用process_response来对response处理,不会去找链上的下一个middleware,也就是采用了“短路”(short-circuit)原则。

关于短路,文档中使用洋葱作为比喻:

During the request phase, before calling the view, Django applies middleware in the order it’s defined in MIDDLEWARE, top-down.
You can think of it like an onion: each middleware class is a “layer” that wraps the view, which is in the core of the onion. If the request passes through all the layers of the onion (each one calls get_response to pass the request in to the next layer), all the way to the view at the core, the response will then pass through every layer (in reverse order) on the way back out.
If one of the layers decides to short-circuit and return a response without ever calling its get_response, none of the layers of the onion inside that layer (including the view) will see the request or the response. The response will only return through the same layers that the request passed in through.

至于viewtemplateexception怎么处理,之前已经谈到过了。
所以说,一个middleware中,可定义以下几个方法:process_requestprocess_responseprocess_viewprocess_template_responseprocess_exception。可少不可多。

3、middleware的变迁

new:MIDDLEWARE
old:MIDDLEWARE_CLASSES
主要优化了两点:
第一,对response的处理更加短路化。
第二,调整了exception的处理,处理的地方变了(从上述内容可知,convert_exception_to_response可处理,_exception_middleware也处理),处理的结果也变了。
详情还是看文档吧。

四、最后

如果看不懂,到源码里调试一下就懂了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值