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也处理),处理的结果也变了。
详情还是看文档吧。

四、最后

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335

推荐阅读更多精彩内容

  • 在阅读资料的时候,经常见到资料上说,django处理请求流程的时候,是先middleware处理,如果没有返回re...
    llicety阅读 2,489评论 0 4
  • Django Middleware 这里说的是 Django 1.8.7 的中间件官方文档在这里 Middlewa...
    raku阅读 167评论 0 0
  • 中间件是一个钩子框架,它们可以介入Django 的请求和响应处理过程。它是一个轻量级、底层的“插件”系统,用于在全...
    低吟浅唱1990阅读 512评论 0 0
  • 假设我们有如下中间件: setting.py文件 Django中间件的五个方法调用顺序如下: process_re...
    naralv阅读 817评论 0 3
  • django 从请求到返回都经历了什么 从runserver说起 ruserver是使用django自己的web ...
    星丶雲阅读 315评论 1 1