1. 程式人生 > >django中的中介軟體機制和執行順序

django中的中介軟體機制和執行順序

這片文章將討論下面內容:

1.什麼是middleware

2.什麼時候使用middleware

3.我們寫middleware必須要記住的東西

4.寫一些middlewares來理解中介軟體的工作過程和要點

 

什麼是middleware

Middleware是修改django request 或者 response物件的鉤子,下面是django文件中的一段描述

Middleware is a framework of hooks into Django’s request/response processing. It’s a light, low-level “plugin” system for globally altering Django’s input or
output.

什麼時候使用middleware

如果你想修改請求,例如被傳送到view中的HttpResquest物件。或者你想修改view返回的HttpResponse物件,這些都可以通過中介軟體來實現。

可能你還想在view執行之前做一些操作,這種情況就可以用middleware來實現。

django提供了一些預設的moddleware,例如:

AuthenticationMiddleware

大家可能頻繁在view使用request.user吧。django想在每個view執行之前把user設定為request的屬性,於是就用了一箇中間件來實現這個目標。所以django提供了可以修改request物件的中介軟體部分:AuthenticationMiddleware

from django.conf import settings
from django.contrib import auth
from django.contrib.auth import load_backend
from django.contrib.auth.backends import RemoteUserBackend
from django.core.exceptions import ImproperlyConfigured
from django.utils.deprecation import MiddlewareMixin
from
django.utils.functional import SimpleLazyObject def get_user(request): if not hasattr(request, '_cached_user'): request._cached_user = auth.get_user(request) return request._cached_user 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)) class RemoteUserMiddleware(MiddlewareMixin): """ Middleware for utilizing Web-server-provided authentication. If request.user is not authenticated, then this middleware attempts to authenticate the username passed in the ``REMOTE_USER`` request header. If authentication is successful, the user is automatically logged in to persist the user in the session. The header used is configurable and defaults to ``REMOTE_USER``. Subclass this class and change the ``header`` attribute if you need to use a different header. """ # Name of request header to grab username from. This will be the key as # used in the request.META dictionary, i.e. the normalization of headers to # all uppercase and the addition of "HTTP_" prefix apply. header = "REMOTE_USER" force_logout_if_no_header = True def process_request(self, request): # AuthenticationMiddleware is required so that request.user exists. if not hasattr(request, 'user'): raise ImproperlyConfigured( "The Django remote user auth middleware requires the" " authentication middleware to be installed. Edit your" " MIDDLEWARE setting to insert" " 'django.contrib.auth.middleware.AuthenticationMiddleware'" " before the RemoteUserMiddleware class.") try: username = request.META[self.header] except KeyError: # If specified header doesn't exist then remove any existing # authenticated remote-user, or return (leaving request.user set to # AnonymousUser by the AuthenticationMiddleware). if self.force_logout_if_no_header and request.user.is_authenticated: self._remove_invalid_user(request) return # If the user is already authenticated and that user is the user we are # getting passed in the headers, then the correct user is already # persisted in the session and we don't need to continue. if request.user.is_authenticated: if request.user.get_username() == self.clean_username(username, request): return else: # An authenticated user is associated with the request, but # it does not match the authorized user in the header. self._remove_invalid_user(request) # We are seeing this user for the first time in this session, attempt # to authenticate the user. user = auth.authenticate(request, remote_user=username) if user: # User is valid. Set request.user and persist user in the session # by logging the user in. request.user = user auth.login(request, user) def clean_username(self, username, request): """ Allow the backend to clean the username, if the backend defines a clean_username method. """ backend_str = request.session[auth.BACKEND_SESSION_KEY] backend = auth.load_backend(backend_str) try: username = backend.clean_username(username) except AttributeError: # Backend has no clean_username method. pass return username def _remove_invalid_user(self, request): """ Remove the current authenticated user in the request which is invalid but only if the user is authenticated via the RemoteUserBackend. """ try: stored_backend = load_backend(request.session.get(auth.BACKEND_SESSION_KEY, '')) except ImportError: # backend failed to load auth.logout(request) else: if isinstance(stored_backend, RemoteUserBackend): auth.logout(request) class PersistentRemoteUserMiddleware(RemoteUserMiddleware): """ Middleware for Web-server provided authentication on logon pages. Like RemoteUserMiddleware but keeps the user authenticated even if the header (``REMOTE_USER``) is not found in the request. Useful for setups when the external authentication via ``REMOTE_USER`` is only expected to happen on some "logon" URL and the rest of the application wants to use Django's authentication mechanism. """ force_logout_if_no_header = False
View Code

假如你有一個應用,它的使用者是不同時區的人。你想讓他們在訪問任何頁面的時候都能顯示正確的時區,想讓所有的views中都能得到使用者自己的timezone資訊。這種情況下可以用session來解決,所以你可以像下面新增一個middleware:

class TimezoneMiddleware(object):
    def process_request(self, request):
        # Assuming user has a OneToOneField to a model called Profile
        # And Profile stores the timezone of the User.
        request.session['timezone'] = request.user.profile.timezone

TimezoneMiddleware是依賴於request.user的,request.user是通過AuthenticationMiddleware來設定的。所以在settings.MIDDLEWARE_CLASSES配置中,TimezoneMiddleware一定要在AuthenticationMiddleware之後。

下面的例子可以得到關於中介軟體順序的更多體會。

 

使用middleware時應該記住的東西

  middlewares 的順序非常重要

  一個middleware只需要繼承object類

  一個middleware可以實現一些方法並且不需要實現所有的方法

  一個middleware可以實現process_request(方法)但是不可以實現process_respose(方法)和process_view方法。這些都很常見,django提供了很多middlewares可以做到。

  一個midd可以實現process_response 方法,但是不需要實現process_request 方法。

 

