django middleware簡單分析

在閱讀資料的時候,經常見到資料上說,django處理請求流程的時候,是先middleware處理,如果沒有返回response,那么才到我們寫的視圖view中去處理(包括函數視圖和對象視圖【基于django-restframe-work】)

那么django的middleware是在什么時候,如何被加載,middleware又做了些什么處理呢?
首先要明確middleware是一個類,他有一些固定名字的一系列方法(process_系列),從django1.10版本起,middleware是繼承自django/utils/deprecations中的MiddlewareMixin類,這是一個可調用的對象,其代碼如下:

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

其他中間件類可以繼承這個類,然后自己實現中間件中固定的方法,從而實現自己的中間件。

現在我們從頭開始梳理django處理request的流程,進而窺探中間件的處理過程。
先看WSGIHandler類

class WSGIHandler(base.BaseHandler):
    request_class = WSGIRequest

    def __init__(self, *args, **kwargs):
        super(WSGIHandler, self).__init__(*args, **kwargs)
        self.load_middleware()

    def __call__(self, environ, start_response):
        set_script_prefix(get_script_name(environ))
        signals.request_started.send(sender=self.__class__, environ=environ)
        try:
            request = self.request_class(environ)
            print "request.COOKIES: ", request.COOKIES
            print "request.HTTP_AUTHORIZATION: ", request.META.get('HTTP_AUTHORIZATION','No HTTP_AUTHORIZATION')
        except UnicodeDecodeError:
            logger.warning(
                'Bad Request (UnicodeDecodeError)',
                exc_info=sys.exc_info(),
                extra={
                    'status_code': 400,
                }
            )
            response = http.HttpResponseBadRequest()
        else:
            response = self.get_response(request)

        response._handler_class = self.__class__

        status = '%d %s' % (response.status_code, response.reason_phrase)
        response_headers = [(str(k), str(v)) for k, v in response.items()]
        for c in response.cookies.values():
            response_headers.append((str('Set-Cookie'), str(c.output(header=''))))
        start_response(force_str(status), response_headers)
        if getattr(response, 'file_to_stream', None) is not None and environ.get('wsgi.file_wrapper'):
            response = environ['wsgi.file_wrapper'](response.file_to_stream)
        
        print "type(response), response: ", type(response), response
#        print "response.cookies: ", response.items()[0][1]
#        print "response.headers: ", response._headers
        return response

