HTTP 協議 Cache-Control 頭——性能啊


原文地址:http://tools.ietf.org/html/rfc2616#section-14.9

 

本文內容

概述
術語
HTTP Cache-Control 頭
    可緩存的資源
    可被高速緩存存儲的資源
    修改基本過期機制
    緩存重新驗證和重新加載的控制
    no-transform 指令
    緩存控制擴展
參考資料
 

概述
最近做的項目使用了 Ext.Net,由於之前看了很多介紹提高站點性能的文章,這類文章大多趨於理論,或是工程實踐,都是三言兩語,只能意會,沒個直觀的理解。很多東西還得靠自己。

開始關注性能問題源於我畢業之初做的一個項目,當時沒什么經驗,只是一味寫代碼,功能和設計至上,完全沒考慮性能問題。另一部分原因是當時對 Web 應用程序的很多實現細節還不甚了解。知道 Why 和 What,但不知道 How。

這段時間,閑來無事,就針對 Ext.Net Web 應用程序利用網頁測試和統計工具,比如 HttpWatch、YSLOW 等工具進行了一些測試。這些測試着實花了我很多時間,但收獲巨大。

本文針對利用 HTTP 頭 Cache-Control 的緩存問題。之前在網和 MSDN 上搜,沒找到幾個關於 Cache-Control 頭的可能賦值,后來直接去找 RFC 文檔,才豁然開朗。

以下是 RFC 2616 文檔關於 Cache-Control 的部分,RFC 文檔對一些語氣詞,如 "MUST", "MUST NOT" 等,以及術語都做了明確的規定。本文文字實在太多,也不容易翻譯。因為 RFC 文檔側重理論,使用的概念要比通常的叫法更廣。比如用實體可以涵蓋很多東西,頁面、CSS、腳本、圖像等等都可以認為是實體,但本文把它們當作是實體體,而包裝后的實體體才叫實體。除了實體,還區分了實體體,實體體是實體的一部分,可以看作是實體的內容;信息概念更側重於 HTTP 協議里邊的相關東西;另外,區分了服務器(server)和源服務器(origin server)。

 

術語
以下是本文需要的術語(RFC 2616 文檔還有其他一些術語)。本來不想列出來的,但想下還是有必要的——了解一個事物,首要的是能區分概念。

消息(message)
HTTP 通信的基本單元,它由一個結構化八進制序列組成,包括消息頭、消息體、消息長度、通用消息頭域等等(參看 RFC 2616 的 4 節),並通過網絡傳輸。

 

請求(request) 
一個 HTTP 請求信息。

 

響應(response)
一個 HTTP 響應信息。

 

資源(resource)
可以由 URI(Uniform Resource Identifier)識別的一個網絡數據對象或服務(參看 RFC 2616 的 3.2 節)。資源可以可靠的方式進行多種呈現(例如,多語言,數據格式、大小和解析),或是其他變化。

 

實體(entity)

信息作為一個請求或響應的有效載荷(payload)來傳輸。一個實體由以實體頭域形式的元信息和以實體體形式的內容所組成。

實體(entity)即 HTTP 協議,實體頭域(entity-header fields )即 HTTP 頭,而實體體(entity-body)即 HTTP 體。

有效載荷的意思是,通常在傳輸數據時,為了使數據傳輸更可靠,要把原始數據分批傳輸,並且在每一批數據的頭和尾加上一定輔助信息,比如該批數據量的大小,校驗位等,這樣為包裝原始數據,使原始數據不易丟失,形成了傳輸通道中基本的傳輸單元——數據幀或數據包。這些數據幀中的原始數據就是有效載荷數據。

 

客戶端(client) 
一個為發送請求建立連接的程序。

 

用戶代理(user agent)
初始化一個請求的客戶端。這通常是瀏覽器、編輯器、網絡蜘蛛(網頁抓取程序),或其他的終端用戶工具。

 

服務器/服務器端(server)

一個接收連接的應用程序,通過發回響應服務於請求。任何給定的程序都可能既是一個客戶端,也是一個服務器。我們使用的這些術語僅指由程序執行一個特定連接的角色,而不是通常程序的功能。同樣,基於每個請求的性質來切換行為,任何服務器都可以作為源服務器(origin server)、代理(proxy)、網關(gateway),或是管道(tunnel)。

