瀏覽器快取步驟
1)瀏覽器在載入資源時,先根據這個資源的一些http header判斷它是否命中強快取,強快取如果命中,瀏覽器直接從自己的快取中讀取資源,不會發請求到伺服器。比如某個css檔案,如果瀏覽器在載入它所在的網頁時,這個css檔案的快取配置命中了強快取,瀏覽器就直接從快取中載入這個css,連請求都不會傳送到網頁所在伺服器;
2)當強快取沒有命中的時候,瀏覽器一定會傳送一個請求到伺服器,通過伺服器端依據資源的另外一些http header驗證這個資源是否命中協商快取,如果協商快取命中,伺服器會將這個請求返回,但是不會返回這個資源的資料,而是告訴客戶端可以直接從快取中載入這個資源,於是瀏覽器就又會從自己的快取中去載入這個資源;
3)強快取與協商快取的共同點是:如果命中,都是從客戶端快取中載入資源,而不是從伺服器載入資源資料;區別是:強快取不發請求到伺服器,協商快取會發請求到伺服器。
4)當協商快取也沒有命中的時候,瀏覽器直接從伺服器載入資源資料。
例項: 以常見的請求一個CSS樣式來說。
第一次請求
通常伺服器會傳送這4個欄位過來, 可能是4個都要,也可能一個欄位也沒有。這裡主要講解4個欄位都存在的情況。
第二次請求
前端:首先,瀏覽器會檢查Cache-Control與Expires,有Cache-Control的情況下,以其為標準,如果超時,則向後端傳送請求,請求中會帶上 If-Modified-Since,If-None-Match。
後臺:後端伺服器接收到請求之後,會對這兩個欄位進行對比,同樣以If-None-Match為標準,沒有If-None-Match的情況下,比對If-Modified-Since,如果比對後發現檔案沒有過期,即Etag沒有發生變化,或者Last-Modified與If-Modified-Since一致(只存在If-Modified-Since時)。如果改變了,就會傳送新的檔案,反之,則直接返回304。
瀏覽器快取分類
強快取
客戶端第一次問伺服器要某個資源時,伺服器丟還給客戶端所請求的這個資源同時,告訴客戶端將這個資源儲存在本地,並且在未來的某個時點之前如果還需要這個資源,直接從本地獲取就行了,不用向伺服器請求。這種方式快取下來的資源稱為強快取。 強快取利用http的返回頭部的中Expires(實體首部欄位)或者Cache-Control(通用首部欄位)兩個欄位來控制的,用來表示資源的快取時間。伺服器通過這兩個首部欄位告知客戶端資源的快取過期時間和快取最大生命週期。客戶端得知資源的快取過期時間和最大生命週期後,即可自行判斷是否可不建立與伺服器的連結,直接從瀏覽器快取中獲取資源。
命中強快取時,瀏覽器同樣會受到status=200的response,chrome中可通過size區分從伺服器返回的資源還是強快取獲得的資源。
Expires
該欄位是http1.0時的規範,值為一個絕對時間的GMT格式的時間字串,代表快取資源的過期時間,在這個時點之前,即命中快取。其過程如下:
瀏覽器第一次跟伺服器請求一個資源時,伺服器在返回這個資源時,在相應頭部會加上Expires,如圖: clipboard.png
瀏覽器接收到該資源後,會把這個資源連同response header一起快取下來;
瀏覽器再次請求這個資源時,會先從快取中找到這個資源,然後獲取Expires時間與當前的請求時間比較,如果Expires時間比當前瀏覽器的請求時間晚,說明快取未過期,即命中快取;
如果當前請求時間比Expires晚,說明快取過期,即未命中快取,瀏覽器就會傳送請求到伺服器申請獲取資源。
缺點:伺服器返回的Expires時點是伺服器上的時間,可能與客戶端有時間差,時間差太大時可能造成快取混亂。
Cache-Control:max-age
Cache-Control有很多屬性,不同的屬性代表的意義也不同。
private:客戶端可以快取
public:客戶端和代理伺服器都可以快取
max-age=t:快取內容將在t秒後失效
no-cache:需要使用協商快取來驗證快取資料
no-store:所有內容都不會快取。
該欄位是http1.1的規範,強快取利用其max-age值來判斷快取資源的最大生命週期,它的值單位為秒,Cache-Control : max-age=3600代表快取資源有效時間為1小時,即從第一次獲取該資源起一小時內的請求都被認為可命中強快取。其過程如下:
瀏覽器第一次跟伺服器請求一個資源時,伺服器在返回這個資源時,在相應頭部會加上Cache-Control:max-age=XXXXXXXX,如圖: clipboard.png
瀏覽器接收到資源後,連同response header一起快取下來;
瀏覽器再次跟伺服器請求同一個資源時,會先從快取中找到這個資源,獲取date(第一次資源返回的響應時間)和max-age並計算出一個有效期(date + max-age),然後再和瀏覽器請求時間比較最後判斷是否命中快取(邏輯同Expires);
如果沒有命中快取,瀏覽器直接從伺服器請求資源,Cache-Control會在重新獲取到伺服器返回資源時更新。
Cache-Control描述的是相對時間,採用本地時間來計算資源的有效期,所以相比Expires更可靠。
這兩個Header可以只用其一,也可以一起使用。一起使用時以Cache-Control為準。
協商快取
客戶端第一次問伺服器要某個資源時,伺服器丟還給客戶端所請求的這個資源同時,將該資源的一些資訊(檔案摘要、或者最後修改時間)也返回給客戶端,告訴客戶端將這個資源快取在本地。當客戶端下一次需要這個資源時,將請求以及相關資訊(檔案摘要、或者最後修改時間)一併傳送給伺服器,由伺服器來判斷客戶端快取的資源是否需要更新:如不需要更新,就直接告訴客戶端獲取本地快取資源;如需要更新,則將最新的資源連同相應的資訊一併返回給客戶端。 當強快取未命中時,瀏覽器就會傳送請求到伺服器,伺服器會驗證協商快取是否命中,如果協商快取命中,請求返回的http狀態為304,並會顯示說明Not Modified,瀏覽器收到該返回後,就會從快取中載入了。
協商快取利用[Last-Modified , If-Modified-Since] 和 [ETag , If-None-Match]這兩對Header來管理。
Last-Modified & If-Modified-Since
Last-Modified為實體首部欄位,值為資源最後更新時間,隨伺服器response返回。
If-Modified-Since為請求首部欄位,通過比較兩個時間來判斷資源在兩次請求期間是否有過修改,如果沒有修改,則命中協商快取,瀏覽器從快取中獲取資源;如果有過修改,則伺服器返回資源,同時返回新的Last-Modified時間。其過程如下:
-
瀏覽器第一次請求伺服器資源,且伺服器返回了該資源時,會在response headers中加上Last-Modified,這個header表示這個資源在伺服器上的最後一次修改時間;
-
當瀏覽器再次請求該資源時,會在request headers中加上If-Modified-Since,這個值即為上一次伺服器返回的Last-Modified時間;
-
伺服器再次收到資源請求時,將If-Modified-Since時間和資源在伺服器上的最後修改時間與對比,如果If-Modifid-Since與最後修改時間一致,則命中快取,伺服器返回304,瀏覽器從快取中獲取資源;若未命中快取,伺服器返回資源同時,瀏覽器快取資源的Last-Modified會被更新。
ETag & If-None-Match
有些情況下僅判斷最後修改日期來驗證資源是否有改動是不夠的:
存在週期性重寫某些資源,但資源實際包含的內容並無變化;
被修改的資訊並不重要,如註釋等;
Last-Modified無法精確到毫秒,但有些資源更新頻率有時會小於一秒。
為解決這些問題,http允許使用者對資源打上標籤(ETag)來區分兩個相同路徑獲取的資源內容是否一致。通常會採用MD5等密碼雜湊函式對資源編碼得到標籤(強驗證器);或者通過版本號等方式,如W/”v1.0”(W/表示弱驗證器)。
ETag為相應頭部欄位,表示資源內容的唯一標識,隨伺服器response返回;
If-None-Match為請求頭部欄位,伺服器通過比較請求頭部的If-None-Match與當前資源的ETag是否一致來判斷資源是否在兩次請求之間有過修改,如果沒有修改,則命中協商快取,瀏覽器從快取中獲取資源;如果有過修改,則伺服器返回資源,同時返回新的ETag。其過程如下:
-
伺服器第一次收到瀏覽器發出的資源請求時,會在response headers中加上ETag,這個ETag是根據該資源生成的唯一標識,這個唯一標識是個字串,只要伺服器認為資源有變化且應該提供新的資源,則ETag就必須有變化。瀏覽器將資源連同ETag一併快取。
-
當瀏覽器再一次向伺服器傳送該資源的請求時,會在request headers中加上If-None-Match,該值即為第一次伺服器返回的ETag值;
-
伺服器收到資源請求後,會根據要請求的資源重新計算生成相應的ETag,然後與If-None-Match比較。對比結果一致即命中快取,不一致則未命中快取,返回資源同時將新的ETag傳送至瀏覽器。
協商快取管理 [Last-Modified , If-Modified-Since]和[ETag , If-None-Match]一般同時啟用,這是為了處理Last-Modified不可靠的情況。
前端部署方案
之前大家可能都知道 一般的公司對於靜態資源以及快取的處理方式無非就這麼幾種。 1 在靜態資源後面加一個版本號 v=1.111
類似於上面這種方式。2 為了準確的確定檔案是否修改,將後面的版本號修改為檔案摘要(主要根據檔案內容生成的一個值)。
類似於上面這種,後面的紅框表示的部分就是根據檔案的摘要生成的key.3 直接將資原始檔名使用檔案摘要或者說某個固定的字串加上一個檔案摘要拼接成一個檔名。
類似上面這種方式,最後面紅圈內表示的程式碼是根據檔案摘要來生成的,這裡需要區別和第二種方式,第二種方式是拿來放在url後面作為一個引數,但檔名沒有改變。而這裡直接選擇修改了檔名。(彩蛋:有意思的,找了幾個TX的網站,發現其實並不是所有的網站都採用了最後一種方式。我想應該技術都是用來追求完美的,但實現還是人實現的,畢竟人的天性是喜歡偷懶的。)
那麼問題來了? 以上三種方式的區別是什麼?為什麼最後會最終演變為第三種方式?
1 第一種方式,需要維護版本號,如果在一個檔案中,存在多個資源,那麼沒有被修改過的資原始檔也會被修改版本號,導致不必要的資源載入。(當然,如果需要加上時間戳之類的,就已經不屬於第一個的範圍了)
2 第二種方式,可以精確的發現哪一個檔案被修改過。從而要求客戶端進行重新載入。但是同樣會存在一些問題。 一般能做到第二種方式的公司,網頁流量自然可以想像(小公司請自動忽略)。 那麼當在釋出版本的時候,會存在兩個型別的檔案需要釋出: 1) html檔案,上面有資原始檔的引用 2 )資原始檔
那麼釋出以上兩個檔案的順序就成問題了。
如果先發 html檔案: 那麼會導致重新載入資源,但一樣還是無法訪問到最新的特性。(畢竟資原始檔還沒有真正的更新。),如是Html頁面的結構有更新,但載入了舊的資源,很有可能導致頁面結構的錯亂。並且會快取資源,直到資源過期,否則除非強制重新整理,會一直是錯誤頁面。(這裡要注意到,由於第一次載入了舊的資源,版本號又是新的版本號,所以即使在這之後上了資源,這裡依舊會讀取舊的資源.)
如果先發資原始檔: 如果之前訪問過頁面,那就會有儲存有本地快取,那麼由於訪問的還是快取檔案,不會出現問題。但如果是新使用者,那麼就會訪問到新的資原始檔,很有可能導致頁面錯亂。而等到頁面html也釋出之後,頁面又恢復了正常。
PS: 當然有的人可能會說,釋出就那麼一會的時間,有必要那麼在乎這些一點點時間麼? 如果你這麼想,那麼我只能說,我無話可說。
發上兩種都是屬於覆蓋式資源釋出,不管如何處理,都會存在這樣的問題。那麼解決方案就是第三種。非覆蓋式釋出。
3 第三種方式,應該是最完美的解決方案: 1 首先發資原始檔,由於檔名已經不一樣了,所以不會覆蓋掉之前存在的資原始檔,客戶端依舊可以安全的訪問。 2 再發客戶端檔案,在客戶端檔案一旦釋出成功,那麼就會立馬切成新的特性,中間可以做到無縫銜接。 這就是所謂的非覆蓋釋出的方案。