瀏覽器快取詳解:expires,cache-control,last-modified,etag詳細說明

ZY_FlyWay發表於2018-12-29

最近在對CDN進行優化,對瀏覽器快取深入研究了一下,記錄一下,方便後來者

畫了一個草圖:

在這裡插入圖片描述

每個狀態的詳細說明如下:

1、Last-Modified

在瀏覽器第一次請求某一個URL時,伺服器端的返回狀態會是200,內容是你請求的資源,同時有一個Last-Modified的屬性標記(HttpReponse Header)此檔案在服務期端最後被修改的時間,格式類似這樣:

Last-Modified:Tue, 24 Feb 2009 08:01:04 GMT

客戶端第二次請求此URL時,根據HTTP協議的規定,瀏覽器會向伺服器傳送If-Modified-Since報頭(HttpRequest Header),詢問該時間之後檔案是否有被修改過:

If-Modified-Since:Tue, 24 Feb 2009 08:01:04 GMT

如果伺服器端的資源沒有變化,則自動返回HTTP304(NotChanged.)狀態碼,內容為空,這樣就節省了傳輸資料量。當伺服器端程式碼發生改變或者重啟伺服器時,則重新發出資源,返回和第一次請求時類似。從而保證不向客戶端重複發出資源,也保證當伺服器有變化時,客戶端能夠得到最新的資源。

注:如果If-Modified-Since的時間比伺服器當前時間(當前的請求時間request_time)還晚,會認為是個非法請求

2、Etag工作原理

HTTP協議規格說明定義ETag為“被請求變數的實體標記”(參見14.19)。簡單點即伺服器響應時給請求URL標記,並在HTTP響應頭中將其傳送到客戶端,類似伺服器端返回的格式:

Etag:“5d8c72a5edda8d6a:3239″

客戶端的查詢更新格式是這樣的:

If-None-Match:“5d8c72a5edda8d6a:3239″

如果ETag沒改變,則返回狀態304。

即:在客戶端發出請求後,HttpReponse Header中包含Etag:“5d8c72a5edda8d6a:3239″

標識,等於告訴Client端,你拿到的這個的資源有表示ID:5d8c72a5edda8d6a:3239。當下次需要發Request索要同一個URI的時候,瀏覽器同時發出一個If-None-Match報頭(Http RequestHeader)此時包頭中資訊包含上次訪問得到的Etag:“5d8c72a5edda8d6a:3239″標識。

If-None-Match:“5d8c72a5edda8d6a:3239“

,這樣,Client端等於Cache了兩份,伺服器端就會比對2者的etag。如果If-None-Match為False,不返回200,返回304(Not Modified) Response。

3、Expires

給出的日期/時間後,被響應認為是過時。如Expires:Thu, 02 Apr 2009 05:14:08 GMT

需和Last-Modified結合使用。用於控制請求檔案的有效時間,當請求資料在有效期內時客戶端瀏覽器從快取請求資料而不是伺服器端.當快取中資料失效或過期,才決定從伺服器更新資料。

4、Last-Modified和Expires

Last-Modified標識能夠節省一點頻寬,但是還是逃不掉髮一個HTTP請求出去,而且要和Expires一起用。而Expires標識卻使得瀏覽器乾脆連HTTP請求都不用發,比如當使用者F5或者點選Refresh按鈕的時候就算對於有Expires的URI,一樣也會發一個HTTP請求出去,所以,Last-Modified還是要用的,而且要和Expires一起用。

5、Etag和Expires

如果伺服器端同時設定了Etag和Expires時,Etag原理同樣,即與Last-Modified/Etag對應的HttpRequestHeader:If-Modified-Since和If-None-Match。我們可以看到這兩個Header的值和WebServer發出的Last-Modified,Etag值完全一樣;在完全匹配If-Modified-Since和If-None-Match即檢查完修改時間和Etag之後,伺服器才能返回304.

6、Last-Modified和Etag

分散式系統裡多臺機器間檔案的last-modified必須保持一致,以免負載均衡到不同機器導致比對失敗

分散式系統儘量關閉掉Etag(每臺機器生成的etag都會不一樣)

Last-Modified和ETags請求的http報頭一起使用,伺服器首先產生Last-Modified/Etag標記,伺服器可在稍後使用它來判斷頁面是否已經被修改,來決定檔案是否繼續快取

過程如下:

1.客戶端請求一個頁面(A)。

2.伺服器返回頁面A,並在給A加上一個Last-Modified/ETag。

3.客戶端展現該頁面,並將頁面連同Last-Modified/ETag一起快取。

4.客戶再次請求頁面A,並將上次請求時伺服器返回的Last-Modified/ETag一起傳遞給伺服器。

5.伺服器檢查該Last-Modified或ETag,並判斷出該頁面自上次客戶端請求之後還未被修改,直接返回響應304和一個空的響應體。

注:

1、Last-Modified和Etag頭都是由WebServer發出的HttpReponse Header,WebServer應該同時支援這兩種頭。