服務器端或客戶端主要是指它們在網絡中所處的角色。

 

源服務器(origin server)
駐留或創建一個給定資源的服務器。

換句話說,源服務器是存儲資源的地方,比如存放靜態資源的服務器,存放 Web 應用程序的服務器等,出於提高客戶端響應速度的考慮,會將靜態資源單獨放到一個域名下。

 

代理(proxy)

一個作為服務器端和客戶端,代表客戶端發出請求的中間程序。請求在內部處理,或是通過翻譯,把它們發送到其他服務器。代理必須要同時實現這個規范的客戶端和服務器端需求。一個“透明代理”是一個不修改超出代理身份驗證和識別要求的請求或響應的代理。一個“非透明代理”是一個修改請求或響應,為用戶代理提供額外服務(增值服務)的代理,例如組注釋服務,媒體類型轉換,協議簡化,或匿名過濾。除了透明或不透明的行為要明確指出外,HTTP 代理的需求要同時適用於兩種類型的代理。

 

網關(gateway)

一個作為處理其他服務的中介的服務器。與代理不同,網關接收請求,就好像它是為請求資源的源服務器;請求的客戶端並沒有意識到,它們正在與網關通信。

也就是說,網關與代理都接收用戶的請求。往往最終用戶並沒有意識到,其他它們正在與網關通信。

 

可緩存(cacheable)

如果一個緩存被允許存儲響應信息的副本,以便在回答接下來的請求中使用,那么一個響應就是可緩存的。確定 HTTP 響應緩存能力的規則參看 RFC 2616 的13 節。即使一個資源是可緩存的,也可能有其他限制,如一個緩存是否可以對一個特定請求使用已緩存的副本。

 

顯式過期時間(explicit expiration time)

這個時間是,源服務器計划,一個實體無需進一步驗證不再從緩存返回。

也就是說,當服務器發現請求中的過期時間表明,用戶緩存中的資源已經過期,那就不能再從緩存獲得資源。

 

絕對時間(age)

響應的絕對時間是,是自被源服務器發送,或成功驗證的開始時間。

 

響應生命周期(freshness lifetime)

一個響應的產生與它過期時間之間的時間長度。

 

新的(fresh)

如果一個響應的年齡還沒有超過它的響應生命周期,那么這個響應就是新的。

 

陳舊(stale)
如果一個響應已經超過它的響應生命周期,那么這個響應就是陳舊。

 

驗證器(validator)
一個用於檢查一個緩存條目是否為一個等價的實體副本的協議元素,例如一個實體標簽(Etags),或是最后修改時間。

用於驗證一個實體是否過期的方法。

 

請求/響應鏈(request/response chain)

   request chain ------------------------>
UA -------------------v------------------- O
<----------------------- response chain
 

HTTP Cache-Control 頭
Cache-Control 頭用於指定緩存指令,所有請求/響應鏈的緩存機制必須遵守這個指令。該指令規定行為,意在防止緩存受到請求或響應的不利干擾。通常,這些指令可以覆蓋默認的緩存算法。緩存指令是單向的,也就是說,在一個請求中存在緩存指令不意味着也在其響應中存在。

注意:HTTP/1.0 緩存沒有實現 Cache-Control,只實現了 Pragma: no-cache。

不管緩存指令(Cache directives)對應用程序意義如何重大,它們必須通過代理(瀏覽器)或是網關應用程序傳遞,因為該指令可以應用與請求/響應鏈上的所有接收者。為一個特定的緩存規定緩存指令是不可能的。下面是 Cache-Control 的可能值。

Cache-Control = "Cache-Control" ":" 1#cache-directive
 
cache-directive = cache-request-directive
| cache-response-directive
 
cache-request-directive =
"no-cache"
| "no-store"
| "max-age" "=" delta-seconds
| "max-stale" [ "=" delta-seconds ]
| "min-fresh" "=" delta-seconds
| "no-transform"
| "only-if-cached"
| cache-extension
 
