HTTP客戶端可能發(fā)送一些協(xié)議頭來告訴服務(wù)端它們已經(jīng)看過了哪些資源。這在獲取網(wǎng)頁(使用HTTPGET
請求)時非常常見,可以避免發(fā)送客戶端已經(jīng)獲得的完整數(shù)據(jù)。然而,相同的協(xié)議頭可用于所有HTTP方法(POST
, PUT
, DELETE
, 以及其它)。
對于每一個Django從視圖發(fā)回的頁面(響應(yīng)),都會提供兩個HTTP協(xié)議頭:ETag
和Last-Modified
。這些協(xié)議頭在HTTP響應(yīng)中是可選的。它們可以由你的視圖函數(shù)設(shè)置,或者你可以依靠 CommonMiddleware
中間件來設(shè)置ETag
協(xié)議頭。
當(dāng)你的客戶端再次請求相同的資源時,它可能會發(fā)送 If-modified-since 或者If-unmodified-since的協(xié)議頭,包含之前發(fā)送的最后修改時間;或者 If-match 或If-none-match協(xié)議頭,包含之前發(fā)送的ETag
。如果頁面的當(dāng)前版本匹配客戶端發(fā)送的ETag
,或者如果資源沒有被修改,會發(fā)回304狀態(tài)碼,而不是一個完整的回復(fù),告訴客戶端沒有任何修改。根據(jù)協(xié)議頭,如果頁面被修改了,或者不匹配客戶端發(fā)送的 ETag
,會返回412(先決條件失敗,Precondition Failed)狀態(tài)碼。
當(dāng)你需要更多精細(xì)化的控制時,你可以使用每個視圖的按需處理函數(shù)。
Changed in Django 1.8:
向按需視圖處理添加If-unmodified-since
協(xié)議頭的支持
condition
有時(實(shí)際上是經(jīng)常),你可以創(chuàng)建一些函數(shù)來快速計算出資源的ETag值或者最后修改時間,并不需要執(zhí)行構(gòu)建完整視圖所需的所有步驟。Django可以使用這些函數(shù)來為視圖處理提供一個“early bailout”的選項。來告訴客戶端,內(nèi)容自從上次請求并沒有任何改動。
這兩個函數(shù)作為參數(shù)傳遞到django.views.decorators.http.condition
裝飾器中。這個裝時期使用這兩個函數(shù)(如果你不能既快又容易得計算出來,你只需要提供一個)來弄清楚是否HTTP請求中的協(xié)議頭匹配那些資源。如果它們不匹配,會生成資源的一份新的副本,并調(diào)用你的普通視圖。
condition
裝飾器的簽名為i:
condition(etag_func=None, last_modified_func=None)
計算ETag的最后修改時間的兩個函數(shù),會以相同的順序傳入request
對象和相同的參數(shù),就像它們封裝的視圖函數(shù)那樣。last_modified_func
函數(shù)應(yīng)該返回一個標(biāo)準(zhǔn)的datetime值,它制訂了資源修改的最后時間,或者資源不存在為 None
。傳遞給etag
裝飾器的函數(shù)應(yīng)該返回一個表示資源Etag的字符串,或者資源不存在時為None
。
用一個例子可以很好展示如何使用這一特性。假設(shè)你有這兩個模型,表示一個簡單的博客系統(tǒng):
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)
...
如果頭版展示最后的博客文章,僅僅在你添加新文章的時候修改,你可以非??焖俚赜嬎愠鲎詈笮薷臅r間。你需要這個博客每一篇文章的最后 發(fā)布
日期。實(shí)現(xiàn)它的一種方式是:
def latest_entry(request, blog_id):
return Entry.objects.filter(blog=blog_id).latest("published").published
接下來你可以使用這個函數(shù),來為你的頭版視圖事先探測未修改的頁面:
from django.views.decorators.http import condition
@condition(last_modified_func=latest_entry)
def front_page(request, blog_id):
...
一個普遍的原則是,如果你提供了計算 ETag_和_最后修改時間的函數(shù),你應(yīng)該這樣做:你并不知道HTTP客戶端會發(fā)給你哪個協(xié)議頭,所以要準(zhǔn)備好處理兩種情況。但是,有時只有二者之一容易計算,并且Django只提供給你計算ETag或最后修改日期的裝飾器。
django.views.decorators.http.etag
和django.views.decorators.http.last_modified
作為condition
裝飾器,傳入相同類型的函數(shù)。他們的簽名是:
etag(etag_func)
last_modified(last_modified_func)
我們可以編寫一個初期的示例,它僅僅使用最后修改日期的函數(shù),使用這些裝飾器之一:
@last_modified(latest_entry)
def front_page(request, blog_id):
...
...或者:
def front_page(request, blog_id):
...
front_page = last_modified(latest_entry)(front_page)
condition
如果你想要測試兩個先決條件,把etag
和last_modified
裝飾器鏈到一起看起來很不錯。但是,這會導(dǎo)致不正確的行為:
# Bad code. Don't do this!
@etag(etag_func)
@last_modified(last_modified_func)
def my_view(request):
# ...
# End of bad code.
第一個裝飾器不知道后面的任何事情,并且可能發(fā)送“未修改”的響應(yīng),即使第二個裝飾器會處理別的事情。condition
裝飾器同時更使用兩個回調(diào)函數(shù),來弄清楚哪個是正確的行為。
condition
裝飾器不僅僅對GET
和 HEAD
請求有用(HEAD
請求在這種情況下和GET
相同)。它也可以用于為 POST
, PUT
和 DELETE
請求提供檢查。在這些情況下,不是要返回一個“未修改(not modified,314)”的響應(yīng),而是要告訴服務(wù)端,它們嘗試修改的資源在此期間被修改了。
例如,考慮以下客戶端和服務(wù)端之間的交互:
/foo/
。"abcd1234"
ETag的內(nèi)容。PUT
請求到 /foo/
來更新資源。同時也發(fā)送了If-Match: "abcd1234"
協(xié)議頭來指定嘗試更新的版本。GET
上所做的相同方式計算ETag(使用相同的函數(shù))。如果資源 已經(jīng) 修改了,會返回412狀態(tài)碼,意思是“先決條件失?。╬recondition failed)”。GET
請求到 /foo/
,來在更新之前獲取內(nèi)容的新版本。重要的事情是,這個例子展示了在所有情況下,ETag和最后修改時間值都采用相同函數(shù)計算。實(shí)際上,你 應(yīng)該 使用相同函數(shù),以便每次都返回相同的值。
你可能注意到,Django已經(jīng)通過django.middleware.http.ConditionalGetMiddleware
和 CommonMiddleware
.提供了簡單和直接的GET
的按需處理。這些中間件易于使用并且適用于多種情況,然而它們的功能有一些高級用法上的限制:
GET
請求。在這里,你應(yīng)該選擇最適用于你特定問題的工具。如果你有辦法快速計算出ETag和修改時間,并且如果一些視圖需要花一些時間來生成內(nèi)容,你應(yīng)該考慮使用這篇文檔描述的condition
裝飾器。如果一些都執(zhí)行得非??欤瑘猿质褂弥虚g件在如果視圖沒有修改的條件下也會使發(fā)回客戶端的網(wǎng)絡(luò)流量也會減少。
譯者:Django 文檔協(xié)作翻譯小組,原文:Conditional content processing。
本文以 CC BY-NC-SA 3.0 協(xié)議發(fā)布,轉(zhuǎn)載請保留作者署名和文章出處。
Django 文檔協(xié)作翻譯小組人手緊缺,有興趣的朋友可以加入我們,完全公益性質(zhì)。交流群:467338606。