這片文章將討論下面內容:
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
假如你有一個應用,它的用戶是不同時區的人。你想讓他們在訪問任何頁面的時候都能顯示正確的時區,想讓所有的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將會被省略,不會被執行。