home > webfront > SGML > web >

瀏覽器緩存機制剖析

author:zhoulujun    hits:

緩存一直是前端優化的主戰場, 利用好緩存就成功了一半 本篇從http請求和響應的頭域入手, 讓你對瀏覽器緩存有個整體的概念 最終你會發現強緩存, 協商緩存 和 啟發式緩存是如此的簡單

最初我只知道Expires、Cache-Control、Last-Modified、ETag都是在http response的返回header中用來控制瀏覽器客戶端緩存行為的。但是瀏覽器這些字段背后的原理只知道個大概,所以今天特別來梳理下瀏覽器緩存機制

瀏覽器對于請求資源, 流程如圖所示:

1.png

可以看到瀏覽器的緩存機制分為兩個部分:

1、當前緩存是否過期?

2、服務器中的文件是否有改動?

第一步:判斷當前緩存是否過期

這是判斷是否啟用緩存的第一步。如果瀏覽器通過某些條件(條件之后再說)判斷出來,ok現在這個緩存沒有過期可以用,那么連請求都不會發的,直接是啟用之前瀏覽器緩存下來的那份文件,此時狀態碼為200

第二步:判斷服務器中的文件是否有改動

1、緩存過期,文件有改動,那么下載新文件,此時狀態碼為200

2、緩存過期,文件無改動,那么服務器只會給你返回一個頭信息(304),瀏覽器讀取304后,就會去讀取過期緩存文件。

如何判斷緩存的過期以及文件的變動?

瀏覽器擁有一系列成熟的緩存策略. 按照發生的時間順序分別為存儲策略, 過期策略, 協商策略,  其中存儲策略在收到響應后應用, 過期策略, 協商策略在發送請求前應用. 流程圖如下所示.

Screen-Shot-2018-05-18-at-20.34.01.png




判斷緩存過期,主要還是靠HTTP頭,廢話不多說, 我們先來看兩張表格

http header中與緩存有關的key

key描述存儲策略過期策略協商策略
Cache-Control指定緩存機制,覆蓋其它設置????
Pragmahttp1.0字段,指定緩存機制??

Expireshttp1.0字段,指定緩存的過期時間
??
Last-Modified資源最后一次的修改時間

??
ETag唯一標識請求資源的字符串

??

2.緩存協商策略用于重新驗證緩存資源是否有效, 有關的key如下.

key描述
If-Modified-Since緩存校驗字段, 值為資源最后一次的修改時間, 即上次收到的Last-Modified值
If-Unmodified-Since同上, 處理方式與之相反
If-Match緩存校驗字段, 值為唯一標識請求資源的字符串, 即上次收到的ETag值
If-None-Match同上, 處理方式與之相反

下面我們來看下各個頭域(key)的作用.

Cache-Control

瀏覽器緩存里, Cache-Control是金字塔頂尖的規則, 它藐視一切其他設置, 只要其他設置與其抵觸, 一律覆蓋之.

不僅如此, 它還是一個復合規則, 包含多種值, 橫跨 存儲策略, 過期策略 兩種, 同時在請求頭和響應頭都可設置.

語法為: “Cache-Control : cache-directive”.

Cache-directive共有如下12種(其中請求中指令7種, 響應中指令9種):

Cache-directive描述存儲策略過期策略請求字段響應字段
public資源將被客戶端和代理服務器緩存??

??
private資源僅被客戶端緩存, 代理服務器不緩存??

??
no-store請求和響應都不緩存??
????
no-cache相當于max-age:0,must-revalidate即資源被緩存, 但是緩存立刻過期, 同時下次訪問時強制驗證資源有效性????????
max-age緩存資源, 但是在指定時間(單位為秒)后緩存過期????????
s-maxage同上, 依賴public設置, 覆蓋max-age, 且只在代理服務器上有效.????
??
max-stale指定時間內, 即使緩存過時, 資源依然有效
????
min-fresh緩存的資源至少要保持指定時間的新鮮期
????
must-revalidation / proxy-revalidation如果緩存失效, 強制重新向服務器(或代理)發起驗證(因為max-stale等字段可能改變緩存的失效時間)
??
??
only-if-cached僅僅返回已經緩存的資源, 不訪問網絡, 若無緩存則返回504