cache-response-directive =
"public"
| "private" [ "=" <;"> 1#field-name <"> ]
| "no-cache" [ "=" <;"> 1#field-name <"> ]
| "no-store"
| "no-transform"
| "must-revalidate"
| "proxy-revalidate"
| "max-age" "=" delta-seconds
| "s-maxage" "=" delta-seconds
| cache-extension
 
cache-extension = token [ "=" ( token | quoted-string ) ]
 
當指令中沒有 1#field-name 字段名參數時,指令應用於整個請求或響應。當出現 1#field-name 參數時,它僅僅應用於命名的字段,而不會應用於請求或響應的其余部分。該機制支持擴展,以適應未來 HTTP 協議。

cache-control 指令,即以上可能的值,可以被划分成以下幾類:

可緩存資源的限制。只能由源服務器完成。
可被緩存存儲的限制。可以由源服務器或用戶代理完成。
修改基本的過期機制。可以由源服務或用戶代理完成。
控制緩存重新驗證和重新加載。只能由用戶代理完成。
控制實體傳輸。
擴展緩存系統。
 

可緩存的資源
默認情況下,如果請求方法、請求頭部和響應狀態的需要指示是可緩存的,那么一個響應就是可緩存的。RFC 2616 的 13.4 節概述了默認的緩存功能。下面 Cache-Control 響應指令允許源服務器覆蓋一個響應默認的緩存功能:

public

指示響應可以被任何緩存所緩存,即使通常它只是非可緩存或可緩存到一個非共享緩存內。(參考 RFC 2616 14.8 節 授權))

 

private

指示響應信息的全部或部分用於單個用戶,而不能用一個共享緩存來緩存。這可以讓源服務器指示,響應的特定部分只用於一個用戶,而對其他用戶的請求則是一個不可靠的響應。一個 private(非共享)緩存可以緩存這樣的響應。

注意:使用 private 僅僅控制可以緩存響應的哪里,不能保證信息內容的隱私。

 

no-cache

如果 no-cache 指令沒有規定 field-name,那么一個緩存不能使用響應以滿足接下來的、沒有與源服務器重新驗證的請求。這可以讓源服務器防止緩存,甚至是已被配置的緩存,返回給客戶端陳舊的響應。

如果 no-cache 指令規定了一個或多個 field-names,那么一個緩存可以使用響應來滿足接下來的請求,遵守緩存的其他限制。然而,指定的 field-name 參數不能在響應中被發送給接下來的、沒有與源服務器成功重新驗證的請求。這可以讓源服務器防止重用響應中的某個頭,而仍然可以緩存響應的其他部分。

 

可被緩存存儲的資源
no-store

no-store 指令的目的是防止無心發布或是保留了敏感信息(例如,備份)。no-store 指令應用於整個信息,可以在響應或請求中發送。如果是在一個請求中發送,那么緩存不能存儲這個請求或任何響應的任何部分給它。如果在一個響應中發送,那么緩存不能存儲它引起的響應或請求的任何部分。這個指令可以應用於共享或非共享緩存。在上下文環境中,“不能存儲”意思是緩存不能把信息有意地存儲在非易失行性儲器上,而且,在使用后,必須盡最大努力從易失存儲上盡可能快地刪除信息。

即使該指令與一個響應一起使用,用戶也可能會顯式地把這個響應存儲到緩存系統之外(例如,"Save As" 對話框,或“導出”)。歷史記錄緩存可以把響應作為其正常操作的一部分來存儲。

該指令的目的是為了滿足某些用戶和服務作者指定的要求,他們關心的是,通過意外地訪問緩存的數據結構,導致的信息意外釋放。當使用該指令可以在某些情況下提高隱私,但是需要注意的是,在某種程度上,它是不可靠的,或者說,是個不足以確保隱私的機制。特別是,惡意的緩存可能無法識別或遵守這個指令,這樣通訊網絡很容易會被竊聽。

 

修改基本的過期機制
實體的過期時間可以由源服務器通過 Expires 頭來指定(參考 RFC 2616 的 14.21 節)。另一個方法是,在響應中使用 max-age 指令。當一個已緩存的響應中存在 max-age 緩存指令時,如果當前的絕對時間大於一個新請求該資源的給定時間值,那么該響應就是陳舊的。響應中的 max-age 指令意味着,響應是可緩存的(即,"public"),除非其他的,還有更限制的緩存指令。

