在有些場合,需要對Django處理的每個request都執行某段代碼。 這類代碼可能是在view處理之前修改傳入的request,或者記錄日志信息以便于調試,等等。
這類功能可以用Django的中間件框架來實現,該框架由切入到Django的request/response處理過程中的鉤子集合組成。 這個輕量級低層次的plug-in系統,能用于全面的修改Django的輸入和輸出。
每個中間件組件都用于某個特定的功能。 如果你是順著這本書讀下來的話,你應該已經多次見到“中間件”了
第12章中所有的session和user工具都籍由一小簇中間件實現(例如,由中間件設定view中可見的request.session 和 request.user )。
第13章討論的站點范圍cache實際上也是由一個中間件實現,一旦該中間件發現與view相應的response已在緩存中,就不再調用對應的view函數。
這一章將深入到中間件及其工作機制中,并闡述如何自行編寫中間件。
我們從一個簡單的例子開始。
高流量的站點通常需要將Django部署在負載平衡proxy(參見第20章)之后。 這種方式將帶來一些復雜性,其一就是每個request中的遠程IP地址(request.META["REMOTE_IP"])將指向該負載平衡proxy,而不是發起這個request的實際IP。 負載平衡proxy處理這個問題的方法在特殊的 X-Forwarded-For 中設置實際發起請求的IP。
因此,需要一個小小的中間件來確保運行在proxy之后的站點也能夠在request.META["REMOTE_ADDR"]
中得到正確的IP地址:
class SetRemoteAddrFromForwardedFor(object):
def process_request(self, request):
try:
real_ip = request.META['HTTP_X_FORWARDED_FOR']
except KeyError:
pass
else:
# HTTP_X_FORWARDED_FOR can be a comma-separated list of IPs.
# Take just the first one.
real_ip = real_ip.split(",")[0]
request.META['REMOTE_ADDR'] = real_ip
(Note: Although the HTTP header is called X-Forwarded-For , Django makes it available asrequest.META['HTTP_X_FORWARDEDFOR'] . With the exception of content-length and content-type , any HTTP headers in the request are converted to request.META keys by converting all characters to uppercase, replacing any hyphens with underscores and adding an HTTP prefix to the name.)
一旦安裝了該中間件(參見下一節),每個request中的 X-Forwarded-For 值都會被自動插入到request.META['REMOTE_ADDR'] 中。這樣,Django應用就不需要關心自己是否位于負載平衡proxy之后;簡單讀取 request.META['REMOTE_ADDR'] 的方式在是否有proxy的情形下都將正常工作。
實際上,為針對這個非常常見的情形,Django已將該中間件內置。 它位于 django.middleware.http 中, 下一節將給出這個中間件相關的更多細節。
如果按順序閱讀本書,應當已經看到涉及到中間件安裝的多個示例,因為前面章節的許多例子都需要某些特定的中間件。 出于完整性考慮,下面介紹如何安裝中間件。
要啟用一個中間件,只需將其添加到配置模塊的 MIDDLEWARE_CLASSES 元組中。 在 MIDDLEWARE_CLASSES 中,中間件組件用字符串表示: 指向中間件類名的完整Python路徑。 例如,下面是 django-admin.py startproject創建的缺省 MIDDLEWARE_CLASSES :
MIDDLEWARE_CLASSES = (
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
)
Django項目的安裝并不強制要求任何中間件,如果你愿意, MIDDLEWARE_CLASSES 可以為空。
這里中間件出現的順序非常重要。 在request和view的處理階段,Django按照 MIDDLEWARE_CLASSES 中出現的順序來應用中間件,而在response和異常處理階段,Django則按逆序來調用它們。 也就是說,Django將MIDDLEWARE_CLASSES 視為view函數外層的順序包裝子: 在request階段按順序從上到下穿過,而在response則反過來。
現在,我們已經知道什么是中間件和怎么安裝它,下面將介紹中間件類中可以定義的所有方法。
在中間件類中, init() 方法用于執行系統范圍的設置。
出于性能的考慮,每個已啟用的中間件在每個服務器進程中只初始化 一 次。 也就是說 init() 僅在服務進程啟動的時候調用,而在針對單個request處理時并不執行。
對一個middleware而言,定義 init() 方法的通常原因是檢查自身的必要性。 如果 init() 拋出異常django.core.exceptions.MiddlewareNotUsed ,則Django將從middleware棧中移出該middleware。 可以用這個機制來檢查middleware依賴的軟件是否存在、服務是否運行于調試模式、以及任何其它環境因素。
在中間件中定義 init() 方法時,除了標準的 self 參數之外,不應定義任何其它參數。
這個方法的調用時機在Django接收到request之后,但仍未解析URL以確定應當運行的view之前。 Django向它傳入相應的 HttpRequest 對象,以便在方法中修改。
process_request() 應當返回 None 或 HttpResponse 對象.
如果返回 None , Django將繼續處理這個request,執行后續的中間件, 然后調用相應的view.
這個方法的調用時機在Django執行完request預處理函數并確定待執行的view之后,但在view函數實際執行之前。
表15-1列出了傳入到這個View預處理函數的參數。
表 15-1. 傳入process_view()的參數 | 參數 | 說明 |
---|---|---|
request | The HttpRequest object. | |
view | The Python function that Django will call to handle this request. This is the actual function object itself, not the name of the function as a string. | |
args | 將傳入view的位置參數列表,但不包括request 參數(它通常是傳 入view的第一個參數) | |
kwargs | 將傳入view的關鍵字參數字典. |
Just like process_request() , process_view() should return either None or an HttpResponse object.
If it returns None , Django will continue processing this request, executing any other middleware and then the appropriate view.
這個方法的調用時機在Django執行view函數并生成response之后。 Here, the processor can modify the content of a response. One obvious use case is content compression, such as gzipping of the request’s HTML.
這個方法的參數相當直觀: request 是request對象,而 response 則是從view中返回的response對象。 requestis the request object, and response is the response object returned from the view.
不同可能返回 None 的request和view預處理函數, process_response() 必須 返回 HttpResponse 對象. 這個response對象可以是傳入函數的那一個原始對象(通常已被修改),也可以是全新生成的。 That response could be the original one passed into the function (possibly modified) or a brand-new one.
這個方法只有在request處理過程中出了問題并且view函數拋出了一個未捕獲的異常時才會被調用。 這個鉤子可以用來發送錯誤通知,將現場相關信息輸出到日志文件, 或者甚至嘗試從錯誤中自動恢復。
這個函數的參數除了一貫的 request 對象之外,還包括view函數拋出的實際的異常對象 exception 。
process_exception() 應當返回 None 或 HttpResponse 對象.
如果返回 None , Django將用框架內置的異常處理機制繼續處理相應request。
備注
Django自帶了相當數量的中間件類(將在隨后章節介紹),它們都是相當好的范例。 閱讀這些代碼將使你對中間件的強大有一個很好的認識。
在Djangos wiki上也可以找到大量的社區貢獻的中間件范例:http://code.djangoproject.com/wiki/ContributedMiddlewarehttp://code.djangoproject.com/wiki/ContributedMiddleware
Django自帶若干內置中間件以處理常見問題,將從下一節開始討論。
中間件類: django.contrib.auth.middleware.AuthenticationMiddleware .django.contrib.auth.middleware.AuthenticationMiddleware .
這個中間件激活認證支持功能. 它在每個傳入的 HttpRequest 對象中添加代表當前登錄用戶的 request.user 屬性。 It adds the request.user attribute, representing the currently logged-in user, to every incomingHttpRequest object.
完整的細節請參見第12章。
Middleware class: django.middleware.common.CommonMiddleware .
這個中間件為完美主義者提供了一些便利:
_禁止
DISALLOWED_USER_AGENTS
列表中所設置的user agent訪問_ :一旦提供,這一列表應當由已編譯的正則表達式對象組成,這些對象用于匹配傳入的request請求頭中的user-agent域。 下面這個例子來自某個配置文件片段:
import re
DISALLOWED_USER_AGENTS = (
re.compile(r'^OmniExplorer_Bot'),
re.compile(r'^Googlebot')
)
請注意 import re ,因為 DISALLOWED_USER_AGENTS 要求其值為已編譯的正則表達式(也就是 re.compile()的返回值)。
_依據
APPEND_SLASH
和PREPEND_WWW
的設置執行URL重寫_ :如果 APPEND_SLASH 為 True , 那些尾部沒有斜杠的URL將被重定向到添加了斜杠的相應URL,除非path的最末組成部分包含點號。 因此,foo.com/bar 會被重定向到 foo.com/bar/ , 但是 foo.com/bar/file.txt 將以不變形式通過。如果 PREPEND_WWW 為 True , 那些缺少先導www.的URLs將會被重定向到含有先導www.的相應URL上。 will be redirected to the same URL with a leading www..
這兩個選項都是為了規范化URL。 其后的哲學是每個URL都應且只應當存在于一處。 技術上來說,URLexample.com/bar 與 example.com/bar/ 及 www.example.com/bar/ 都互不相同。
_依據
USE_ETAGS
的設置處理Etag_ : ETags 是HTTP級別上按條件緩存頁面的優化機制。 如果USE_ETAGS 為 True ,Django針對每個請求以MD5算法處理頁面內容,從而得到Etag, 在此基礎上,Django將在適當情形下處理并返回 Not Modified 回應(譯注:請注意,還有一個條件化的 GET 中間件, 處理Etags并干得更多,下面馬上就會提及。
中間件類 django.middleware.gzip.GZipMiddleware .
這個中間件自動為能處理gzip壓縮(包括所有的現代瀏覽器)的瀏覽器自動壓縮返回]內容。 這將極大地減少Web服務器所耗用的帶寬。 代價是壓縮頁面需要一些額外的處理時間。
相對于帶寬,人們一般更青睞于速度,但是如果你的情形正好相反,盡可啟用這個中間件。
Middleware class: django.middleware.http.ConditionalGetMiddleware .
這個中間件對條件化 GET 操作提供支持。 如果response頭中包括 Last-Modified 或 ETag 域,并且request頭中包含 If-None-Match 或 If-Modified-Since 域,且兩者一致,則該response將被response 304(Not modified)取代。 對 ETag 的支持依賴于 USE_ETAGS 配置及事先在response頭中設置 ETag 域。稍前所討論的通用中間件可用于設置response中的 ETag 域。 As discussed above, the ETag header is set by the Common middleware.
此外,它也將刪除處理 HEAD request時所生成的response中的任何內容,并在所有request的response頭中設置 Date 和 Content-Length 域。
Middleware class: django.middleware.http.SetRemoteAddrFromForwardedFor .
這是我們在 什么是中間件 這一節中所舉的例子。 在 request.META['HTTP_X_FORWARDED_FOR'] 存在的前提下,它根據其值來設置 request.META['REMOTE_ADDR'] 。在站點位于某個反向代理之后的、每個request的REMOTE_ADDR 都被指向 127.0.0.1 的情形下,這一功能將非常有用。 It sets request.META['REMOTE_ADDR']based on request.META['HTTP_X_FORWARDED_FOR'] , if the latter is set. This is useful if you’re sitting behind a reverse proxy that causes each request’s REMOTE_ADDR to be set to 127.0.0.1 .
紅色警告!
這個middleware并 不 驗證 HTTP_X_FORWARDED_FOR 的合法性。
如果站點并不位于自動設置 HTTP_X_FORWARDED_FOR 的反向代理之后,請不要使用這個中間件。 否則,因為任何人都能夠偽造 HTTP_X_FORWARDED_FOR 值,而 REMOTE_ADDR 又是依據 HTTP_X_FORWARDED_FOR 來設置,這就意味著任何人都能夠偽造IP地址。
只有當能夠絕對信任 HTTP_X_FORWARDED_FOR 值得時候才能夠使用這個中間件。
Middleware class: django.contrib.sessions.middleware.SessionMiddleware .
這個中間件激活會話支持功能. 細節請參見第12章。 See Chapter 14 for details.
Middleware classes: django.middleware.cache.UpdateCacheMiddleware anddjango.middleware.cache.FetchFromCacheMiddleware .
這些中間件互相配合以緩存每個基于Django的頁面。 已在第13章中詳細討論。
Middleware class: django.middleware.transaction.TransactionMiddleware .
這個中間件將數據庫的 COMMIT 或 ROLLBACK 綁定到request/response處理階段。 如果view函數成功執行,則發出 COMMIT 指令。 如果view函數拋出異常,則發出 ROLLBACK 指令。
這個中間件在棧中的順序非常重要。 其外層的中間件模塊運行在Django缺省的 保存-提交 行為模式下。 而其內層中間件(在棧中的其后位置出現)將置于與view函數一致的事務機制的控制下。
關于數據庫事務處理的更多信息,請參見附錄C。