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