這里面我們先重點關注_init_函數中的self.load_middleware()和_call_函數中的response = self.get_response(request)
load_middleware的源碼如下:

    def load_middleware(self):
        """
        Populate middleware lists from settings.MIDDLEWARE (or the deprecated
        MIDDLEWARE_CLASSES).

        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 = []

        if settings.MIDDLEWARE is None:
            warnings.warn(
                "Old-style middleware using settings.MIDDLEWARE_CLASSES is "
                "deprecated. Update your middleware and use settings.MIDDLEWARE "
                "instead.", RemovedInDjango20Warning
            )
            handler = convert_exception_to_response(self._legacy_get_response)
            for middleware_path in settings.MIDDLEWARE_CLASSES:
                mw_class = import_string(middleware_path)
                try:
                    mw_instance = mw_class()
                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

                if hasattr(mw_instance, 'process_request'):
                    self._request_middleware.append(mw_instance.process_request)
                if hasattr(mw_instance, 'process_view'):
                    self._view_middleware.append(mw_instance.process_view)
                if hasattr(mw_instance, 'process_template_response'):
                    self._template_response_middleware.insert(0, mw_instance.process_template_response)
                if hasattr(mw_instance, 'process_response'):
                    self._response_middleware.insert(0, mw_instance.process_response)
                if hasattr(mw_instance, 'process_exception'):
                    self._exception_middleware.insert(0, mw_instance.process_exception)
        else:
            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 six.text_type(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

self.load_middleware()主要作用就是去settings配置文件讀取設置的middleware,然后初始化WSGIHandler類中的各個middleware的相關變量,這些變量主要包括self._request_middleware,self._view_middleware,self._response_middleware等存放中間件方法的列表。

WSGIHandler的_call_函數中的response = self.get_response(request),這也是django處理request的入口

    def get_response(self, request):
        """Return an HttpResponse object for the given HttpRequest."""
        # Setup default url resolver for this thread
        set_urlconf(settings.ROOT_URLCONF)

        response = self._middleware_chain(request)

        # This block is only needed for legacy MIDDLEWARE_CLASSES; if
        # MIDDLEWARE is used, self._response_middleware will be empty.
        try:
            # Apply response middleware, regardless of the response
            for middleware_method in self._response_middleware:
                response = middleware_method(request, response)
                # Complain if the response middleware returned None (a common error).
                if response is None:
                    raise ValueError(
                        "%s.process_response didn't return an "
                        "HttpResponse object. It returned None instead."
                        % (middleware_method.__self__.__class__.__name__))
        except Exception:  # Any exception should be gathered and handled
            signals.got_request_exception.send(sender=self.__class__, request=request)
            response = self.handle_uncaught_exception(request, get_resolver(get_urlconf()), sys.exc_info())

        response._closable_objects.append(request)

        # If the exception handler returns a TemplateResponse that has not
        # been rendered, force it to be rendered.
        if not getattr(response, 'is_rendered', True) and callable(getattr(response, 'render', None)):
            response = response.render()

        if response.status_code == 404:
            logger.warning(
                'Not Found: %s', request.path,
                extra={'status_code': 404, 'request': request},
            )

        return response

get_response函數中重點關注response = self._middleware_chain(request)這句。self._middleware_chain在WSGIHandler調用_init_的時候調用self.load_middleware時完成初始化的。當settings中的middleware是用MIDDLEWARE_CLASSES 表示時,_middleware_chain其實就是一個被裝飾的_get_response函數,當settings中的middleware是MIDDLEWARE表示時,_middleware_chain是一個middleware對象,這個middleware對象中的get_response方法是前面加載的middleware的一個合集(個人理解表述)。具體可以參見上面self.load_middleware的源碼。

下面看_get_response,也就是真正處理request的函數,看明白了這個函數,也就基本明白了django處理request的流程

    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.
        """
        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_args, callback_kwargs = resolver_match
        request.resolver_match = resolver_match

        # Apply view middleware
        for middleware_method in self._view_middleware:
            response = middleware_method(request, callback, callback_args, callback_kwargs)
            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:
                response = self.process_exception_by_middleware(e, request)

        # Complain if the view returned None (a common error).
        if response is None:
            if isinstance(callback, types.FunctionType):    # FBV
                view_name = callback.__name__
            else:                                           # CBV
                view_name = callback.__class__.__name__ + '.__call__'

            raise ValueError(
                "The view %s.%s didn't return an HttpResponse object. It "
                "returned None instead." % (callback.__module__, view_name)
            )

        # If the response supports deferred rendering, apply template
        # response middleware and then render the response
        elif hasattr(response, 'render') and callable(response.render):
            for middleware_method in self._template_response_middleware:
                response = middleware_method(request, response)
                # Complain if the template response middleware returned None (a common error).
                if response is None:
                    raise ValueError(
                        "%s.process_template_response didn't return an "
                        "HttpResponse object. It returned None instead."
                        % (middleware_method.__self__.__class__.__name__)
                    )

            try:
                response = response.render()
            except Exception as e:
                response = self.process_exception_by_middleware(e, request)

        return response

在_get_response函數中,首先解析訪問的url,從而獲得后臺開發者自己寫的view處理函數,也就是callback, callback_args, callback_kwargs = resolver_match中的callback,真正調用在wrapped_callback = self.make_view_atomic(callback),從_get_response的執行順序我們就可以看出,只有在所有的middleware執行完后還沒有獲得response,才會執行開發者所寫的view函數,這也是開頭說的,django處理request流程,現有middleware開始,最后才到view函數。
在django的1.10版本源碼中,并沒有看到誰去顯示的調用各個中間件的各種函數,比如process_request,那么middleware中的process_request等一些列函數誰去調用呢?其實關鍵點在_middleware_chain函數。前面提到,在django的1.10版本以前,各個中間件中的函數在load_middleware的時候放到固定的函數列表中,然后在固定的流程去執行這些函數,但是從1.10版本起,并沒有地方顯示的調用,剛剛說了,關鍵點在于1.10版本以后,_middleware_chain已經變成了一個特殊的middleware對象了,這個middleware對象中的get_response函數在每一次加載新的中間件時被迭代更新,從而包含了前面加載的中間件。所以在最后執行middleware_chain的時候就相當于調用了中間件類的_call_方法,這個_call_去遞歸調用前面加載的中間件的_call_方法,從而調用每一個中間件的定義的process*系列函數。這是一個難以理解的地方,好好理解load_middelware函數中的函數convert_exception_to_response,就可以明白這個點。

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, assigned=available_attrs(get_response))
    def inner(request):
        try:
            response = get_response(request)
        except Exception as exc:
            response = response_for_exception(request, exc)
        return response
    return inner