??
no-transform強制要求代理服務器不要對資源進行轉換, 禁止代理服務器對 Content-Encoding, Content-Range, Content-Type字段的修改(因此代理的gzip壓縮將不被允許)

????

假設所請求資源于4月5日緩存, 且在4月12日過期.

當max-age 與 max-stale 和 min-fresh 同時使用時, 它們的設置相互之間獨立生效, 最為保守的緩存策略總是有效. 這意味著, 如果max-age=10 days, max-stale=2            days, min-fresh=3 days, 那么:

  • 根據max-age的設置, 覆蓋原緩存周期,  緩存資源將在4月15日失效(5+10=15);

  • 根據max-stale的設置, 緩存過期后兩天依然有效, 此時響應將返回110(Response is stale)狀態碼, 緩存資源將在4月14日失效(12+2=14);

  • 根據min-fresh的設置, 至少要留有3天的新鮮期, 緩存資源將在4月9日失效(12-3=9);

由于客戶端總是采用最保守的緩存策略, 因此, 4月9日后, 對于該資源的請求將重新向服務器發起驗證.

技術細節:must-revalidate,no-cache,max-age=0,no-store,             

  • must-revalidate:   如果你配置了max-age信息,當緩存資源仍然新鮮(小于max-age)時使用緩存,否則需要對資源進行驗證。所以must-revalidate可以和max-age組合使用Cache-Control: must-revalidate, max-age=60

  • no-cache: 雖然字面意義是“不要緩存”。但它實際上的機制是,仍然對資源使用緩存,但每一次在使用緩存之前必須(MUST)向服務器對緩存資源進行驗證

  • max-age=0:告知瀏覽器,資源已經過期了,你應該(SHOULD)對資源進行重新驗證了;在重新獲取資源之前,先檢驗ETag/Last-Modified。而no-cache則是告訴瀏覽器在每一次使用緩存之前,你必須(MUST)對資源進行重新驗證。

    區別在于:SHOULD是非強制性的,而MUST是強制性的。在no-cache的情況下,瀏覽器在向服務器驗證成功之前絕不會使用過期的緩存資源,而max-age=0則不一定了。

  • no-store:  不使用任何緩存。有趣的事情是,雖然no-cache意為對緩存進行驗證,但是因為大家廣泛的錯誤的把它當作no-store來使用,所以有的瀏覽器也就附和了這種設計。這是一個典型的劣幣驅逐良幣

不管是max-age=0還是no-cache,都會返回304(資源無修改的情況下),no-store才是真正的不進行緩存

public VS. private

要知道從服務器到瀏覽器之間并非只有瀏覽器能夠對資源進行緩存,服務器的返回可能會經過一些中間(intermediate)服務器甚至甚至專業的中間緩存服務器,還有CDN。而有些請求返回是用戶級別、是私人的,所以你可能不希望這些中間服務器緩存返回。此時你需要將Cache-Control設置為private以避免暴露。

所以綜上,關于如何設計緩存機制,還是要依據你的需求而定,可以通過下面的這棵決策樹決定:

v2-95444200875d9cdc6783deb48e72da6c_hd.jpg


Expires

Expires:Wed, 05 Apr 2017 00:55:35 GMT1

即到期時間, 以服務器時間為參考系,在指定的日期到達之前再次訪問則認為緩存有效, 其優先級比 Cache-Control:max-age 低, 兩者同時出現在響應頭時, Expires將被后者覆蓋(Expires在HTTP/1.0中已經定義,Cache-Control:max-age=xxx在HTTP/1.1中才有定義,為了向下兼容,僅使用max-age不夠)

如果Expires, Cache-Control: max-age, 或 Cache-Control:s-maxage 都沒有在響應頭中出現, 并且也沒有其它緩存的設置, 那么瀏覽器默認會采用一個啟發式的算法, 通常會取響應頭的Date_value - Last-Modified_value值的10%作為緩存時間.

如下資源便采取了啟發式緩存算法.

其緩存時間為 (Date_value - Last-Modified_value) * 10%, 計算如下:

