爲何要寫這篇文章?
故事背景發生在 2019 年的某一天,國內某大型開源 CDN 網站服務器奔潰以後。公司的 H5 產品,引用了他們的 CDN 資源,當天沒法訪問,後來咱們緊急發佈了熱修復,用阿里雲 CDN 資源替換了線上的連接地址。css
發佈熱補丁以後,咱們指望的結果固然是用戶從新打開微信公衆號訪問 H5 就能正常加載最新的資源,正常訪問。html
可是,最後的問題是,iOS 系統的微信公衆號可以正常訪問,安卓卻不行,安卓加載的仍是緩存中的靜態資源文件。前端
這裏面固然是由於微信安卓的瀏覽器內核對緩存作了特殊的處理(坑),可是怎麼從根本上解決緩存問題?nginx
關於 HTTP 緩存的文章網上不少,因此我這裏主要作簡單總結和問題探討。算法
問題:跨域
先給結論:瀏覽器
緩存是一種保存資源副本並在下次請求時直接使用該副本的技術緩存
瀏覽器緩存通常只有 GET 請求才有效。性能優化
緩存的種類有不少,其大體可歸爲兩類:私有與共享緩存。共享緩存存儲的響應可以被多個用戶使用。私有緩存只能用於單獨用戶。bash
"public" 指令表示該響應能夠被任何中間人(譯者注:好比中間代理、CDN等)緩存,隱含的意思是,其餘用戶可能也能分享這個資源的緩存
private 是指資源應該被緩存,可是隻能被客戶端的瀏覽器緩存.
下面是和 HTTP 緩存有關的 HTTP 消息頭:
HTTP Header | 出如今 | 應用在 |
---|---|---|
Cache-Control | 請求、響應 | 強制和協商緩存 |
Expires | 響應 | 強制緩存 |
Last-Modified | 響應 | 協商緩存 |
If-Modified-Since | 請求 | 協商緩存 |
ETag | 響應 | 協商緩存 |
If-None-Match | 請求 | 協商緩存 |
文件的緩存位置,目前廣泛使用的有三種,其中 cacheStorage 是在 ServiceWorker 中應用中產生的。
緩存位置 | 資源 |
---|---|
from ServiceWorker(cacheStorage) | 經過 ServiceWorker 註冊安裝 |
from memory cache | 腳本、字體、圖片 |
from disk cache | 非腳本:css、svg |
緩存放在內存仍是硬盤中,也會和文件大小有關係。
強制緩存是經過 Expires 和 Cache Control 的 max-age 來判斷緩存是否過時的策略,此時不會向服務器發起請求。
若是緩存沒有過時,將會直接從瀏覽器緩存中獲取資源。
Expires 的值一般是一個絕對時間,存在的問題當是客戶端和服務器時間不一致或者被修改的狀況下就會失效。而 max-age 的值是相對時間,根據瀏覽器緩存根據服務器返回的 Date 和 Max-Age 判斷緩存是否過時。
打個比方:max-age 至關於說「保質期六個月」,而 Expires 是說「在此日期以前」飲用。
max-age 和 Expires 設置的緩存過時時間最多爲一年(365天),若是多於這個值則瀏覽器有可能會忽略
當 Expires 和 Max-Age 同時存在時,會忽略 Expires 指令。
做爲請求首部時,cache-directive 的可選值
字段名稱 | 說明 |
---|---|
no-cache | 告知(代理)服務器不直接使用緩存,要求向原服務器發起請求 |
no-store | 全部內容都不會被保存到緩存或 Internet 臨時文件中,直接下載文件 |
max-age=delta-seconds | 告知服務器客戶端但願接收一個存在時間(Age)不大於 delta-seconds 秒的資源, 爲 0 表示直接進行協商緩存 |
max-stale[=delta-seconds] | 告知(代理)服務器端願意接收一個超過緩存時間的資源,若定義delta-seconds則爲delta-seconds秒,若沒有則爲任意超出時間 |
min-fresh=delta-seconds | 全部內容都不會被保存到緩存或 Internet臨時文件中 |
no-transform | 告知(代理)服務器客戶端但願獲取實體數據沒有被轉換(好比壓縮)過的資源 |
only-if-cached | 告知(代理)服務器客戶端但願獲取緩存的內容(如有), 而不用向原服務器發去請求 |
cache-extension | 自定義擴展值,若服務器不識別該值將被忽略掉 |
Cache-Control 在響應中的字段:
字段名稱 | 說明 |
---|---|
public | 指示響應能夠被任何緩存所緩存,即便一般它只是非可緩存或可緩存到一個非共享緩存內 |
private | 指示響應信息的所有或者部分用於單個用戶,而不能用一個共享緩存來緩存 |
no-cache | 能夠緩存,可是隻有在跟 WEB 服務器驗證了其有效後,才能返回給客戶端(直接進行協商緩存) |
no-store | 全部內容不會被保存到緩存或 Internet 臨時文件中,指令的目的是防止無意發佈或是保留了敏感信息(例如,備份) |
no-transform | 告知客戶端緩存文件時不得對實體數據作任何改變。它對__轉換某個實體體的媒體類型__頗有用,例如,一個非透明的代理把圖像轉換格式,以節省緩存空間,或是減小慢速連接中的通訊量 |
only-if-cached | 在某些狀況下,如網絡鏈接很是差時,客戶端可能須要一個緩存,只返回目前已存儲的那些響應,而不是從新加載,或與源服務器從新驗證。要作到這一點,客戶端能夠在一個請求中包含該指令。 |
must-revalidate | 當前資源必定是向原服務器發去驗證請求的,若請求失敗會返回__504__(而非代理服務器上的緩存) |
proxy-revalidate | 與 must-revalidate相似,可是僅能應用於共享緩存(如代理) |
max-age=delta-seconds | 告知客戶端該資源在delta-seconds秒內是新鮮的,除非還包含 max-stale 指令,不然客戶端不指望接收一個陳舊的響應。 |
s-maxage=delta-seconds | 同 max-age,但僅應用於共享緩存(如代理) |
cache-extension | 自定義擴展值,若服務器不識別該值將被忽略 |
Last-Modified 和 If-Modified-Since 是用最後修改日期時間判斷一個緩存的資源是否有效。
Last-Modified 存在的問題多是,將打包的前端資源文件夾進行覆蓋式部署時,部分文件內容並無發生變化,可是修改時間卻被更新了,此時瀏覽器會下載資源,形成了沒必要要的時延和帶寬消耗。
Etag 和 If-None-Match 則是用內容摘要做爲斷定的依據。內容摘要是指根據一個資源的內容產生一串比較短的數字,當內容變化時,產生的數字串也會改變。內容摘要的算法有不少種,較常見的是SHA-1哈希算法、CRC32等。
Etag 的運行流程和 Last-Modified 同樣:
Etag 的優先級會比 Last-Modified 更高, Etag 就是爲了解決 Last-Modified 文件更新時間變化,但文件內容沒變的問題,另外 Etag 的缺點在於會佔用比 Last-Modified 更高的服務器 CPU 消耗。
由以上的知識,咱們瞭解到:
強制緩存返回的 HTTP 響應狀態碼是 200,而協商緩存返回的響應狀態碼是 304。
大多數狀況是這樣沒錯,可是在部分瀏覽器中(好比谷歌),協商緩存也是會返回 200 的,這是瀏覽器的算法使然,瀏覽器判斷,越長時間沒有更新的文件,會直接從瀏覽器緩存中獲取資源,此時狀態碼是 200.
下面是網上常見強制緩存和協商緩存的流程圖,我也畫了一個:
這個問題主要在產品部署發佈迭代版本的時候會遇到。
若是服務器沒有配置 Last-Modified 和 etag, 當強制緩存尚未失效的時候,如何更新文件版本呢?
目前廣泛的作法有兩種,不緩存 index.html,在 js,css,字體,圖片的連接地址後拼接版本參數:好比
或者將資源文件的內容 HASH 短碼添加在文件名中:
微信安卓版內置瀏覽器符合通常瀏覽器的緩存策略。可是 iOS 內置瀏覽器會將 index.html 文件強制緩存,儘管你在 index.html 文件的 head 標籤上添加了以下代碼:
<meta http-equiv="Cache-Control" content="no-cache, no-store" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="expires" content="0" />
複製代碼
上面 Pragma 是 HTTP1.0 的消息頭,基本沒用,僅有 IE 才能識別這段 meta 標籤含義。 解決方案就是從服務器層面對 index.html 文件進行緩存控制,好比 Nginx:
location / {
root /var/www/;
index index.html index.htm;
try_files $uri $uri/ /index.html;
#### kill cache
add_header Last-Modified $date_gmt;
add_header Cache-Control 'no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0';
if_modified_since off;
expires off;
etag off;
}
location ~* ^.+\.(jpg|jpeg|gif|png|ico|css|js)$ {
root /mnt/dat1/test/tes-app;
access_log off;
expires 30d;
}
複製代碼
大公司通常採用 index.html 和 js、css、字體、圖片資源利用 CDN 分開部署,不在同一臺服務器上,因此只須要對 index.html 設置不緩存便可。
順便提一下,若是是字體文件部署在 CDN 上,會存在跨域問題(CSS、JS、圖片不會受到同源政策的限制),開啓 CORS 便可:
access-control-allow-origin: *
複製代碼
瀏覽器緩存是前端性能優化中關鍵的一步,利用 GET 請求緩存文件。
瀏覽器緩存分爲強制緩存和協商緩存,強制緩存中 Cache-Control 比 Expires 的優先級更高。協商緩存中,If-None-Match 比 If-Modified-Since 的優先級更高。
強制緩存返回狀態碼 200,協商緩存狀態 304。
前端打包應該利用打包工具對文件名添加 hash 短碼,如需校驗身份信息應該利用協商緩存和私有緩存。
參考連接:
@Starbucks 2019/04/13