最近遇到了一個問題,咱們團隊開發了一個js庫,提供給各個業務方引入,採集相關業務數據,漸漸發現,每次更新js庫的代碼,從新發布,並清除dns緩存後,仍是有些業務方在請求舊的js庫的代碼,怎麼才能讓業務方拿到最新的sdk代碼呢?我陷入了困擾,每次從新發布修改js庫的文件名?這彷佛不太合理,接入的業務方那麼多,每次發佈一個個去通知他們更改引入的文件名?怕是會被業務方們扔雞蛋,且更改了文件名業務方也不會由於這個小改動從新發版,這顯然不靠譜.因而我研究了一下瀏覽器的靜態資源緩存的策略,原覺得nginx配置那裏修改相應頭的Etag字段,告訴瀏覽器我這個資源更新了,瀏覽器便會從服務器下載新的資源文件了.但後來通過一番折騰後,發現nginx只能保證ETag開啓和關閉,也就是詳情頭裏是否包含該字段,可是並不能保證資源更新後該ETag字段的更新,個人問題並無解決,可是花功夫仔細研究了一下Expires, Last-Modified, Etag的緩存機制.在此,我把我對瀏覽器的靜態資源緩存策略的相關內容作一個分享,但願能夠幫助遇到相似困擾的童鞋們~vue
在開始重點內容的分享以前,我想先分享一下關於請求頭和響應頭中的Cache-Control字段,這個字段在http請求中充當着緩存控制的角色,是控制緩存的開關,用於標識請求或訪問中是否開啓了緩存,使用了哪一種緩存方式.nginx
Cache-Control通用消息頭字段被用於在http請求和響應中經過指令來實現緩存機制,緩存指令是單向的,這意味着在請求設置的指令,在響應中不必定包含相同的指令.Cache-Control響應指令容許源服務器覆蓋一個響應默認的緩存功能.web
Cache-Control字段瀏覽器
在請求中使用Cache-Control,它可選的值有:緩存
字段名稱 | 說明 |
---|---|
no-cache | 告知(代理)服務器不直接使用緩存,要求向原服務器發起請求(這並不表明你每次均可以取到最新的資源) |
no-store | 全部內容都不會被保存到緩存或Internet臨時文件中 |
max-age=delta-seconds | 告知服務器但願接收一個存在時間(Age)不大於delta-seconds秒的資源 |
min-fresh=delta-seconds | 告知(代理)服務器客戶端但願接一個在小於delta-seconds秒內被更新過的資源 |
no-transform | 告知(代理)服務器客戶端但願獲取實體數據沒有被轉換(好比壓縮)過的資源 |
only-if-cached | 告知(代理)服務器客戶端但願獲取緩存的內容,而不用向原服務器發去請求 |
cache-extension | 自定義擴展值,若服務器不識別該值將被忽略掉 |
在響應中使用Cache-Control時,它可選的值有:服務器
字段名稱 | 說明 |
---|---|
public | 代表任何狀況下都得緩存該資源(即便是須要HTTP認證的資源) |
Private[="field-name"] | 代表返回報文中所有或部分(若指定了field-name則爲field-name的字段數據)僅開放給某些用戶(服務器指定的share-user,如代理服務器)作緩存使用,其餘用戶則不能緩存這些數據 |
no-cache | 不直接使用緩存,要求向服務器發起(新鮮度校驗)請求(這就到了Etag和Last-Modified起做用的時候了) |
no-store | 全部內容都不會被保存到緩存或Internet臨時文件中 |
no-transform | 告知客戶端緩存文件時不得對實體數據作出任何改變 |
only-if-cached | 告知(代理)服務器客戶端但願獲取緩存的內容(如有),而不用向原服務器發去請求 |
must-revalidate | 當前資源必定是向原服務器發去驗證請求的,若請求失敗會返回504(而非代理服務器上的緩存) |
proxy-revalidate | 與must-revalidate相似,但技能應用於共享緩存(如代理) |
max-age=delta-seconds | 告訴客戶端,該資源在delta-seconds秒內是新鮮的,無需向服務器發請求 |
s-maxage=delta-seconds | 同max-age,但僅應用於共享緩存(如代理) |
cache-extension | 自定義擴展值,若客戶端不識別該值將被忽略掉 |
須要注意的是,在Cache-Control中,這些值能夠自由組合,多個值衝突時,是有優先級的,no-store優先級最高.post
緩存校驗: 在緩存中,咱們須要一個機制來驗證緩存是否有效.好比服務器的資源更新了,客戶端須要及時刷新緩存,又或者客戶端的資源過了有效期,可是服務器上的資源仍是舊的,此時並不須要從新請求資源.緩存校驗就是用來解決這些問題的.url
服務器設置Expires字段爲一個日期,客戶端請求該資源時將這個日期與客戶端當前日期進行比對,若是當前時間小於這個日期,則表示資源未過時,使用緩存,若是當前時間大於這個日期,則表示資源已過時,客戶端就會從新請求該資源.可是這一策略會收到客戶端與服務器時間不一致的問題的影響.若是客戶端時間晚於服務器的時間,會致使資源還未過時就從新請求,反之,會致使客戶端還在使用過時的舊資源.代理
當瀏覽器第一次請求一個資源時,服務端返回狀態碼200,返回請求的資源的同時HTTP響應頭會有一個Last-Modified標記着文件在服務端最後被修改的時間.orm
瀏覽器第二次請求上次請求過的資源時,瀏覽器會在HTTP請求頭中添加一個If-Modified-Since的標記,用來詢問服務器該時間以後文件是否被修改過
若是服務器端的資源沒有變化,則自動返回304狀態,使用瀏覽器緩存,從而保證了瀏覽器不會重複從服務器端獲取資源,也保證了服務器有變化時,客戶端可以及時獲得最新的資源.
當瀏覽器第一次請求一個資源時,服務端返回的狀態碼爲200,同時HTTP相應頭會有一個Etag字段,存放着服務器端生成的一個序列值.
瀏覽器第二次請求上次請求過的url時,瀏覽器會在HTTP請求頭添加一個If-None-Match的標記,用來詢問服務器該文件有沒有被修改。
若是服務器的資源沒有變化,Etag字段沒有被修改依然與If-None-Match的值保持一致,則請求自動返回304狀態,使用瀏覽器緩存.若是不一致,則說明資源被更改,則從新去下載新的資源.
Etag主要爲了解決Last-Modified沒法解決的一些問題:
(1) 一些文件也許週期性的更改,可是它的內容並不改變(僅僅改變的是修改時間),這個時候咱們不但願客戶端認爲這個文件被修改了,而從新獲取資源.
(2) 某些文件修改很是頻繁,好比在秒一下的時間內進行修改(好比1s內修改了N次),If-Modified-Since能檢查到的粒度是秒級的,這種修改是沒法判斷的(或者說UNIX記錄MTIME只能精確到秒);
(3) 某些服務器不能精確的獲得文件的最後修改時間;
nginx配置裏ETag選項默認開啓的,因此請求的資源文件若發生改動,會在響應頭裏生成新的ETag值.這樣客戶端就可以發現If-None-Match的值和Etag字段的值不匹配,從而去請求最新的資源文件.
靜態資源修改之後.對應的文件最後更改時間和ETag字段有時彷佛都不會作相應的更改,瀏覽器緩存該靜態資源文件,致使在文件更改後不能及時更新.是否是靜態資源的緩存只能依靠修改文件名的策略來拿到最新的資源? 那麼Expires, Last-Modified, Etag這三個字段只能在後臺代碼裏才能修改嗎?各路英雄可還有更好的解決方案?望各路英雄不吝賜教~