const Date_value = new Date('Thu, 06 Apr 2017 01:30:56 GMT').getTime();const LastModified_value = new Date('Thu, 01 Dec 2016 06:23:23 GMT').getTime();const cacheTime = (Date_value - LastModified_value) / 10;const Expires_timestamp = Date_value + cacheTime;const Expires_value = new Date(Expires_timestamp);console.log('Expires:', Expires_value); // Expires: Tue Apr 18 2017 23:25:41 GMT+0800 (CST)123456

可見該資源將于2017年4月18日23點25分41秒過期, 嘗試以下兩步進行驗證:

1) 試著把本地時間修改為2017年4月18日23點25分40秒, 迅速刷新頁面, 發現強緩存依然有效(依舊是200 OK (from disk cache)).

2) 然后又修改本地時間為2017年4月18日23點26分40秒(即往后撥1分鐘), 刷新頁面, 發現緩存已過期, 此時瀏覽器重新向服務器發起了驗證, 且命中了304協商緩存, 如下所示.

3) 將本地時間恢復正常(即 2017-04-06 09:54:19). 刷新頁面, 發現Date依然是4月18日, 如下所示.

?? Provisional headers are shown 和Date字段可以看出來, 瀏覽器并未發出請求, 緩存依然有效, 只不過此時Status Code顯示為200 OK.            (甚至我還專門打開了charles, 也沒有發現該資源的任何請求, 可見這個200 OK多少有些誤導人的意味)

可見, 啟發式緩存算法采用的緩存時間可長可短, 因此對于常規資源, 建議明確設置緩存時間(如指定max-age 或 expires).


Expires VS. max-age

Expires和max-age都是用于控制緩存的生命周期。不同的是Expires指定的是過期的具體時間,例如Sun, 21 Mar 2027 08:52:14            GMT,而max-age指定的是生命時長秒數315360000。

區別在于Expires是 HTTP/1.0 的中的標準,而max-age是屬于Cache-Control的內容,是 HTTP/1.1 中的定義的。但為了想向前兼容,這兩個屬性仍然要同時存在。


但有一種更傾向于使用max-age的觀點認為Expires過于復雜了。例如上面的例子Sun, 21 Mar 2027 08:52:14            GMT,如果你在表示小時的數字缺少了一個0,則很有可能出現出錯;如果日期沒有轉換到用戶的正確時區,則有可能出錯。這里出錯的意思可能包括但不限于緩存失效、緩存生命周期出錯等。

判斷文件變動

常用的方式為Etag和Last-Modified,思路上差不多,這里作者只介紹Last-Modified的用法。

Last-Modified方式需要用到兩個字段:Last-Modified & if-modified-since。

先來看下這兩個字段的形式:

Last-Modified : Fri , 12 May 2006 18:53:33 GMT

If-Modified-Since : Fri , 12 May 2006 18:53:33 GMT

可以看出其實形式是一樣的,就是一個標準時間。那么怎么用呢?來看下圖:

8.png

當第一次請求某一個文件的時候,就會傳遞回來一個Last-Modified 字段,其內容是這個文件的修改時間。當這個文件緩存過期,瀏覽器又向服務器請求這個文件的時候,會自動帶一個請求頭字段If-Modified-Since,其值是上一次傳遞過來的Last-Modified的值,拿這個值去和服務器中現在這個文件的最后修改時間做對比,如果相等,那么就不會重新拉取這個文件了,返回304讓瀏覽器讀過期緩存。如果不相等就重新拉取。


Last-Modified

語法: Last-Modified: 星期,日期 月份 年份 時:分:秒 GMT

Last-Modified: Tue, 04 Apr 2017 10:01:15 GMT1

用于標記請求資源的最后一次修改時間, 格式為GMT(格林尼治標準時間). 如可用 new Date().toGMTString()獲取當前GMT時間. Last-Modified是 ETag 的fallback機制, 優先級比 ETag 低, 且只能精確到秒, 因此不太適合短時間內頻繁改動的資源. 不僅如此, 服務器端的靜態資源, 通常需要編譯打包, 可能出現資源內容沒有改變,  而Last-Modified卻改變的情況。


瀏覽器會向服務器傳送 If-Modified-Since 報頭(Http Request Header),詢問該時間之后文件是否有被修改過

If-Modified-Since

語法同上, 如:

If-Modified-Since: Tue, 04 Apr 2017 10:12:27 GMT1

