按需内容處理
HTTP用戶端可能發送一些協定頭來告訴服務端它們已經看過了哪些資源。這在擷取網頁(使用HTTP
GET
請求)時非常常見,可以避免發送用戶端已經獲得的完整資料。然而,相同的協定頭可用于所有HTTP方法(
POST
,
PUT
DELETE
, 以及其它)。
對于每一個Django從視圖發回的頁面(響應),都會提供兩個HTTP協定頭:
ETag
和
Last-Modified
。這些協定頭在HTTP響應中是可選的。它們可以由你的視圖函數設定,或者你可以依靠
CommonMiddleware
中間件來設定
ETag
協定頭。
當你的用戶端再次請求相同的資源時,它可能會發送
If-modified-since或者
If-unmodified-since的協定頭,包含之前發送的最後修改時間;或者
If-match或
If-none-match協定頭,包含之前發送的
ETag
。如果頁面的目前版本比對用戶端發送的
ETag
,或者如果資源沒有被修改,會發回304狀态碼,而不是一個完整的回複,告訴用戶端沒有任何修改。根據協定頭,如果頁面被修改了,或者不比對用戶端發送的
ETag
,會傳回412(先決條件失敗,Precondition Failed)狀态碼。
當你需要更多精細化的控制時,你可以使用每個視圖的按需處理函數。
Changed in Django 1.8:
向按需視圖處理添加
If-unmodified-since
協定頭的支援
The condition
condition
有時(實際上是經常),你可以建立一些函數來快速計算出資源的
ETag值或者最後修改時間,并不需要執行建構完整視圖所需的所有步驟。Django可以使用這些函數來為視圖處理提供一個“early bailout”的選項。來告訴用戶端,内容自從上次請求并沒有任何改動。
這兩個函數作為參數傳遞到
django.views.decorators.http.condition
裝飾器中。這個裝時期使用這兩個函數(如果你不能既快又容易得計算出來,你隻需要提供一個)來弄清楚是否HTTP請求中的協定頭比對那些資源。如果它們不比對,會生成資源的一份新的副本,并調用你的普通視圖。
condition
裝飾器的簽名為i:
condition(etag_func=None, last_modified_func=None)
計算ETag的最後修改時間的兩個函數,會以相同的順序傳入
request
對象和相同的參數,就像它們封裝的視圖函數那樣。
last_modified_func
函數應該傳回一個标準的datetime值,它制訂了資源修改的最後時間,或者資源不存在為
None
。傳遞給
etag
裝飾器的函數應該傳回一個表示資源
Etag的字元串,或者資源不存在時為
None
。
用一個例子可以很好展示如何使用這一特性。假設你有這兩個模型,表示一個簡單的部落格系統:
import datetime
from django.db import models
class Blog(models.Model):
...
class Entry(models.Model):
blog = models.ForeignKey(Blog)
published = models.DateTimeField(default=datetime.datetime.now)
...
如果頭版展示最後的部落格文章,僅僅在你添加新文章的時候修改,你可以非常快速地計算出最後修改時間。你需要這個部落格每一篇文章的最後
釋出
日期。實作它的一種方式是:
def latest_entry(request, blog_id):
return Entry.objects.filter(blog=blog_id).latest("published").published
接下來你可以使用這個函數,來為你的頭版視圖事先探測未修改的頁面:
from django.views.decorators.http import condition
@condition(last_modified_func=latest_entry)
def front_page(request, blog_id):
...
隻計算一個值的快捷方式
一個普遍的原則是,如果你提供了計算 ETag_和_最後修改時間的函數,你應該這樣做:你并不知道HTTP用戶端會發給你哪個協定頭,是以要準備好處理兩種情況。但是,有時隻有二者之一容易計算,并且Django隻提供給你計算ETag或最後修改日期的裝飾器。
django.views.decorators.http.etag
django.views.decorators.http.last_modified
作為
condition
裝飾器,傳入相同類型的函數。他們的簽名是:
etag(etag_func)
last_modified(last_modified_func)
我們可以編寫一個初期的示例,它僅僅使用最後修改日期的函數,使用這些裝飾器之一:
@last_modified(latest_entry)
def front_page(request, blog_id):
...
…或者:
def front_page(request, blog_id):
...
front_page = last_modified(latest_entry)(front_page)
Use condition
condition
如果你想要測試兩個先決條件,把
etag
last_modified
裝飾器鍊到一起看起來很不錯。但是,這會導緻不正确的行為:
# Bad code. Don't do this!
@etag(etag_func)
@last_modified(last_modified_func)
def my_view(request):
# ...
# End of bad code.
第一個裝飾器不知道後面的任何事情,并且可能發送“未修改”的響應,即使第二個裝飾器會處理别的事情。
condition
裝飾器同時更使用兩個回調函數,來弄清楚哪個是正确的行為。
使用帶有其它HTTP方法的裝飾器
condition
裝飾器不僅僅對
GET
HEAD
請求有用(
HEAD
請求在這種情況下和
GET
相同)。它也可以用于為
POST
PUT
DELETE
請求提供檢查。在這些情況下,不是要傳回一個“未修改(not modified,314)”的響應,而是要告訴服務端,它們嘗試修改的資源在此期間被修改了。
例如,考慮以下用戶端和服務端之間的互動:
- 用戶端請求
/foo/
- 服務端回複一些帶有
ETag的内容。"abcd1234"
- 用戶端發送HTTP
請求到PUT
來更新資源。同時也發送了/foo/
協定頭來指定嘗試更新的版本。If-Match: "abcd1234"
- 服務端檢查是否資源已經被修改,通過和
上所做的相同方式計算ETag(使用相同的函數)。如果資源 已經 修改了,會傳回412狀态碼,意思是“先決條件失敗(precondition failed)”。GET
- 用戶端在接收到412響應之後,發送
GET
,來在更新之前擷取内容的新版本。/foo/
重要的事情是,這個例子展示了在所有情況下,ETag和最後修改時間值都采用相同函數計算。實際上,你 應該 使用相同函數,以便每次都傳回相同的值。
使用中間件按需處理來比較
你可能注意到,Django已經通過
django.middleware.http.ConditionalGetMiddleware
CommonMiddleware
.提供了簡單和直接的
GET
的按需處理。這些中間件易于使用并且适用于多種情況,然而它們的功能有一些進階用法上的限制:
- 它們在全局上用于你項目中的所有視圖。
- 它們不會代替你生成響應本身,這可能要花一些代價。
- 它們隻适用于HTTP
請求。GET
在這裡,你應該選擇最适用于你特定問題的工具。如果你有辦法快速計算出ETag和修改時間,并且如果一些視圖需要花一些時間來生成内容,你應該考慮使用這篇文檔描述的
condition
裝飾器。如果一些都執行得非常快,堅持使用中間件在如果視圖沒有修改的條件下也會使發回用戶端的網絡流量也會減少。
譯者: Django 文檔協作翻譯小組 ,原文: Conditional content processing 本文以 CC BY-NC-SA 3.0 協定釋出,轉載請保留作者署名和文章出處。 人手緊缺,有興趣的朋友可以加入我們,完全公益性質。交流群:467338606。