1. 程式人生 > >HTTP協議進階之快取

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年,那麼,該副本將在我的有生之年都不會過期。待我老時,瀏覽的還是幾十年前的頁面。
實際上ExpiresHTTP 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:DateIf-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-CacheHTTP 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(僅為響應標頭)    

    響應:告知快取者(據我所知,是指使用者代理,常見瀏覽器的本地快取.使用者也是指,系統使用者.但也許,不應排除,某些閘道器,可以識別每個終端使用者的情況),只針對單個使用者快取響應. 且可以具體指定某個欄位.如private –“username”,則響應頭中,名為username的標頭內容,不會被共享快取.


.no-cache:    

     請求: 告知快取者,必須原原本本的轉發原始請求,並告知任何快取者,別直接拿你快取的副本,糊弄人.你需要去轉發我的請求,並驗證你的快取(如果有的話).對應名詞:端對端過載.    

     響應: 允許快取者快取副本.那麼其實際價值是,總是強制快取者,校驗快取的新鮮度.一旦確認新鮮,則可以使用快取副本作為響應. no-cache,還可以指定某個包含欄位,比如一個典型應用,no-cache=Set-Cookie. 這樣做的結果,就是告知快取者,對於Set-Cookie欄位,你不要使用快取內容.而是使用新滴.其他內容則可以使用快取.


.no-store:    

    請求:告知,請求和響應都禁止被快取.(也許是出於隱私考慮)    

     響應:同上.


.max-age:    

     請求:強制響應快取者,根據該值,校驗新鮮性.即與自身的Age值,與請求時間做比較.如果超出max-age值,則強制去伺服器端驗證.以確保返回一個新鮮的響應.其功能本質上與傳統的Expires類似,但區別在於Expires是根據某個特定日期值做比較.一但快取者自身的時間不準確.則結果可能就是錯誤的.而max-age,顯然無此問題. Max-age的優先順序也是高於Expires的.

     

            響應:同上類似,只不過發出方不一樣.


.max-stale:    

     請求:意思是,我允許快取者,傳送一個,過期不超過指定秒數的,陳舊的快取.    

     響應:同上.

 

.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:


  If you use a Cache-Control: no-cache HTTP header to prevent the files from caching, remove that header. In some situations, if you substitute an Expires HTTP header, you do not trigger the problem.

  -or-

  Do not enable HTTP compression for the script files.

      顯然,微軟給了我們兩條路,去掉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

  • post-check
    • Defines an interval in seconds after which an entity must be checked for freshness. The check may happen after the user is shown the resource but ensures that on the next roundtrip the cached copy will be up-to-date.
  • pre-check
    • Defines an interval in seconds after which an entity must be checked for freshness prior to showing the user the resource.

    一張圖:

           

         簡單來說, 就是控制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物件,那麼是否我們主動使其持有控制代碼,其實意義也都不大了.