如果一個響應既包含 Expires 頭,又包含 max-age 指令,那么 max-age 指令會覆蓋 Expires  頭,即使 Expires 頭更有限制性。這個規則允許源服務器,對於一個給定響應,向 HTTP/1.1(或之后)緩存比 HTTP/1.0 提供一個更長的過期時間。如果某個 HTTP/1.0 緩存由於不同步的時鍾而不當地計算絕對時間或過期時間,那么這個就會很有用。

很多 HTTP/1.0 緩存的實現會把小於等於響應日期值的過期值當作等價於 Cache-Control  響應指令 "no-cache"。如果一個 HTTP/1.1 緩存接收到這樣的響應,並且響應不包含 Cache-Control  頭,那么它會考慮把響應作為不可緩存,以便同 HTTP/1.0 兼容。

注意:源服務器可能希望在一個包含不能理解該指令的舊緩存的網絡中使用一個相對較新的 HTTP 緩存控制功能,如 "private" 指令。源服務器會把這個新功能與過期結合起來,該過期的值小於等於日期值。這將防止陳舊的緩存不當地緩存的響應。

s-maxage

如果一個響應包含 s-maxage 指令,那么對於共享緩存(而不是對私有緩存),由該指令規定的最大絕對時間會覆蓋由 max-age 指令或 Expires 頭規定的最絕對時間。s-maxage 指令也隱含 proxy-revalidate 指令的語義(將在本文“控制緩存重新驗證和重新加載”小節介紹),也就是說,當共享緩存對接下來的請求的響應變得陳舊后,該請求沒有與源服務器重新驗證,共享緩存不能使用緩存條目。私有緩存總是忽略 s-maxage 指令。

注意,大多數與上面規范不兼容的舊緩存沒有實現任何 cache-control 指令。希望使用 cache-control 指令來限制的源服務器,而不妨礙 HTTP/1.1-compliant 緩存,可以使用 max-age 指令覆蓋 Expires 頭,事實上,HTTP/1.1-compliant 之前的緩存不檢查 max-age 指令。

其他指令可以讓用戶代理修改基本的過期機制。這些指令可以在一個請求中規定:

max-age

指示客戶端願意接收其絕對時間不大於指定的時間,以秒計。除非還包含 max-stale 指令,否則客戶端不期望接收一個陳舊的響應。

 

min-fresh

指示客戶端願意接收一個其響應生命周期不小於它當前絕對時間,再加上指定的時間的響應,以秒計。也就是說,客戶端想要的一個響應,至少在指定的秒數是新的。

  

max-stale

指示客戶端願意接收一個已經超過其過期時間的響應。如果 max-stale 被分配一個值,那么客戶端願意接收已經超過其過期時間,不超過指定的秒數。如果沒有分配給 max-stale 值,那么客戶端願意接收一個任何絕對時間的陳舊的響應。

 

如果緩存返回一個陳舊的響應,無論是因為一個請求中的 max-stale 指令,還是因為緩存被配置為覆蓋一個響應的過期時間,那么,緩存必須把一個警告頭 110 加到這個陳舊的響應。

一個緩存可以被配置為返回陳舊的響應,無需驗證,但只有與任何需要 "MUST級別" 的緩存驗證不沖突時(例如,一個 "must-revalidate" 緩存控制指令)。

如果這新請求和已緩存的條目都包含 "max-age" 指示,那么這兩個值中較小的那個用於為請求確定已緩存條目的新的程度。

 

控制緩存重新驗證和重加載
有時,用戶代理可能想或需要堅持,一個緩存與源服務器(不僅僅沿着源服務器路徑的下一緩存)重新驗證它的緩存條目,或是從源服務器重新加載緩存條目。如果緩存或源服務器已過高的估計已緩存的響應的過期時間,那么可能需要點對點重新驗證。如果緩存條目處於某種原因已經完全沒有用處,那么可能需要點對點重新加載。

可以請求點對點重新驗證,當客戶端沒有屬於自己的本地已緩存的副本時,稱為 "unspecified end-to-end revalidation",或是當客戶端沒有本地已緩存的副本時,稱為 "specific end-to-end revalidation"。