緩存校驗字段, 其值為上次響應頭的Last-Modified值, 若與請求資源當前的Last-Modified值相同, 那么將返回304狀態碼的響應, 反之, 將返回200狀態碼響應.

ETag

ETag:"fcb82312d92970bdf0d18a4eca08ebc7efede4fe"1

實體標簽,(參見14.19), 服務器資源的唯一標識符, 瀏覽器可以根據ETag值緩存數據, 節省帶寬. 如果資源已經改變, etag可以幫助防止同步更新資源的相互覆蓋. ETag 優先級比 Last-Modified 高.

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

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

If-None-Match

語法: If-None-Match: ETag_value 或者 If-None-Match: ETag_value, ETag_value, …

緩存校驗字段, 結合ETag字段, 常用于判斷緩存資源是否有效, 優先級比If-Modified-Since高.

  • 對于 GET 或 HEAD 請求, 如果其etags列表均不匹配, 服務器將返回200狀態碼的響應, 反之, 將返回304(Not Modified)狀態碼的響應. 無論是200還是304響應,                    都至少返回 Cache-ControlContent-LocationDate,                    ETagExpires, and Vary 中之一的字段.

  • 對于其他更新服務器資源的請求, 如果其etags列表匹配, 服務器將執行更新, 反之, 將返回412(Precondition Failed)狀態碼的響應。

ETag&(If-Match&If-None-Match)關系如同Last-Modified&if-modified-since

If-Match

語法: If-Match: ETag_value 或者 If-Match: ETag_value, ETag_value, …

緩存校驗字段, 其值為上次收到的一個或多個etag 值. 常用于判斷條件是否滿足, 如下兩種場景:

  • 對于 GET 或 HEAD 請求, 結合 Range 頭字段, 它可以保證新范圍的請求和前一個來自相同的源, 如果不匹配, 服務器將返回一個416(Range Not Satisfiable)狀態碼的響應.

  • 對于 PUT 或者其他不安全的請求, If-Match 可用于阻止錯誤的更新操作, 如果不匹配, 服務器將返回一個412(Precondition Failed)狀態碼的響應.

If-Unmodified-Since

緩存校驗字段, 語法同上. 表示資源未修改則正常執行更新, 否則返回412(Precondition Failed)狀態碼的響應. 常用于如下兩種場景:

  • 不安全的請求, 比如說使用post請求更新wiki文檔, 文檔未修改時才執行更新.

  • 與 If-Range 字段同時使用時, 可以用來保證新的片段請求來自一個未修改的文檔.

Etag VS. Last-Modified


Etag和Last-Modified都可以用于對資源進行驗證,而Last-Modified顧名思義,表示資源最后的更新時間。

我們把這兩者都成為驗證器(Validators),不同的是,Etag屬于強驗證(Strong Validation),因為它期望的是資源字節級別的一致;而Last-Modified屬于弱驗證(Weak Validation),只要資源的主要內容一致即可,允許例如頁底的廣告,頁腳不同。

根據RFC 2616標準中的13.3.4小節,一個使用HTTP 1.1標準的服務端應該(SHOULD)同時發送Etag和Last-Modified字段。同時一個支持HTTP 1.1的客戶端,比如瀏覽器,如果服務端有提供Etag的話,必須(MUST)首先對Etag進行Conditional Request(If-None-Match頭信息);如果兩者都有提供,那么應該(SHOULD)同時對兩者進行Conditional Request(If-Modified-Since頭信息)。如果服務端對兩者的驗證結果不一致,例如通過一個條件判斷資源發生了更改,而另一個判定資源沒有發生更改,則不允許返回304狀態。但話說回來,是否返回還是通過服務端編寫的實際代碼決定的。所以仍然有操縱的空間。

強緩存

一旦資源命中強緩存, 瀏覽器便不會向服務器發送請求, 而是直接讀取緩存. Chrome下的現象是 200 OK (from disk cache) 或者 200 OK (from            memory cache). 如下:

對于常規請求, 只要存在該資源的緩存, 且Cache-Control:max-age 或者expires沒有過期, 那么就能命中強緩存.

協商緩存

