# 按需內容處理
HTTP客戶端可能發送一些協議頭來告訴服務端它們已經看過了哪些資源。這在獲取網頁(使用HTTP`GET`請求)時非常常見,可以避免發送客戶端已經獲得的完整數據。然而,相同的協議頭可用于所有HTTP方法(`POST`, `PUT`, `DELETE`, 以及其它)。
對于每一個Django從視圖發回的頁面(響應),都會提供兩個HTTP協議頭:`ETag`和`Last-Modified`。這些協議頭在HTTP響應中是可選的。它們可以由你的視圖函數設置,或者你可以依靠 [`CommonMiddleware`](../ref/middleware.html#django.middleware.common.CommonMiddleware "django.middleware.common.CommonMiddleware") 中間件來設置`ETag` 協議頭。
當你的客戶端再次請求相同的資源時,它可能會發送 [If-modified-since](http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.25) 或者[If-unmodified-since](http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.28)的協議頭,包含之前發送的最后修改時間;或者 [If-match](http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.24) 或[If-none-match](http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.26)協議頭,包含之前發送的`ETag`。如果頁面的當前版本匹配客戶端發送的`ETag`,或者如果資源沒有被修改,會發回304狀態碼,而不是一個完整的回復,告訴客戶端沒有任何修改。根據協議頭,如果頁面被修改了,或者不匹配客戶端發送的 `ETag`,會返回412(先決條件失敗,Precondition Failed)狀態碼。
當你需要更多精細化的控制時,你可以使用每個視圖的按需處理函數。
Changed in Django 1.8:
向按需視圖處理添加`If-unmodified-since`協議頭的支持
## The `condition`
有時(實際上是經常),你可以創建一些函數來快速計算出資源的[ETag](http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.11)值或者最后修改時間,**并不**需要執行構建完整視圖所需的所有步驟。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](http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.11)的字符串,或者資源不存在時為`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`
如果你想要測試兩個先決條件,把`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)”的響應,而是要告訴服務端,它們嘗試修改的資源在此期間被修改了。
例如,考慮以下客戶端和服務端之間的交互:
1. 客戶端請求`/foo/`。
2. 服務端回復一些帶有`"abcd1234"`ETag的內容。
3. 客戶端發送HTTP `PUT` 請求到 `/foo/` 來更新資源。同時也發送了`If-Match: "abcd1234"` 協議頭來指定嘗試更新的版本。
4. 服務端檢查是否資源已經被修改,通過和`GET` 上所做的相同方式計算ETag(使用相同的函數)。如果資源 _已經_ 修改了,會返回412狀態碼,意思是“先決條件失敗(precondition failed)”。
5. 客戶端在接收到412響應之后,發送 `GET`請求到 `/foo/`,來在更新之前獲取內容的新版本。
重要的事情是,這個例子展示了在所有情況下,ETag和最后修改時間值都采用相同函數計算。實際上,你 **應該** 使用相同函數,以便每次都返回相同的值。
## 使用中間件按需處理來比較
你可能注意到,Django已經通過[`django.middleware.http.ConditionalGetMiddleware`](../ref/middleware.html#django.middleware.http.ConditionalGetMiddleware "django.middleware.http.ConditionalGetMiddleware") 和 [`CommonMiddleware`](../ref/middleware.html#django.middleware.common.CommonMiddleware "django.middleware.common.CommonMiddleware").提供了簡單和直接的`GET` 的按需處理。這些中間件易于使用并且適用于多種情況,然而它們的功能有一些高級用法上的限制:
* 它們在全局上用于你項目中的所有視圖。
* 它們不會代替你生成響應本身,這可能要花一些代價。
* 它們只適用于HTTP `GET` 請求。
在這里,你應該選擇最適用于你特定問題的工具。如果你有辦法快速計算出ETag和修改時間,并且如果一些視圖需要花一些時間來生成內容,你應該考慮使用這篇文檔描述的`condition`裝飾器。如果一些都執行得非常快,堅持使用中間件在如果視圖沒有修改的條件下也會使發回客戶端的網絡流量也會減少。
> 譯者:[Django 文檔協作翻譯小組](http://python.usyiyi.cn/django/index.html),原文:[Conditional content processing](https://docs.djangoproject.com/en/1.8/topics/conditional-view-processing/)。
>
> 本文以 [CC BY-NC-SA 3.0](http://creativecommons.org/licenses/by-nc-sa/3.0/cn/) 協議發布,轉載請保留作者署名和文章出處。
>
> [Django 文檔協作翻譯小組](http://python.usyiyi.cn/django/index.html)人手緊缺,有興趣的朋友可以加入我們,完全公益性質。交流群:467338606。
- 新手入門
- 從零開始
- 概覽
- 安裝
- 教程
- 第1部分:模型
- 第2部分:管理站點
- 第3部分:視圖和模板
- 第4部分:表單和通用視圖
- 第5部分:測試
- 第6部分:靜態文件
- 高級教程
- 如何編寫可重用的應用
- 為Django編寫首個補丁
- 模型層
- 模型
- 模型語法
- 元選項
- 模型類
- 查詢集
- 執行查詢
- 查找表達式
- 模型的實例
- 實例方法
- 訪問關聯對象
- 遷移
- 模式編輯器
- 編寫遷移
- 高級
- 管理器
- 原始的SQL查詢
- 聚合
- 多數據庫
- 自定義查找
- 條件表達式
- 數據庫函數
- 其它
- 遺留的數據庫
- 提供初始數據
- 優化數據庫訪問
- 視圖層
- 基礎
- URL配置
- 視圖函數
- 快捷函數
- 裝飾器
- 參考
- 內建的視圖
- TemplateResponse 對象
- 文件上傳
- 概覽
- File 對象
- 儲存API
- 管理文件
- 自定義存儲
- 基于類的視圖
- 概覽
- 內建顯示視圖
- 內建編輯視圖
- API參考
- 分類索引
- 高級
- 生成 CSV
- 生成 PDF
- 中間件
- 概覽
- 內建的中間件類
- 模板層
- 基礎
- 面向設計師
- 語言概覽
- 人性化
- 面向程序員
- 表單
- 基礎
- 概覽
- 表單API
- 內建的Widget
- 高級
- 整合媒體
- 開發過程
- 設置
- 概覽
- 應用程序
- 異常
- 概覽
- django-admin 和 manage.py
- 添加自定義的命令
- 測試
- 介紹
- 部署
- 概述
- WSGI服務器
- 部署靜態文件
- 通過email追蹤代碼錯誤
- Admin
- 管理操作
- 管理文檔生成器
- 安全
- 安全概述
- 說明Django中的安全問題
- 點擊劫持保護
- 加密簽名
- 國際化和本地化
- 概述
- 本地化WEB UI格式化輸入
- “本地特色”
- 常見的網站應用工具
- 認證
- 概覽
- 使用認證系統
- 密碼管理
- 日志
- 分頁
- 會話
- 數據驗證
- 其它核心功能
- 按需內容處理
- 重定向
- 信號
- 系統檢查框架