客戶端通過 Cache-Control 請求指令可以規定三種動作:

End-to-end reload

請求包含 "no-cache" 緩存控制指令,或是為了與 HTTP/1.0 客戶端兼容的 "Pragma: no-cache"。請求中的 no-cache 指令不能包含 field-name。當響應這樣一個請求時,服務器不能使用已緩存的副本。

 

Specific end-to-end revalidation

請求包含一個 "max-age=0" 緩存控制指令,這會迫使每個沿着源服務器路徑的緩存,如果有,重新驗證它自己的條目。初始請求包含一個帶客戶端當前的驗證器的緩存驗證條件。

 

Unspecified end-to-end revalidation

請求包含一個 "max-age=0" 緩存控制指令,這會迫使每個沿着源服務器路徑的緩存,如果有,重新驗證它自己的條目。初始請求不包含緩存驗證條件;擁有此資源緩存條目的第一個沿路徑的緩存(如果有)包含一個帶客戶端當前的驗證器的緩存驗證條件。

 

max-age

當利用 max-age=0 指令迫使一個中間緩存重新驗證它自己的緩存條目,並且客戶端已經在請求中提供它自己的驗證器,那么,這個所提供的驗證器可能與當前存儲緩存條目的驗證器不同。這種情況下,在不影響語義的情況下,緩存也可以使用它自己的請求中的驗證器。