當難以理解某段代碼的時候,可以寫一個小例子測試實驗一下。

from functools import wraps

def available_attrs(fn):
    """
    Return the list of functools-wrappable attributes on a callable.
    This is required as a workaround for http://bugs.python.org/issue3445
    under Python 2.
    """
    WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__doc__')

    return tuple(a for a in WRAPPER_ASSIGNMENTS if hasattr(fn, a))

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, assigned=available_attrs(get_response))
    def inner():
        response = None
        try:
            response = get_response()
        except Exception as exc:
            print exc
        return response
    return inner

def get_response():
    print "xxxxx"

class A1(object):
    def __init__(self, f):
        self.f = f
        print "A1 init"

    def __call__(self, *args, **kwargs):
        self.f()
        print "A1 call"

class A2(object):
    def __init__(self, f):
        self.f = f
        print "A2 init"

    def __call__(self, *args, **kwargs):
        self.f()
        print "A2 call"

class A3(object):
    def __init__(self, f):
        self.f = f
        print "A3 init"

    def __call__(self, *args, **kwargs):
        self.f()
        print "A3 call"


f = convert_exception_to_response(get_response)
# print dir(f)
f = convert_exception_to_response(A1(f))
# print dir(f)
# f.f()
f = convert_exception_to_response(A2(f))
# print dir(f)
f.f()
f = convert_exception_to_response(A3(f))
# print type(f)
# print dir(f)
# print type(available_attrs)
# print dir(available_attrs)
f()

輸出結果為

A1 init
A2 init
xxxxx
A1 call
A3 init
xxxxx
A1 call
A2 call
A3 call

通過小例子,就比較清晰的看到convert_exception_to_response函數做了什么。

比如django.contrib.auth.middleware.AuthenticationMiddleware中的認證函數process_request就是在這里被調用的。

class AuthenticationMiddleware(MiddlewareMixin):
    def process_request(self, request):
        assert hasattr(request, 'session'), (
            "The Django authentication middleware requires session middleware "
            "to be installed. Edit your MIDDLEWARE%s setting to insert "
            "'django.contrib.sessions.middleware.SessionMiddleware' before "
            "'django.contrib.auth.middleware.AuthenticationMiddleware'."
        ) % ("_CLASSES" if settings.MIDDLEWARE is None else "")
        request.user = SimpleLazyObject(lambda: get_user(request))

網上的資料說middleware繼承MiddlewareMixin是從django的1.10版本開始的,前面的版本是沒有繼承對象的,也就是傳統的中間件(legacy middleware)

總結下來就是,django 1.10版本以前,所有的middlware的方法都是加入到特定的數組中的,然后依次調用數組的中方法處理request和response。1.10版本起,middleware是一個可調用對象,process_request,get_response, process_response在直接調用meddleware對象時通過調用call方法調用對應的函數。比如用戶認證的AuthenticationMiddleware,就是初始化request.user。
借用網上的一張圖片:

image.png

中間件的應用場景
由于中間件工作在 視圖函數執行前、執行后(像不像所有視圖函數的裝飾器!)適合所有的請求/一部分請求做批量處理

1、做IP限制
放在中間件類的列表中,阻止某些IP訪問了;

2、URL訪問過濾
如果用戶訪問的是login視圖(放過)
如果訪問其他視圖(需要檢測是不是有session已經有了放行,沒有返回login),這樣就省得在 多個視圖函數上寫裝飾器了!

3、緩存(還記得CDN嗎?)
客戶端請求來了,中間件去緩存看看有沒有數據,有直接返回給用戶,沒有再去邏輯層 執行視圖函數

參考來源:
https://docs.djangoproject.com/en/2.0/topics/http/middleware/
https://code.ziqiangxuetang.com/django/django-middleware.html
http://www.cnblogs.com/huchong/p/7819296.html
http://daoluan.net/%E5%AD%A6%E4%B9%A0%E6%80%BB%E7%BB%93/2013/09/13/decode-django-have-look-at-middleware.html

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容