AuthenticationMiddleware只實現了對請求的處理,並沒有處理響應。

GZipMiddleware 只實現了對響應的處理,並沒有實現對請求和view的處理

import re

from django.utils.cache import patch_vary_headers
from django.utils.deprecation import MiddlewareMixin
from django.utils.text import compress_sequence, compress_string

re_accepts_gzip = re.compile(r'\bgzip\b')


class GZipMiddleware(MiddlewareMixin):
    """
    Compress content if the browser allows gzip compression.
    Set the Vary header accordingly, so that caches will base their storage
    on the Accept-Encoding header.
    """
    def process_response(self, request, response):
        # It's not worth attempting to compress really short responses.
        if not response.streaming and len(response.content) < 200:
            return response

        # Avoid gzipping if we've already got a content-encoding.
        if response.has_header('Content-Encoding'):
            return response

        patch_vary_headers(response, ('Accept-Encoding',))

        ae = request.META.get('HTTP_ACCEPT_ENCODING', '')
        if not re_accepts_gzip.search(ae):
            return response

        if response.streaming:
            # Delete the `Content-Length` header for streaming content, because
            # we won't know the compressed size until we stream it.
            response.streaming_content = compress_sequence(response.streaming_content)
            del response['Content-Length']
        else:
            # Return the compressed content only if it's actually shorter.
            compressed_content = compress_string(response.content)
            if len(compressed_content) >= len(response.content):
                return response
            response.content = compressed_content
            response['Content-Length'] = str(len(response.content))

        # If there is a strong ETag, make it weak to fulfill the requirements
        # of RFC 7232 section-2.1 while also allowing conditional request
        # matches on ETags.
        etag = response.get('ETag')
        if etag and etag.startswith('"'):
            response['ETag'] = 'W/' + etag
        response['Content-Encoding'] = 'gzip'

        return response
參照文件

 

寫一些middlewares

首先確認你有一個django專案,需要一個url和view,並且可以進入這個view。下面我們會對request.user做幾個測試,確認許可權設定好了,並可以在view中正確列印request.user的資訊。

在任意一個app中建立middleware.py檔案。

我有一個叫做books的app,所以檔案的位置是books/middleware.py

class BookMiddleware(object):
    def process_request(self,request):
        print('Middleware executed')

MIDDLEWARE_CLASSES中新增這個中介軟體

MIDDLEWARE_CLASSES = (
    'books.middleware.BookMiddleware',
    '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',
)

對任意的一個url傳送請求

Middleware executed

修改BookMiddleware.process_request如下

class BookMiddleware(object):
    def process_request(self, request):
        print "Middleware executed"
        print request.user

再次訪問一個url,將會引起一個錯誤

'WSGIRequest' object has no attribute 'user'

這是因為request物件還沒有設定user屬性呢。

現在我們改變下middlewares的順序,BookMiddleware 放在 AuthenticationMiddleware之後

MIDDLEWARE_CLASSES = (
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'books.middleware.BookMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
)

訪問一個url,runserver控制檯列印如下

Middleware executed
<username>

這說明middlewares處理request的順序跟settings.MIDDLEWARE_CLASSES中列出的順序是一致的。

可以進一步證實,middleware.py 新增另外一個middleware

class AnotherMiddleware(object):
    def process_request(self, request):
        print "Another middleware executed"

把他也加到MIDDLEWARE_CLASSES

MIDDLEWARE_CLASSES = (
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'books.middleware.BookMiddleware',
    'books.middleware.AnotherMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
)

現在的輸出是:

Middleware executed
<username>
Another middleware executed

在process_request方法中返回HttpResponse,把BookMilldeware改成下面這樣:

class BookMiddleware(object):
    def process_request(self, request):
        print "Middleware executed"
        print request.user
        return HttpResponse("some response")

嘗試下任何一個url,會得到如下輸出:

Middleware executed
<username>

你會注意到下面兩個事情:

  不管你訪問哪個url,自己寫的view處理方法都不執行了,只有“some response” 這樣一種響應。

  AnotherMiddleware.process_request不再被執行。

所以如果Middleware的process_request方法中返回了HttpResponse物件,那麼它之後的中介軟體將被略過,view中的處理方法也被略過。

所以在實際的專案中很少會這麼幹(不過也有些專案會,比如做代理)

註釋掉 "return HttpResponse("some response")" ,兩個middleware才能正常的處理請求。

 

使用process_response

給這兩個middleware新增process_response方法

class AnotherMiddleware(object):
    def process_request(self, request):
        print "Another middleware executed"

    def process_response(self, request, response):
        print "AnotherMiddleware process_response executed"
        return response

class BookMiddleware(object):
    def process_request(self, request):
        print "Middleware executed"
        print request.user
        return HttpResponse("some response")
        #self._start = time.time()

    def process_response(self, request, response):
        print "BookMiddleware process_response executed"
        return response

訪問一些url,得到如下的輸出

Middleware executed
<username>
Another middleware executed
AnotherMiddleware process_response executed
BookMiddleware process_response executed

AnotherMiddleware.process_response() 在BookMiddleware.process_response() 之前執行 而 AnotherMiddleware.process_request() 在BookMiddleware.process_request() 之後執行. 所以process_response()  執行的順序跟 process_request正好相反. process_response() 執行的順序是從最後一箇中間件執行,到倒數第二個,然後直到第一個中介軟體.

 

process_view

django按順序執行中介軟體process_view()的方法,從上到下。類似process_request方法執行的順序。

所以如果任何一個process_view() 返回了HttpResponse物件,那麼在它後面process_view將會被省略,不會被執行。