然而,驗證器的選擇可能會影響性能。對中間緩存(代理)來說,最好的方法是當自己發出請求時,使用它自己的驗證器。如果服務器響應 304(Not Modified),那么緩存可以用一個 200 響應返回它已經驗證的副本給客戶端。然而,如果服務器用一個新的實體和緩存驗證器回應,那么,中間緩存使用強比較函數把返回的驗證器與客戶端請求中提供的進行比較。如果客戶端驗證器與源服務器的相等,那么中間緩存(代理)就簡單地返回 304((Not Modified)響應。否則,用一個 200(OK)響應返回新的實體。

如果一個請求包含 no-cache 指令,那么它不就應該包含 min-fresh、max-stale 或 max-age。

 

only-if-cached

在某些情況下,如網絡連接非常差時,客戶端可能需要一個緩存,只返回目前已存儲的那些響應,而不是重新加載,或與源服務器重新驗證。要做到這一點,客戶端可以在一個請求中包含 only-if-cached 指令。如果服務器接收到這個指令,緩存應該,或是使用與其他請求限制一致的緩存項響應,或是用 504(Gateway Timeout)響應。但是,如果一個緩存組在一個統一的具有良好的網絡連接的系統內被操作,這樣一個請求可能會被在緩存組內轉發。

 

must-revalidate

因為緩存可以配置成忽略服務器指定的過期時間,並且,因為一個客戶端請求可以包含 max-stale 指令(具有類似的效果),對源服務器,協議還包括一個機制,需要在接下來的使用中重新驗證緩存條目。當 must-revalidate 指令存在於一個已被緩存收到的響應時,響應接下來沒有與源服務器初次重新驗證的請求的條目變舊之后,緩存不能使用該條目。(即,在只基於源服務器 Expires 頭或 max-age 值,如果已緩存的響應舊了,那么緩存必須每次完成一個點對點的重新驗證。)

must-revalidate 指令對於某些協議功能的可靠運行是必需的。在任何情況下,HTTP/1.1 緩存必須遵守 must-revalidate 指令;特別是,如果緩存出於某種原因不能到達源服務器,那么它必須產生 504(Gateway Timeout)響應。

服務器應該發送 must-revalidate 指令,當且僅當沒有成功重新驗證一個實體的請求會導致不正確的操作,例如一個靜默未執行的金融事務。接收者不能自動執行任何違反該指令的動作,並且,如果重新驗證失敗,不能自動提供一個未驗證的實體副本。

雖然這是不推薦的,在嚴格連接限制操作下的用戶代理可能會違反該指令,如果是這樣,必須顯式警告用戶,提供了一個未經驗證的響應。該警告必須提供給每個未經驗證的訪問,並且應該要求顯式的用戶確認信息。

 

proxy-revalidate

除了 proxy-revalidate 指令不能應用於非共享的用戶代理緩存外,它與 must-revalidate  指令含義相同。proxy-revalidate 指令可以被用在響應一個已授權的請求,以便允許用戶緩存存儲,之后返回無需重新驗證的響應(因為它已經被授權一次),但仍然需要代理來為用戶重新驗證(以確保每個用戶已授權)。

注意,這種已授權的響應也需要 public 緩存控制指令,以便讓它們完全被緩存。

 

No-Transform 指令
no-transform

中間緩存(代理)的實施者已經發現它對轉換某個實體體的媒體類型很有用。例如,一個非透明的代理把圖像轉換格式,以節省緩存空間,或是減少慢速鏈接中的通信量。

然而,當這些轉換被應用到實體體以便某種應用時,就會發生嚴重的操作性問題。例如,醫療成像、科學數據分析,以及點對點授權的應用程序來說,所有這些都依賴於接收的實體體,每個比特都要與原實體體一致。

因此,如果一個消息包含 no-transform 指令,那么中間緩存或代理就不能改變頭(參看 RFC 2616 的 13.5.2 節 列出的)。這意味着,緩存或代理不能改變由頭規定的實體體的任何方面,包括實體體自身的值。

 

緩存控制擴展
Cache-Control 頭可以通過使用一個或多個 cache-extension 標記擴展,並為每個標記分配可選值。可以添加信息擴展(不需要改變緩存行為),而無需改變這些指令的語義。通過把現存的基本緩存指令作為修飾符,設計行為擴展。同時提供新指令和標准指令,不能理解新指令的應用程序將默認采用標准指令的行為,可以理解新指令的那些程序將其與標准指令一起修改要求。通過這種方式,無需改變基本協議,就可以擴展 cache-control 指令。

該擴展機制取決於遵守其本地 HTTP 版本中定義的所有緩存控制指令,以及一定程度的擴展,並忽略所有它不能理解的指令。

例如,假設一個新的稱為 "community" 的響應指令,它作為一個 private 指令的修飾符。我們定義這個新指令的含義是,除了任何非共享緩存,只有由指定的 community 成員共享的任何緩存可以緩存響應。源服務器希望允許 UCI community 使用它們緩存中的 private  響應,按如下方式

Cache-Control: private, community="UCI"
一個看到這個頭的緩存將執行正確的行為,即使緩存並不明白 community 緩存擴展,因為它也將看到和理解 private 指令,因此去執行默認的安全行為。

無法識別的緩存指令必須被忽略。它假定可能無法被 HTTP/1.1 緩存識別的任何緩存指令將被與標准指令(或響應默認的緩存功能)結合,這樣,緩存行為將保持最低限度的正確性,即使緩存不能理解擴展。

 

參考資料
Wiki: Hypertext Transfer Protocol http://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol

RFC 2616: Hypertext Transfer Protocol -- HTTP/1.1 http://tools.ietf.org/html/rfc2616

W3.org: A detailed technical history of HTTP http://www.w3.org/Protocols/History.html

W3.org: Design Issues by Berners-Lee when he was designing the protocol http://www.w3.org/Protocols/DesignIssues.html

Wiki: List of HTTP header fields http://en.wikipedia.org/wiki/List_of_HTTP_headers

httpwatch.com: HTTP Headers http://www.httpwatch.com/httpgallery/headers/

microsoft.com: HTTP Response Headers: http://msdn.microsoft.com/en-us/library/ms537417(v=VS.85).aspx

RFC 4229: HTTP Header Field Registrations http://tools.ietf.org/html/rfc4229

Internet Explorer and Custom HTTP Headers - EricLaw's IEInternals - Site Home - MSDN Blogs http://blogs.msdn.com/b/ieinternals/archive/2009/06/30/internet-explorer-custom-http-headers.aspx

HTTP Request Header Viewer http://www.myhttp.info/

HTTP Response Header Viewer - Retrieves the HTTP response headers of any domain http://viewdns.info/httpheaders/

HTTP Header with Privacyinfo - Display your HTTP request and response headers http://www.privacyinfo.org/http-headers

MSDN metal HTTP-EQUIV http://msdn.microsoft.com/en-us/library/ms533876(v=VS.85).aspx


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM