原文地址:http://tools.ietf.org/html/rfc2616#section-14.9html
本文內容算法
概述
術語
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 文檔還有其餘一些術語)。原本不想列出來的,但想下仍是有必要的——瞭解一個事物,首要的是能區分概念。dom
消息(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