2、WebServer傳送完Last-Modified/Etag頭給客戶端後,客戶端會快取這些頭;

3、客戶端再次發起相同頁面的請求時,將分別傳送與Last-Modified/Etag對應的HttpRequestHeader:If-Modified-Since和If-None-Match。我們可以看到這兩個Header的值和WebServer發出的Last-Modified,Etag值完全一樣;

4、通過上述值到伺服器端檢查,判斷檔案是否繼續快取;

7、關於 Cache-Control: max-age=秒 和 Expires

Expires = 時間,HTTP 1.0 版本,快取的載止時間,允許客戶端在這個時間之前不去檢查(發請求)
max-age = 秒,HTTP 1.1版本,資源在本地快取多少秒。
如果max-age和Expires同時存在,則被Cache-Control的max-age覆蓋。

Expires 的一個缺點就是,返回的到期時間是伺服器端的時間,這樣存在一個問題,如果客戶端的時間與伺服器的時間相差很大,那麼誤差就很大,所以在HTTP 1.1版開始,使用Cache-Control: max-age=秒替代。

Expires =max-age + “每次下載時的當前的request時間”

所以一旦重新下載的頁面後,expires就重新計算一次,但last-modified不會變化

Last-Modified和Expires針對瀏覽器,而ETag則與客戶端無關,所以可適合REST架構中。兩者都應用在瀏覽器端的區別是:Expires日期到達前,瀏覽器不會再發出新的請求,除非使用者按瀏覽器的重新整理,所以,Last-Modified和Expires基本是降低瀏覽器向伺服器發出請求的次數,而ETag更側重客戶端和伺服器之間聯絡。

先談Last-Modified和Expires,最新的Tomcat 7 將ExpireFilter加入其容器中,這樣,Java WEB也可以象Apache的Mod_expire模組一樣對Http頭部進行統一設定了,不過它只對響應文件型別進行統一設定判斷,如text/html或text/image 或/css等等,如果想對個別URL輸出的jsp進行定製就不行,urlrewrite據說是可以,但是要把URL在其配置檔案再配置一下,麻煩,一旦jsp改動影響面大,還有一個問題就是web.xml配置了Tomcat 7容器的ExpireFilter,與容器耦合,移植性差(移植到Resin就不行了)。

所以,我在jivejdon 4.2最新版本中,通過加入下面一段程式碼在伺服器端對來自客戶端的Last-Modified以及當前時間進行判斷,如未過期,response.setStatus設為304,可以終止後面的各種Jsp介面計算,直接返回瀏覽器一個304的響應包,JSP頁面也不會輸出到客戶端,將頻寬節省給更加需要互動實時性的請求。

再談談ETag,ETag定義:RFC2616(也就是HTTP/1.1)中沒有說明ETag該是什麼格式的,只要確保用雙引號括起來就行了,所以你可以用檔案的hash,甚至是直接用Last-Modified,以下是伺服器端返回的格式:
ETag: “50b1c1d4f775c61:df3” 客戶端向服務端發出的請求:If-None-Match: W/“50b1c1d4f775c61:df3” 這樣,在J2EE/JavaEE伺服器端,我們判斷如果ETag沒改變也是返回狀態304,起到類似Last-Modified和Expires效果。

與Last-Modified和Expires區別是:如果過了Expires日期,伺服器肯定會再次發出JSP完整響應;或者使用者強按瀏覽器的重新整理按鈕,伺服器也必須響應,apache等靜態頁面輸出也是這樣,但是這時動態頁面就發揮了作用,如果JSP涉及的業務領域模型還是沒有更新,和原來一樣,那麼就不必再將動態頁面輸出了(瀏覽器客戶端已有一份),從Etag中獲取上次設定的領域模型物件修改日期,和現在記憶體中領域模型(In-memory Model)修改日期進行比較,如果修改日期一致,表示領域模型沒有被更新過,那麼返回響應包304,瀏覽器將繼續用本地快取的該頁面,再次節省了頻寬傳輸。

通過上述Expire和Etag兩次快取,可以大大降低伺服器的響應負載,如果你的應用不是狀態集中併發修改和實時輸出,而是分散修改然後分發,如個人空間 個人部落格(每個人只是修改它們自己的狀態,不影響全域性)或QQ類似個人工具,那麼採取這樣的方法效果非常明顯,實際就是一種動態頁面靜態化技術,但比通常事先進行頁面靜態化要靈活強大。

InfoQ的那篇:http://www.infoq.com/articles/etags
還用MD5計算放入其中,Md5計算稍微複雜點,負載大了點,有的人結合Hibernate或資料庫觸發器來判斷資料庫資料是否更新,以決定Etag的更新,這將表現層和持久層耦合在一起,由於JiveJdon採取的是MDD/DDD模型驅動架構,表現層的Etag更新是根據中間業務層的模型物件修改日期來決定,不涉及資料庫層,而且起到伺服器快取的更新和http的Etag更新一致的效果,在鬆耦合設計和效能上取得綜合平衡。

相關文章