在有些場合,需要對Django處理的每個request都執行某段代碼。 這類代碼可能是在view處理之前修改傳入的request,或者記錄日志信息以便于調試,等等。
這類功能可以用Django的中間件框架來實現,該框架由切入到Django的request/response處理過程中的鉤子集合組成。 這個輕量級低層次的plug-in系統,能用于全面的修改Django的輸入和輸出。
每個中間件組件都用于某個特定的功能。 如果你是順著這本書讀下來的話,你應該已經多次見到“中間件”了
* 第12章中所有的session和user工具都籍由一小簇中間件實現(例如,由中間件設定view中可見的request.session?和?request.user?)。
* 第13章討論的站點范圍cache實際上也是由一個中間件實現,一旦該中間件發現與view相應的response已在緩存中,就不再調用對應的view函數。
* 第14章所介紹的?flatpages?,?redirects?, 和?csrf?等應用也都是通過中間件組件來完成其魔法般的功能。
這一章將深入到中間件及其工作機制中,并闡述如何自行編寫中間件。
## 什么是中間件
我們從一個簡單的例子開始。
高流量的站點通常需要將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_FORWARDED_FOR']?. 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則反過來。
## 中間件方法
現在,我們已經知道什么是中間件和怎么安裝它,下面將介紹中間件類中可以定義的所有方法。
### Initializer: __init__(self) __init__(self)「初始化]
在中間件類中,?__init__()?方法用于執行系統范圍的設置。
出于性能的考慮,每個已啟用的中間件在每個服務器進程中只初始化?_一_?次。 也就是說?__init__()?僅在服務進程啟動的時候調用,而在針對單個request處理時并不執行。
對一個middleware而言,定義?__init__()?方法的通常原因是檢查自身的必要性。 如果?__init__()?拋出異常django.core.exceptions.MiddlewareNotUsed?,則Django將從middleware棧中移出該middleware。 可以用這個機制來檢查middleware依賴的軟件是否存在、服務是否運行于調試模式、以及任何其它環境因素。
在中間件中定義?__init__()?方法時,除了標準的?self?參數之外,不應定義任何其它參數。
### Request預處理函數: process_request(self, request) process_request(self, request)
這個方法的調用時機在Django接收到request之后,但仍未解析URL以確定應當運行的view之前。 Django向它傳入相應的?HttpRequest?對象,以便在方法中修改。
process_request()?應當返回?None?或?HttpResponse?對象.
* 如果返回?None?, Django將繼續處理這個request,執行后續的中間件, 然后調用相應的view.
* 如果返回?HttpResponse?對象, Django 將不再執行?_任何_?其它的中間件(而無視其種類)以及相應的view。 Django將立即返回該?HttpResponse?.
### View預處理函數: process_view(self, request, view, args, kwargs) process_view(self, request, view, args, kwargs)
這個方法的調用時機在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.
* If it returns an?HttpResponse?object, Django won’t bother calling?_any_?other middleware (of any type) or the appropriate view. Django will immediately return that?HttpResponse?.
### Response后處理函數: process_response(self, request, response) process_response(self, request, response)
這個方法的調用時機在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.
### Exception后處理函數: process_exception(self, request, exception) process_exception(self, request, exception)
這個方法只有在request處理過程中出了問題并且view函數拋出了一個未捕獲的異常時才會被調用。 這個鉤子可以用來發送錯誤通知,將現場相關信息輸出到日志文件, 或者甚至嘗試從錯誤中自動恢復。
這個函數的參數除了一貫的?request?對象之外,還包括view函數拋出的實際的異常對象?exception?。
process_exception()?應當返回 None 或 HttpResponse 對象.
* 如果返回?None?, Django將用框架內置的異常處理機制繼續處理相應request。
* 如果返回?HttpResponse?對象, Django 將使用該response對象,而短路框架內置的異常處理機制。
備注
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服務器所耗用的帶寬。 代價是壓縮頁面需要一些額外的處理時間。
相對于帶寬,人們一般更青睞于速度,但是如果你的情形正好相反,盡可啟用這個中間件。
### 條件化的GET中間件
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?域。
### 反向代理支持 (X-Forwarded-For中間件)
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。