緩存過期后, 繼續請求該資源, 對于現代瀏覽器, 擁有如下兩種做法:

  • 根據上次響應中的ETag_value, 自動往request header中添加If-None-Match字段. 服務器收到請求后,   拿If-None-Match字段的值與資源的ETag值進行比較, 若相同, 則命中協商緩存, 返回304響應.

  • 根據上次響應中的Last-Modified_value, 自動往request header中添加If-Modified-Since字段. 服務器收到請求后, 拿If-Modified-Since字段的值與資源的Last-Modified值進行比較,  若相同, 則命中協商緩存, 返回304響應.

以上, ETag優先級比Last-Modified高, 同時存在時, 前者覆蓋后者. 下面通過實例來理解下強緩存和協商緩存.

如下忽略首次訪問, 第二次通過 If-Modified-Since 命中了304協商緩存.

協商緩存的響應結果, 不僅驗證了資源的有效性, 同時還更新了瀏覽器緩存. 主要更新內容如下:

Age:0
Cache-Control:max-age=600
Date: Wed, 05 Apr 2017 13:09:36 GMTExpires:Wed, 05 Apr 2017 00:55:35 GMT1234

Age:0 表示命中了代理服務器的緩存, age值為0表示代理服務器剛剛刷新了一次緩存.

Cache-Control:max-age=600 覆蓋 Expires 字段, 表示從Date_value, 即 Wed, 05 Apr 2017            13:09:36 GMT 起, 10分鐘之后緩存過期. 因此10分鐘之內訪問, 將會命中強緩存, 如下所示:

當然, 除了上述與緩存直接相關的字段外, http header中還包括如下間接相關的字段.

Pragma

http1.0字段, 通常設置為Pragma:no-cache, 作用同Cache-Control:no-cache.            當一個no-cache請求發送給一個不遵循HTTP/1.1的服務器時, 客戶端應該包含pragma指令. 為此, 勾選?? 上disable cache時, 瀏覽器自動帶上了pragma字段. 如下:

Age

出現此字段, 表示命中代理服務器的緩存. 它指的是代理服務器對于請求資源的已緩存時間, 單位為秒. 如下:

Age:2383321
Date:Wed, 08 Mar 2017 16:12:42 GMT12

以上指的是, 代理服務器在2017年3月8日16:12:42時向源服務器發起了對該資源的請求, 目前已緩存了該資源2383321秒


Date

指的是響應生成的時間. 請求經過代理服務器時, 返回的Date未必是最新的, 通常這個時候, 代理服務器將增加一個Age字段告知該資源已緩存了多久.


Vary

對于服務器而言, 資源文件可能不止一個版本, 比如說壓縮和未壓縮, 針對不同的客戶端, 通常需要返回不同的資源版本. 比如說老式的瀏覽器可能不支持解壓縮, 這個時候, 就需要返回一個未壓縮的版本; 對于新的瀏覽器,            支持壓縮, 返回一個壓縮的版本, 有利于節省帶寬, 提升體驗. 那么怎么區分這個版本呢, 這個時候就需要Vary了.

服務器通過指定Vary: Accept-Encoding, 告知代理服務器, 對于這個資源, 需要緩存兩個版本: 壓縮和未壓縮. 這樣老式瀏覽器和新的瀏覽器, 通過代理,            就分別拿到了未壓縮和壓縮版本的資源, 避免了都拿同一個資源的尷尬.

Vary:Accept-Encoding,User-Agent1

如上設置, 代理服務器將針對是否壓縮和瀏覽器類型兩個維度去緩存資源. 如此一來, 同一個url, 就能針對PC和Mobile返回不同的緩存內容。

怎么讓瀏覽器不緩存靜態資源

實際上, 工作中很多場景都需要避免瀏覽器緩存, 除了瀏覽器隱私模式, 請求時想要禁用緩存, 還可以設置請求頭: Cache-Control: no-cache, no-store, must-revalidate .

當然, 還有一種常用做法: 即給請求的資源增加一個版本號, 如下:

<link rel="stylesheet" type="text/css" href="../css/style.css?version=1.8.9"/>

這樣做的好處就是你可以自由控制什么時候加載最新的資源.

不僅如此, HTML也可以禁用緩存, 即在頁面的meta設置

<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate"/>

上述雖能禁用緩存, 但只有部分瀏覽器支持, 而且由于代理不解析HTML文檔, 故代理服務器也不支持這種方式.

IE8的異常表現

實際上, 上述緩存有關的規律, 并非所有瀏覽器都完全遵循. 比如說IE8.

資源緩存是否有效相關.

瀏覽器前提操作表現正常表現
IE8資源緩存有效新開一個窗口加載網頁重新發送請求(返回200)展示緩存的頁面
IE8資源緩存失效原瀏覽器窗口中單擊 Enter 按鈕展示緩存的頁面重新發送請求(返回200)

Last-Modified / E-Tag 相關.

瀏覽器前提操作表現正常表現
IE8資源內容沒有修改新開一個窗口加載網頁瀏覽器重新發送請求(返回200)重新發送請求(返回304)
IE8資源內容已修改原瀏覽器窗口中單擊 Enter 按鈕瀏覽器展示緩存的頁面重新發送請求(返回200)

服務器如何配置Etag和Expires

Apache、Lighttpd和Nginx中針配置Etag和Expires,有效緩存純靜態如css/js/pic/頁面/流媒體等文件。

A、Expires
A.1、Apache Etag
使用Apache的mod_expires 模塊來設置,這包括控制應答時的Expires頭內容和Cache-Control頭的max-age指令
ExpiresActive On
ExpiresByType image/gif "access plus 1 month"
ExpiresByType image/jpg "access plus 1 month"
ExpiresByType image/jpeg "access plus 1 month"
ExpiresByType image/x-icon "access plus 1 month"
ExpiresByType image/bmp "access plus 1 month"
ExpiresByType image/png "access plus 1 month"
ExpiresByType text/html "access plus 30 minutes"
ExpiresByType text/css  "access plus 30 minutes"
ExpiresByType text/txt  "access plus 30 minutes"
ExpiresByType text/js   "access plus 30 minutes"
ExpiresByType application/x-javascript   "access plus 30 minutes"
ExpiresByType application/x-shockwave-flash     "access plus 30 minutes"


當設置了expires后,會自動輸出Cache-Control 的max-age 信息
具體關于 Expires 詳細內容可以查看Apache官方文檔
在這個時間段里,該文件的請求都將直接通過緩存服務器獲取,
當然如果需要忽略瀏覽器的刷新請求(F5),緩存服務器squid還需要使用 refresh_pattern 選項來忽略該請求

efresh_pattern -i \.gif$ 1440 100% 28800 ignore-reload
refresh_pattern -i \.jpg$ 1440 100% 28800 ignore-reload
refresh_pattern -i \.jpeg$ 1440 100% 28800 ignore-reload
refresh_pattern -i \.png$ 1440 100% 28800 ignore-reload
refresh_pattern -i \.bmp$ 1440 100% 28800 ignore-reload
refresh_pattern -i \.htm$ 60 100% 100 ignore-reload
refresh_pattern -i \.html$ 1440 50% 28800 ignore-reload
refresh_pattern -i \.xml$ 1440 50% 28800 ignore-reload
refresh_pattern -i \.txt$ 1440 50% 28800 ignore-reload
refresh_pattern -i \.css$ 1440 50% 28800 reload-into-ims
refresh_pattern -i \.js$ 60 50% 100 reload-into-ims
refresh_pattern . 10 50% 60
有關Squid中Expires的說明,請參考Squid官方中refresh_pattern介紹。

A.2、Lighttpd Expires
和Apache一樣Lighttpd設置expire也要先查看是否支持了mod_expire模塊,
下面的設置是讓URI中所有images目錄下的文件1小時后過期;
expire.url = ( "/images/" => "access 1 hours" )
下面是讓作用于images目錄及其子目錄的文件;
$HTTP["url"] =~ "^/images/" {
expire.url = ( "" => "access 1 hours" )
}
也可以指定文件的類型;
$HTTP["url"] =~ "\.(jpg|gif|png|css|js)$" {
expire.url = ( "" => "access 1 hours" )
}
具體參考Lighttpd官方Expires解釋

A.3、Nginx中Expires
location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$
{
expires 30d;
}
location ~ .*\.(js|css)?$
{
expires 1h;
}
這類文件并不常修改,通過 expires 指令來控制其在瀏覽器的緩存,以減少不必要的請求。 expires 指令可以控制 HTTP 應答中的" Expires "和" Cache-Control "的頭標(起到控制頁面緩存的作用)。其他請參考Nginx中Expires

這類文件并不常修改,通過 expires 指令來控制其在瀏覽器的緩存,以減少不必要的請求。 expires 指令可以控制 HTTP 應答中的" Expires "和" Cache-Control "的頭標(起到控制頁面緩存的作用)。其他請參考Nginx中Expires

B.1、Apache中Etag設置
在Apache中設置Etag的支持比較簡單,只用在含有靜態文件的目錄中建立一個文件.htaccess, 里面加入:
FileETag MTime Size
這樣就行了,詳細的可以參考Apache的FileEtag文檔頁

B.2、Lighttpd Etag
在Lighttpd中設置Etag支持:
etag.use-inode: 是否使用inode作為Etag
etag.use-mtime: 是否使用文件修改時間作為Etag
etag.use-size: 是否使用文件大小作為Etag
static-file.etags: 是否啟用Etag的功能
第四個參數肯定是要enable的, 前面三個就看實際的需要來選吧,推薦使用修改時間

B.3、 Nginx Etag
Nginx中默認沒有添加對Etag標識.Igor Sysoev的觀點"在對靜態文件處理上看不出如何Etag好于Last-Modified標識。"
Note:
Yes, it's addition,and it's easy to add, however, I do not see howETag is better than Last-Modified for static files. -Igor Sysoev
A nice short description is here:
http://www.mnot.net/cache_docs/#WORK
It looks to me that it makes some caches out there to cache theresponse from the origin server more reliable as in rfc2616(ftp://ftp.rfc-editor.org/in-notes/rfc2616.txt) is written.
3.11 Entity Tags 13.3.2 Entity Tag Cache Validators 14.19 ETag
當然也有第三方nginx-static-etags 模塊了,請參考
http://mikewest.org/2008/11/generating-etags-for-static-content-using-nginx

三、對于非實時交互動態頁面中Epires和Etag處理
對數據更新并不頻繁、如tag分類歸檔等等,可以考慮對其cache。簡單點就是在非實時交互的動態程序中輸出expires和etag標識,讓其緩存。但需要注意關閉session,防止http response時http header包含session id標識;
3.1、Expires
如expires.php
<?php
header('Cache-Control: max-age=86400,must-revalidate');
header('Last-Modified: ' .gmdate('D, d M Y H:i:s') . ' GMT' );
header("Expires: " .gmdate ('D, d M Y H:i:s', time() + '86400′ ). ' GMT');
?>
以上信息表示該文件自請求后24小時后過期。
其他需要處理的動態頁面直接調用即可。

3.2、Etag
根據Http返回狀態來處理。當返回304直接從緩存中讀取
如etag.php
<?php
cache();
echo date("Y-m-d H:i:s");
function cache()
{
$etag = "http://www.jb51.net";
if ($_SERVER['HTTP_IF_NONE_MATCH'] == $etag)
{
header('Etag:'.$etag,true,304);
exit;
}
else header('Etag:'.$etag);
}
?> 

手機之家高春輝關于etag補充: 
幾個不同或者需要補充的地方: 
1、“當然,Etag 對多數站點性能的影響并不是很大。”應該說 Etag 在正確使用的情況下,會讓大量的請求以 304 頭方式響應,可以相當的節省服務器資源和帶寬。之前一些地方寫的不要使用 Etag,是基于有些 webserver 的 Etag 的計算方法中包含了 inode,這在多臺web服務器的情況不可采用的,而改變這個計算方法就可以了。 
2、對于盡早刷新這點,PHP 幾乎是做不到的。即使你執行了 flush 以及類似的函數,也要等到請求完全執行之后,才會輸出給瀏覽器端。 
3、AJAX 使用 GET 和 POST 各有好處,GET 方式可以更快響應,但是可能會有被瀏覽器緩存的問題,一般都需要加個隨機數來避免,POST 方式則不會。所以最好是根據自己的情況分別使用 GET 和 POST 方法。



本文改編自louis的《瀏覽器緩存機制剖析》

參考文章

轉載本站文章《瀏覽器緩存機制剖析》, 請注明出處:http://www.qsexmk.tw/html/webfront/SGML/web/2012_0215_8074.html