瀏覽器緩存
緩存這東西,第一次必須獲取到資源后,然后根據返回的信息來告訴如何緩存資源,可能采用的是強緩存,也可能告訴客戶端瀏覽器是協商緩存,這都需要根據響應的header內容來決定的。下面用兩幅圖來描述瀏覽器的緩存是怎么玩的,讓大家有個大概的認知。
瀏覽器第一次請求時:
瀏覽器后續在進行請求時:
從上圖可以知道,瀏覽器緩存包含兩種類型,即強緩存(也叫本地緩存)和協商緩存,瀏覽器在第一次請求發生后,再次請求時:
瀏覽器在請求某一資源時,會先獲取該資源緩存的header信息,判斷是否命中強緩存(cache-control和expires信息),若命中直接從緩存中獲取資源信息,包括緩存header信息;本次請求根本就不會與服務器進行通信;在firebug下可以查看某個具有強緩存資源返回的信息,例如本地firebug查看的一個強緩存js文件
如果沒有命中強緩存,瀏覽器會發送請求到服務器,請求會攜帶第一次請求返回的有關緩存的header字段信息(Last-Modified/If-Modified-Since和Etag/If-None-Match),由服務器根據請求中的相關header信息來比對結果是否協商緩存命中;若命中,則服務器返回新的響應header信息更新緩存中的對應header信息,但是並不返回資源內容,它會告知瀏覽器可以直接從緩存獲取;否則返回最新的資源內容
強緩存與協商緩存的區別,可以用下表來進行描述:
獲取資源形式 狀態碼 發送請求到服務器
強緩存 從緩存取 200(from cache) 否,直接從緩存取
協商緩存 從緩存取 304(not modified) 是,正如其名,通過服務器來告知緩存是否可用
強緩存
強緩存上面已經介紹了,直接從緩存中獲取資源而不經過服務器;與強緩存相關的header字段有兩個:
expires:這是http1.0時的規范;它的值為一個絕對時間的GMT格式的時間字符串,如Mon, 10 Jun 2015 21:31:12 GMT,如果發送請求的時間在expires之前,那么本地緩存始終有效,否則就會發送請求到服務器來獲取資源
cache-control:max-age=number,這是http1.1時出現的header信息,主要是利用該字段的max-age值來進行判斷,它是一個相對值;資源第一次的請求時間和Cache-Control設定的有效期,計算出一個資源過期時間,再拿這個過期時間跟當前的請求時間比較,如果請求時間在過期時間之前,就能命中緩存,否則就不行;cache-control除了該字段外,還有下面幾個比較常用的設置值:
- no-cache:不使用本地緩存。需要使用緩存協商,先與服務器確認返回的響應是否被更改,如果之前的響應中存在ETag,那么請求的時候會與服務端驗證,如果資源未被更改,則可以避免重新下載。
- no-store:直接禁止游覽器緩存數據,每次用戶請求該資源,都會向服務器發送一個請求,每次都會下載完整的資源。
- public:可以被所有的用戶緩存,包括終端用戶和CDN等中間代理服務器。
- private:只能被終端用戶的瀏覽器緩存,不允許CDN等中繼緩存服務器對其緩存。
注意:如果cache-control與expires同時存在的話,cache-control的優先級高於expires
強緩存總結
1、cache-control: max-age=xxxx,public
客戶端和代理服務器都可以緩存該資源;
客戶端在xxx秒的有效期內,如果有請求該資源的需求的話就直接讀取緩存,statu code:200 ,如果用戶做了刷新操作,就向服務器發起http請求
2、cache-control: max-age=xxxx,private
只讓客戶端可以緩存該資源;代理服務器不緩存
客戶端在xxx秒內直接讀取緩存,statu code:200
3、cache-control: max-age=xxxx,immutable
客戶端在xxx秒的有效期內,如果有請求該資源的需求的話就直接讀取緩存,statu code:200 ,即使用戶做了刷新操作,也不向服務器發起http請求
4、cache-control: no-cache
跳過設置強緩存,但是不妨礙設置協商緩存;一般如果你做了強緩存,只有在強緩存失效了才走協商緩存的,設置了no-cache就不會走強緩存了,每次請求都回詢問服務端。
5、cache-control: no-store
不緩存,這個會讓客戶端、服務器都不緩存,也就沒有所謂的強緩存、協商緩存了。
協商緩存
協商緩存都是由服務器來確定緩存資源是否可用的,所以客戶端與服務器端要通過某種標識來進行通信,從而讓服務器判斷請求資源是否可以緩存訪問,這主要涉及到下面兩組header字段,這兩組搭檔都是成對出現的,即第一次請求的響應頭帶上某個字段(Last-Modified或者Etag),則后續請求則會帶上對應的請求字段(If-Modified-Since或者If-None-Match),若響應頭沒有Last-Modified或者Etag字段,則請求頭也不會有對應的字段。
Last-Modified/If-Modified-Since
二者的值都是GMT格式的時間字符串,具體過程:
瀏覽器第一次跟服務器請求一個資源,服務器在返回這個資源的同時,在respone的header加上Last-Modified的header,這個header表示這個資源在服務器上的最后修改時間
瀏覽器再次跟服務器請求這個資源時,在request的header上加上If-Modified-Since的header,這個header的值就是上一次請求時返回的Last-Modified的值
服務器再次收到資源請求時,根據瀏覽器傳過來If-Modified-Since和資源在服務器上的最后修改時間判斷資源是否有變化,如果沒有變化則返回304 Not Modified,但是不會返回資源內容;如果有變化,就正常返回資源內容。當服務器返回304 Not Modified的響應時,response header中不會再添加Last-Modified的header,因為既然資源沒有變化,那么Last-Modified也就不會改變,這是服務器返回304時的response header
瀏覽器收到304的響應后,就會從緩存中加載資源
如果協商緩存沒有命中,瀏覽器直接從服務器加載資源時,Last-Modified的Header在重新加載的時候會被更新,下次請求時,If-Modified-Since會啟用上次返回的Last-Modified值
Etag/If-None-Match
這兩個值是由服務器生成的每個資源的唯一標識字符串,只要資源有變化就這個值就會改變;其判斷過程與Last-Modified/If-Modified-Since類似,與Last-Modified不一樣的是,當服務器返回304 Not Modified的響應時,由於ETag重新生成過,response header中還會把這個ETag返回,即使這個ETag跟之前的沒有變化。
既生Last-Modified何生Etag
你可能會覺得使用Last-Modified已經足以讓瀏覽器知道本地的緩存副本是否足夠新,為什么還需要Etag呢?HTTP1.1中Etag的出現主要是為了解決幾個Last-Modified比較難解決的問題:
l 一些文件也許會周期性的更改,但是他的內容並不改變(僅僅改變的修改時間),這個時候我們並不希望客戶端認為這個文件被修改了,而重新GET;
l 某些文件修改非常頻繁,比如在秒以下的時間內進行修改,(比方說1s內修改了N次),If-Modified-Since能檢查到的粒度是s級的,這種修改無法判斷(或者說UNIX記錄MTIME只能精確到秒);
l 某些服務器不能精確的得到文件的最后修改時間。
這時,利用Etag能夠更加准確的控制緩存,因為Etag是服務器自動生成或者由開發者生成的對應資源在服務器端的唯一標識符。
Last-Modified與ETag是可以一起使用的,服務器會優先驗證ETag,一致的情況下,才會繼續比對Last-Modified,最后才決定是否返回304。
強緩存如何重新加載緩存緩存過的資源
上面說到,使用強緩存時,瀏覽器不會發送請求到服務端,根據設置的緩存時間瀏覽器一直從緩存中獲取資源,在這期間若資源產生了變化,瀏覽器就在緩存期內就一直得不到最新的資源,那么如何防止這種事情發生呢?
通過更新頁面中引用的資源路徑,讓瀏覽器主動放棄緩存,加載新資源。
類似下圖所示:
這樣每次文件改變后就會生成新的query值,這樣query值不同,也就是頁面引用的資源路徑不同了,之前緩存過的資源就被瀏覽器忽略了,因為資源請求的路徑變了。