HTTP協議進階之快取
1、概論
1.1、 什麼是快取?
Web快取是可以自動儲存常見文件副本的HTTP裝置。當Web請求抵達快取時,如果本地有“已快取的”副本,就可以從本地儲存裝置而不是原始伺服器中提取這個文件。因此,可以這樣理解,快取攔截了客戶端的請求,代替伺服器端做出響應。
1.2、快取的作用
使用快取主要有如下幾個有點:
- 快取減少了冗餘的資料傳輸,節省了你的網路費用。
- 快取緩解了網路瓶頸問題。不需要更多的頻寬就能夠更快的載入頁面。
- 快取降低了對原始伺服器的要求。伺服器可以更快地響應,避免過載的出現。
- 快取降低了距離時延,因為從較遠的地方載入頁面會更慢一些。
1.3、快取分類
快取主要分為兩類:
- 私有快取:快取儲存在客戶端的儲存器裡面。
- 公有代理快取:又稱為快取代理伺服器或者代理快取。它較私有快取的優點是,避免了每個客戶端都需要去伺服器獲取資料,從而節省一定的流量。
二者之間的對比如下圖所示:
私有快取和共享快取.png
2、快取的處理步驟
一個快取從接收到請求到做出響應,大概可以分為如下7個步驟:
(1)接收——快取從網路中讀取抵達的請求報文。
(2)解析——快取對報文進行解析,提取出URL和各種首部。
(3)查詢——快取檢視是否有本地副本可用,如果沒有,就獲取一份副本(並將其儲存在本地)。
(4)新鮮度檢測——快取檢視已快取副本是否足夠新鮮,如果不是,就詢問伺服器是否有任何更新。
(5)建立響應——快取會用新的首部和已快取的主體來構建一條響應報文。
(6)傳送——快取通過網路將響應發回給客戶端。
(7)日誌——快取可選地建立一個日誌檔案條目來描述這個事務。
下圖描述了快取中有新鮮快取時,快取的處理過程。由此可以看出,這整個過程已經與伺服器沒有關係了:
處理一個新鮮的快取命中.png
2.1、快取處理流程圖
下圖以簡化的形式顯示了快取是如何處理請求以獲取一個方法為GET的URL的。
快取GET請求的流程圖.png
3、判斷是否新鮮
從上一節的流程圖可以看出,有兩個工作在快取的過程中是非常重要的。一個是如何判斷該快取還是新鮮的?另一個是驗證快取副本是否與伺服器原本一致?這兩個工作都是由HTTP報文相關的首部欄位決定的。這一節我們先考慮第一個問題,下一節再探討第二個問題。
3.1、Expires
伺服器使用Expires首部來指定過期日期。比如說,如果伺服器傳送一個響應報文,其中首部有如下欄位:
Expires: Mon, 30 Oct 2017, 12:00:00 GMT
這也就說明,該報文會在2017年10月30日的12點整過期。在此後到來的響應請求,快取就會判斷其中的副本不新鮮,需要與伺服器進行再驗證。
3.2、Cache-Control: max-age
使用Expires來判斷是否新鮮雖然可行,但也有一定的問題。那就是它所依賴的是快取本地的時間,假設我所用的快取是私人快取,我將自己電腦的時間調到2200年,那麼,該副本將在我的有生之年都不會過期。待我老時,瀏覽的還是幾十年前的頁面。
實際上Expires是HTTP 1.0協議引入的。在HTTP 1.1協議中引入了Cache-Control: max-age頭部,解決了Expires所遇到的難題。Expires使用的是絕對時間,而max-age使用的則是相對時間。當伺服器生產一個響應或者再驗證成功時,它會在報文中儲存此刻的時間,max-age中的時間就是相對該時間的。比如,如果響應報文是此刻生產的,那麼下面的語句就是說該快取會在60秒後過期,如果你60秒後再來訪問該URL,那麼就需要與伺服器進行再驗證:
Cache-Control: max-age=60
3.3、請求報文如何控制新鮮度
上面的內容都是針對伺服器的響應報文的。那麼,請求報文有沒有辦法控制新鮮度呢?或者可以先問這樣一個問題,客戶端是否有必要插手管理快取的新鮮度呢?
答案是肯定,比如伺服器端使用了Expires欄位,而我一不小心將時間設定成了2200年。一天又一天,無論我怎麼請求,該頁面亙古不變。我依然沒想到是時間設定的錯誤(我可真笨)。然而,有一天,我不再只是在瀏覽器輸入網址然後敲回車鍵,而是按F5重新整理,頁面終於奇蹟的重新整理了。
請求報文中與快取新鮮度有關的也在名為Cache-Control的首部欄位中。大概有如下幾種:
3.3.1 max-age
和響應報文中的max-age類似,該值也是一個相對時間。如果當前時間減去快取中儲存的響應報文的建立時間超過max-age的值的話,那麼快取就得去和伺服器再驗證了。
3.3.2 max-stale
stale的意思是不新鮮,max-stale的意思就是說,在多大的範圍內,客戶端還是願意接受快取中儲存的不新鮮的副本的。也即是說,如果當前時間超過了max-age的範圍,但卻沒有超過max-stale值得範圍,那麼快取副本還是可以代替伺服器做出響應的。
3.3.3、min-fresh
該欄位的意思是說,客戶端不希望快取中副本的新鮮生命週期不低於其建立時間再加上min-fresh指定的時間。如果超過了,那就從伺服器獲取。
4、再驗證
第二個工作就是,快取是如何與伺服器進行互動並再驗證的呢?HTTP協議提供了兩種主要的方式:If-Modified-Since:Date和If-None-Match:Etag。它們又被稱為條件驗證。這兩個標籤的原理類似,只是有一點點細微的差別而已。
整個再驗證的過程和客戶端沒有任何關係,僅涉快取和伺服器雙方。
- 對於If-Modified-Since:Date的方式,快取會根據快取副本的建立時間來建立If-Modified-Since的值,這個值可以和伺服器的響應首部Last-Modified相配合使用,以此判斷內容是否被修改了。
- 如果使用的是If-None-Match:Etag方式,那麼伺服器會為該文件提供一個特殊的標籤(Etag),當快取去和伺服器溝通時,發現伺服器原本的Etag和快取副本不同,也就認為伺服器中的資源被修改了。
如果再驗證發現快取副本沒有更改,那麼伺服器會回送給客戶端304 Not Modified響應。客戶端自動重定向到快取拉取副本,同時快取更新相關首部。如Age、max-age、Etag等。
如果再驗證發現快取副本已改變,那麼伺服器就會將攜帶新首部的新文件傳送給快取,快取進行存取後並對首部進行改裝後再以200 OK對客戶端做出響應。
如果再驗證發現伺服器中已沒有此資源時,伺服器直接向客戶端返回404 Not Found響應。
注:如果報文中即有If-Modified-Since驗證,又有If-None-Match驗證,那麼只有當兩個條件都滿足的情況下才會返回304 Not Modified*響應。
5、快取控制
到這裡為止,快取的處理流程基本上已經理清了。然而,HTTP協議的能力不僅如此,它還為我們開發人員提供了強大的控制快取的機制,我們可以通過相關首部控制是否使用快取,是否一定要再驗證等,這也是由名為Cache-Control的首部欄位提供的:
5.1、no-Store
如果請求報文或響應報文中含有no-Store時,它會禁止快取對響應進行復制。
5.2、no-Cache
如果請求報文和響應報文中含有no-Cache時,也就是強制了快取需要與伺服器端進行在驗證。
5.3、must-revalidate
這是響應報文專用的。
上面說過,請求報文中可以包含Cache-Control:max-stale標籤,它允許快取提供一定期限的超過了新鮮生命週期的資訊。而如果響應報文使用了must-revalidate,那就意味著,快取將嚴格遵守過期資訊,只要超過了新鮮生命週期,就需要和伺服器進行再驗證。
5.4、only-if-cached
這是請求報文專用的。
如果請求報文中有此標籤。那就意味著,客戶端只希望從快取中讀取資源。如果快取中不存在,快取會返回504 Gateway Timeout響應。
5.5、控制私有和公有
- 如果響應報文中含有Cache-Control:public欄位,那就意味著,伺服器只希望代理快取對其進行復制。
- 反之,如果響應報文中含有Cache-Control:private欄位,那就意味著,伺服器只希望私有快取對其進行復制。
5.6 補充:Pragma
由於Control-Cache是HTTP 1.1協議指定的,因此,為了相容1.0版本,客戶端可以傳送包含Pragma:no-Cache首部欄位的請求報文。如果其中有Control-Cache:no-Cache並且該欄位能夠被理解,那麼Pragma中定義的就會被忽略。
6、一種快取策略決策樹
作為Web伺服器端的開發者,可以參考google提供如下決策樹:
image.png
7、總結
快取機制是HTTP協議中相對較難的一塊,也是非常重要的一塊,因此,很難說這篇文章沒有任何錯誤,如有錯誤還請指正批評,謝謝!
代理伺服器簡分類:(並不太全,僅當科普, 瞭解代理在web中的重要作用是有必要的.這裡僅僅是簡單介紹下.)
快取角度分類:
(1) 快取代理 : 根據某種約定,快取曾經請求過的資料
(2) 常規代理 : 只轉發請求的那一種.並不快取資料的代理
控制方分類:
(1) 反向代理:(對於原始伺服器來說,反向代理即是一個客戶端)
a) 服務提供者主觀使用,並控制.
b) 相對其他客戶端來說,離原始伺服器最近(主觀上)
c) 轉發請求,快取,用於負載均衡.
d) 遮蔽其他客戶端與原始伺服器的直接聯,避免原始伺服器被直接攻擊
e) CDN的應用(嚴格來說,CDN已經並非純粹意義上的反向代理了.)
(2) 非反向代理: (非服務提供者所控制)
其他分類:
(1) 使用者代理
a) 瀏覽器
b) 各類終端機
c) 搜尋引擎爬蟲.
(2) 透明代理
(3) 攔截代理
(4) http代理
(5) socket代理(UDP,FTP…etc)
快取相關的約定,HTTP協議、相關概念
HTTP1.1協議:
繼承自http1.0,在其基礎上擴充套件或修改而產生. 比如1.0中Date標頭,允許使用的RFC1036日期格式,的千年蟲問題的修正.悲劇的是,有些明顯的錯誤,無法矯正,比如一個著名的拼寫錯誤. Referer 標頭的拼寫錯誤.一直沿用至今.因其,已經被廣泛應用.在實際使用上,又無傷大雅,所以也就被繼續將錯就錯了.
實體: 用於表示一個,請求或響應的訊息中的一個資源,如post提交的表單內容,或請求伺服器上的abc.jpg時,abc.jpg就代表一個實體.對於一個實體響應來說,除去響應標頭外所剩餘的部分,即是實體主體部分,比如abc.jpg的真實2進位制資料.
標頭(頭域):
(1) 請求標頭 : 在一個請求中使用的相關標頭,用於新增附加資訊,或對被請求伺服器增加限制.
(2) 響應標頭 : 用於新增相關響應的附加資訊.
(3) 實體標頭 : 提供實體相關的附加資訊,如果修改時間,實體的大小等等.
(4) 常規標頭 : (通用標頭)在請求和響應中都可以使用的一些常規標頭,如HTTP1.0所定義的Date.和 Pragma(是的,這並不是一個實體標頭,它僅僅是試圖與客戶端,包括代理,進行協商,不要使用快取的副本.).
補充:HTTP1.1的常規標頭:
Date,Pragma,Cache-Control,Connection,Transfer-Encoding,Upgrade,Trailer,Via,Warning
快取相關標頭:
. Expires (實體標頭,HTTP 1.0+)
一個GMT時間,試圖告知客戶端,在此日期內,可以信任並使用對應快取中的副本,缺點是,一但客戶端日期不準確.則可能導致失效.
.Pragma : no-cache(常規標頭,http1.0+)
對Pragma定義的唯一的偽指令,同http1.1的Cache-Control : no-cache
.Last-Modified(實體標頭,HTTP1.0+)
一個GMT時間,告知,被請求實體的最後修改時間.用於客戶端校驗其快取副本是否仍然可以信任.與其相關的兩個條件請求標頭:
(1) If-Modified-Since:(此標頭,僅對Get方法有意義)
如果實體在指定時間後,沒有修改則返回一個304,否則返回一個常規的Get請求的響應(比如200). 另外,如果該標頭的值是一個非法的值,那麼也同樣返回一個常規的Get請求的響應.
PS:使用者代理髮起 If-Modified-Since嘗試握手的條件,可能會有不同,比如IE系,如果該實體第一次響應頭中包含Cache-Control:no-cache.則 IE不會使用If-Modified-Since請求資源.而其他瀏覽器則會. 但是如果使用Cache-Control:no-store.則所有使用者代理的表現一致.都不使用If-Modified-Since(因為no-store的語義十分強烈.不允許任何快取,這個在後續有專門介紹.)
(2) If-Unmodified-Since:
如果實體在指定時間後,沒有任何修改,那麼就可以直接執行該請求使用方法的對應行為. 而如果有修改,則返回一個412 Precondition Failed狀態碼,並且拋棄該方法對應的行為操作(GET方法除外).
. Cache-Control : (常規標頭,HTTP1.1)
.public:(僅為響應標頭) 響應:告知任何途徑的快取者,可以無條件的快取該響應.
響應:告知快取者(據我所知,是指使用者代理,常見瀏覽器的本地快取.使用者也是指,系統使用者.但也許,不應排除,某些閘道器,可以識別每個終端使用者的情況),只針對單個使用者快取響應. 且可以具體指定某個欄位.如private –“username”,則響應頭中,名為username的標頭內容,不會被共享快取.
請求: 告知快取者,必須原原本本的轉發原始請求,並告知任何快取者,別直接拿你快取的副本,糊弄人.你需要去轉發我的請求,並驗證你的快取(如果有的話).對應名詞:端對端過載. 響應: 允許快取者快取副本.那麼其實際價值是,總是強制快取者,校驗快取的新鮮度.一旦確認新鮮,則可以使用快取副本作為響應. no-cache,還可以指定某個包含欄位,比如一個典型應用,no-cache=Set-Cookie. 這樣做的結果,就是告知快取者,對於Set-Cookie欄位,你不要使用快取內容.而是使用新滴.其他內容則可以使用快取.
請求:告知,請求和響應都禁止被快取.(也許是出於隱私考慮) 響應:同上.
請求:強制響應快取者,根據該值,校驗新鮮性.即與自身的Age值,與請求時間做比較.如果超出max-age值,則強制去伺服器端驗證.以確保返回一個新鮮的響應.其功能本質上與傳統的Expires類似,但區別在於Expires是根據某個特定日期值做比較.一但快取者自身的時間不準確.則結果可能就是錯誤的.而max-age,顯然無此問題. Max-age的優先順序也是高於Expires的.
響應:同上類似,只不過發出方不一樣.
請求:意思是,我允許快取者,傳送一個,過期不超過指定秒數的,陳舊的快取. 響應:同上.
.must-revalidate(僅為響應標頭) 響應:意思是,如果快取過了新鮮期,則必須重新驗證.而不是試圖返回一個不在新鮮期的快取.與no-cache的區別在於,no-cache,完全無視新鮮期的概念.總是強制重新驗證.理論上,must-revalidate更節省流量,但相比no-cache,可能並不總是那麼精準.因為即使快取者,認為是新鮮的,也不能保證伺服器端沒有做過更新.如果快取者是一個快取代理伺服器,如果其試圖重新驗證時,無法連線上原始伺服器,則也不允許返回一個不新鮮的,快取中的副本.而是必須返回一個504 Gateway timeout.
.proxy-revalidate(僅為響應標頭) 響應:限制上與must-revalidate類似.區別在於受體的範圍.proxy-revalidate,是要排除掉使用者代理的快取的.即,其規則並不應用於使用者代理的本地快取上.
.min-fresh(僅為請求標頭) 請求:告知快取者,如果當前時間加上min-fresh的值,超了該快取的過期時間.則要給我一個新的.其實個人覺得,其功能上有點和max-age類似.但是更大的是語義上的區別.
.only-if-cached:(僅為請求標頭) 請求:告知快取者,我希望內容來自快取,我並不關心被快取響應,是否是新鮮的.
.s-maxage(僅為響應標頭) 響應:與max-age的唯一區別是,s-maxage僅僅應用於共享快取.而不引用於使用者代理的本地快取,等針對單使用者的快取. 另外,s-maxage的優先順序要高於max-age.
.cache-extension (cache-extension是一個泛化的代稱.它指所有自定義,或者說擴充套件的,指令,客戶端和伺服器端都可以自定義擴充套件Cache-Control相關的指令.) 那麼,實際上我們可以這樣 Cache-Control:max-age=300, custom-directive = xxx, public. 這樣我們就定義了一個被統稱為cache-extension的擴充套件指令.該指令如果對應的客戶端或伺服器端,不認識,就會忽略掉.
擴充套件指令 中一個常見的東西是 none-check post-check 和 pre-check. 這玩意是IE5被加入的. 所以如果響應頭中有這幾個擴充套件指令,那麼IE就會認得他們, 我經常在一些 為了解決 no-cache + gzip 命中ie6 JSONP 請求,導致指令碼不執行bug的方案中見到這幾個擴充套件指令,其目的是為了讓IE放棄使用本地快取. 我倒是覺得,對IE6放棄使用gzip,是更合理的做法. 當然缺點也很明顯, 如果是cdn部署靜態資源.顯然這樣做會很困難.
bug描述: IE6,某些情況下,開啟gzip的資源,會不渲染或不執行(如果是.js的話.)
會引發此bug的條件: 1. 首先,必須由a頁面指令碼導致跳轉到b頁面 : 即 a頁面有 location.href = b頁面.(點連結,form post,replace, assign等方式都會導致問題,包括target=_blank彈窗的情況) 2. b頁面自身,或其使用動態建立指令碼(硬編碼script src=xxx 也會有此問題) 的響應頭中包含下面情況: cache-control 包含下列偽指令: (1) no-store (2) no-cache + 其他與快取新鮮度檢驗有關頭共存時, 如 max-age=xxx (xxx無所謂.0 或3000都會觸發,) 或 no-cache + must-revalidate甚至是,no-cache, pre-check=0等情況.. (3) no-cache獨立存在時,體現為一種不穩定情況.即當訪問頁面被cache時,可能會觸發.但也不是100%.僅僅是偶爾...
遇到以上情況,頁面可能會不渲染,而指令碼可能會不執行.
ps: 本bug ,與 http1.0 頭域 : Pragma : no-cache ,無關.
解決辦法: 1. 放棄壓縮. 2. 放棄cache-control 中的 no-cache,no-store頭域. 比如 單獨使用max-age=0.並對不支援http1.0的老瀏覽器配合Expires = 一個過期時間.
關於這個bug的msdn的描述: To work around this problem, you can do either of the following:
顯然,微軟給了我們兩條路,去掉no-cache頭,或者使用Expires指定過期時間,強制使其過期代替no-cache, 或者別壓縮. 而pre-check=0.顯然可以代替Expires做到這件事.
關於這幾個擴充套件指令的, 參考msdn 的描述:http://msdn.microsoft.com/en-us/library/ms533020%28VS.85%29.aspx#Use_Cache-Control_Extensions
一張圖:
簡單來說, 就是控制IE,如何使用本地快取 ,如果快取時間,超過post-check的值,就要保證下一次請求該資源,去要驗證過的新鮮的.而pre-check則是超過了,就馬上給個新的. no-check就無需解釋了..
.no-transform 請求:告知代理,不要更改媒體型別,比如jpg,被你改成png. 響應:同上. |
.Etag :(實體標頭,HTTP1.1)
通過[Mog95],( http://www.research.digital.com/wrl/publications/abstracts/95.4.html
遺憾的是,該地址,現在似乎訪問不能.)生成一段可代表實體版本的字串.預設就是一段hash + 時間戳的形式.其實我們是可以使用自己的演算法來生成Etag值.比如md5.
PS:Apache的預設Etag包含Inode,Mtime,Size三部分.而且Etag有強弱之分.比如一般的弱Etag,是以W/開頭的,如:W/”abcde12”,這部分不是我們關注的焦點.因為弱Etag和強Etag的區別只在於演算法.比如某種弱Etag關注的時間精度,為秒.而我們在專案中,最常見的做法是使用MD5.是一種忽略時間維度的,強Etag.為的是保證精確度.以及負載均衡裝置的同步.除非我們的專案有特殊需求.但是往往我們可以根據需求,來調整演算法.而不是沿用一些傳統的弱Etag演算法.
這東西,是要和客戶端的兩個請求標頭配合使用的:
(1) If-Match:
語義:如果有匹配,或者值為”*”,才可以能去執行,請求所使用的方法,所對應的行為.
If-Match,可以看做是一個過濾器,主要應用於資源多版本共存的解決方案.比如伺服器端對同一實體,有多個版本.那麼客戶端,即可按照指定版本來獲取實體.
If-Match的值就是對應指定版本的Etag值.這個值可以是多選的.典型的應用場景是,客戶端使用put方式請求伺服器端,並帶有多個If-Match的值.伺服器端檢查所有該實體的版本.找到匹配項,就立刻更新伺服器端的對應版本.如果無一匹配,則傳送一個412 Precondition Failed狀態碼.
(2) If-None-Match:
語義:如果有任何匹配,或值是”*”,並且原始伺服器存在其請求的實體,則不允許執行該請求所使用放的對應行為,如果此時,該請求使用的get,或head方法.則返回一個304狀態碼.以及其他一些相關的快取控制的標頭.
與If-Match相反 .但它的典型應用,也是我們要關注的部分.支援http1.1的現代瀏覽器,以及web server,應用If-None-Match頭用於,快取新鮮度校驗.典型應用場景就是,一但原始伺服器的某個響應中包含Etag時,如果瀏覽器本地快取了該實體.那麼在第二次的常規的get或head請求時,就會自動帶上 If-None-Match頭.當原始伺服器上該實體的版本對應的Etag值與之匹配時,則原始伺服器會返回304狀態碼.然後瀏覽器認為本地快取是新鮮的.則繼續使用快取的實體. 但,其實Etag的本意是版本管理.而並不是快取有效性校驗.這應該是一個衍生出來的使用方式. 而這種方式相比Last-Modified校驗方式的好處是,如果我們消除時間戳部分,僅使用hash作為Etag值. 就可以方便做負載均衡同步.
.Age(響應標頭,HTTP1.1)
Age標頭,對於原始伺服器來說,用於指明,當前資源被生成了多久,即存活期.而對於一個快取代理伺服器來說,它表示快取副本,被快取了多久.快取代理伺服器,必須生成Age頭.其值以秒為單位.且可能為負值.
.Vary(響應標頭,HTTP1.1)
Vary標頭,用於列出一組響應標頭,用於快取者從其快取副本中篩選合適的變體.舉個例子來說,不同的請求方法,導致對同一資源的響應有區別.這就導致快取者有多份快取副本.那麼Vary所列出的標頭項,就是選擇副本時的一個重要依據. 比如Vary:Accept-Language.那麼如果新的請求中的Accept-Language標頭的值,而原始請求(被快取的那個)中並未包含與之匹配的Accept-Language的標頭的話.那就必須放棄該副本.而是把請求轉發到原始伺服器.
另一個Vary的典型應用是,Vary:Accept-Encoding.這樣做的意義在於,某些使用者使用的瀏覽器,可能不支援一些特定的壓縮演算法.那麼當這個使用者途徑的某個共享的快取代理伺服器,所快取的使用了某種壓縮演算法的響應,就不能直接返回給該使用者.如果伺服器端,並沒有配置這個標頭,那就可能產生悲劇.即使用者的瀏覽器無法解壓縮返回的資源.導致各種異常狀況的出現.
範圍相關標頭.以及快取的意義.
請求標頭:
.Range : 1000- | -1000 | 0 - Content-Length
Range頭可以指定像伺服器(並不一定總是原始伺服器,比如一個快取代理伺服器)端獲取範圍內的資料,這種格式是很鬆散的,可以用”,”逗號分割範圍的一種表示式.比如1-100,-1 這就表示要獲取第一到100個位元組,以及最後一個位元組的部分.又或者 50-則表示50個位元組之後的所有資料搭配可以很靈活.不過,當使用多個範圍的Range標頭時,假設範圍都是合法的(不存在越界的情況,如果越界,則可能返回417 Requested Range Not Satisfiable狀態碼).則伺服器響應時,會修改響應中的Content-Type標頭為 如下格式:
Content-Type : multipart/byteranges;boundary=----ROPE----
----ROPE----
Content-Type:image/jpeg
Content-Range:bytes 0-100/2000
此處為0-100的資料
----ROPE----
Content-Type:image/jpeg
Content-Range:xxx-xxx/2000
xxx-xxx範圍的資料.
----ROPE----
好吧,以上這些不是我們關心的重點.我們關心的是和快取有關的情況,其實對於任何客戶端(包括使用者代理,以及各類快取伺服器)來說.如果它明確的知道自己想要某一部分範圍的資料.就可以使用Range標頭. 但事實上,比如對於瀏覽器來說,一般情況下,只有當伺服器端的響應中包含Accept-Ranges:bytes標頭時,才可能會在有斷點續傳需求的時候,自動使用Range頭去請求實體.而代只有在響應中,明確出現Accept-Ranges:none時.才會完全避免客戶端(包括快取代理伺服器),使用Range標頭去獲取部分資料.這是要區別看待的.
PS:Range頭,還可以在GET方式下與If-Unmodified-Since標頭配合,進行條件請求.其行為類似於If-Range頭中使用日期格式做新鮮度校驗.
.If-Range : 實體的Etag值 | date日期值.
配合Range使用的條件請求標頭,該標頭的值可以是被請求實體的Etag值,又或者是其Last-Modified的日期.客戶端所快取的部分資料,是新鮮的,伺服器端才會,以206方式返回這部分資料.否則,以200方式返回全部資料.
響應標頭:
.Accept-Ranges : bytes | none
用於說明,伺服器是否支援範圍請求.一般,我們常常為圖片等資源設定Accept-Ranges:bytes標頭.以便使客戶端,可以使用斷點續傳功能.當然,這一切的前提是,客戶端之前有快取部分資料.或者換個角度說,如果伺服器端明確宣告,不允許快取某個實體.那麼斷點續傳也就無從說起了. 所以正確的伺服器端配置.是一切的基礎.
.Content-Range : bytes 0-xxxx/xxxx
標明當前伺服器端返回資料的範圍. /xxxx部分是總長度.
HTTP響應類別:
.資訊類:1xx(資訊類響應頭,是在1.1中才被真正具體定義.我們暫時不關注它.)
.成功類:2xx
.重定向類:3xx (我們目前只關注304,是的,304是屬於重定向類的,我們姑且理解為,重定向到客戶端快取副本上去.)
.客戶機錯誤類:4xx
.伺服器錯誤類:5xx
關於客戶端快取,我們應該關注哪些概念
1. 壽命:
即響應的壽命,指從原始伺服器發出實體後所經歷的時間,或者是重新驗證,證明某快取仍處於最新狀態(可信賴)之後,所經歷的時間. 參考響應頭中的 Age,其單位是秒.
2. 過期時間:
對於一個快取實體來說,其過期時間,是由原始伺服器設定的.(參考響應頭中的Expires,Cache-Control:max-age…等)一但發現過期,則快取,必須重新驗證實體,才能決定是否使用快取中對應的實體副本,作為響應.如果原始伺服器,未設定過期時間,則快取可以自己做主,設定一個它認為合適的過期時間(就瀏覽器來說,IE瀏覽器再這裡實現的與眾不同,非IE的主流瀏覽器,不會自作主張的設定一個自認為合適的過期時間,或者說他們總是認為這樣的實體.不應該被快取下來.而IE,則會有一個會話級的快取.實際上也不是真正有一個過期時間了.所謂會話級快取,即瀏覽器不關閉的情況下,始終去讀取瀏覽器本地的快取.而不會去試圖做任何驗證的工作.)
3. 新鮮期和陳舊性:(此概念為伺服器端概念)
一個HTTP響應是在伺服器端某個特定時刻生成的.原始伺服器決定這個響應在多長時間內,是確定新鮮的.在這個新鮮期內,相同實體的請求.可以使用原來生成的那個內容作為響應.即伺服器端快取.一但原始伺服器端確認某相同實體的響應,已過了新鮮期.即處於陳舊狀態.則原始伺服器,需要進一步處理這個響應,比如重新生成響應,或者驗證是否可以繼續使用該響應.並重新設定其新鮮期.
4. 有效性:
快取,可以與伺服器聯絡,來驗證某個快取副本,是否是可信賴的最新狀態.這種檢查,被稱為有效性重驗證.重驗證也可以針對代理伺服器進行,而不一定總要和原始伺服器進行驗證.
5. 可快取性(快取能力):
快取者, 必須決定是否存對一個響應,進行快取.這取決於很多因素,比如原始伺服器是否允許響應被快取,是否設定了快取過期時間.而有時候即快取者快取了一個響應.它仍然需要對該響應,進行驗證,以確保該快取是新鮮的.快取能力和響應驗證,是緊密相關的.
那麼協議就是全部麼?使用者代理就那聽話麼? 我們理所當然的寫一些東西的時候就可靠了麼?
下面這段是我的evernote中翻找出來的一段和快取有關的坑爹的例子.也算給我們自己提個醒. 使用者代理可能應為某些原因,給我們造成一些困擾. 所以切記想當然. 一切應以實測為準. 同樣.代理伺服器也完全可以違背http協議相關的快取策略.所以 理論上很多東西,都是不可信的:
demo:
document.onclick = function () {
var img = new Image;
document.body.appendChild(img);
img.src=https://passport.baidu.com/?verifypic";
alert(img.complete)
//CollectGarbage();
}
IE7+,opera 如果src是一個靜態不變的地址.則 不會再發起請求(無視http快取相關頭域).而直去取快取檔案 。 而且是完全不走network 模組的那種. 所以甚至httpWatch也不會抓到 cache 的項.
因為該img 會被儲存在記憶體中. 即使 沒有 img控制代碼,而直接是一個 new Image().src=xxx 也是如此. 並且 無論圖片資源是否強制快取.http頭指定該資源不快取,亦如此. 這也是為什麼IE6有另外某個必須保持new Image控制代碼的原因.因為IE6 和 IE7+對new Image的策略完全不同, IE6是不會在記憶體中保持new Image物件的(這也是為什麼ie6會有後面提到的那個問題的本質原因.).而 IE7+會在記憶體中快取該物件. 並以url作為索引.opera12-也是如此.
所以,如果是藉助new Image 上報,即使image資源的http 相關快取頭,配置正確(no-cache,no-store,expires過期).在IE7+和Opera12-中,也應該使用隨機數.來繞過瀏覽器 記憶體快取new Image的坑. 但是不能因此而放棄配置正確的http快取策略先關頭. 因為這才是正道. 符合語義的做法. 隨機數是為了修正使用者代理的錯誤. 不能混淆或相互代替. 另外隨機數,也可能是為了解決另外一個問題, 仍然和IE系列有關,比如未指定相關快取頭域情況下,IE系預設對資源有會話級快取的bug.
而ie6 , 一個簡單的 new Image().src=xxx 的上報. 會可能在GC(瀏覽器主動觸發,如因頁面渲染觸發的GC,而不是主動呼叫CollectGarbage方法)時abort 掉這個請求.(此問題其他瀏覽器,IE7+都不存在.)
即使是
var img = new Image();
img.src= xxx 也如此
解決辦法:
var log = function(){
var list = [];
return function(src){
var index = list.push(new Image) -1;
list[index].onload = function(){
list[index] = list[index].onload = null;
}
list[index ].src = src;
}
}();
但是要注意的問題是: 0 content length or not image mimeTypes . 不會觸發onload. 也就是說,上面的程式碼.有可能不會及時回收資源.這取決於上報的伺服器的配置了. 也許可以藉助onerror來綜合解決。但我個人認為.這裡不會造成記憶體洩露. 就算你一個頁面有100次上報,也無所謂. 而且 對於ie7 - ie10 pp2(更高版本還不好說)來說 .總是在記憶體中自動快取每個new Image物件,那麼是否我們主動使其持有控制代碼,其實意義也都不大了.