Last-Modifed、Expires以及ETag、Cache-Control的動作流程與關係
在我們用伺服器端的動態頁面,去打出一些靜態的資料時,我們總希望不要讓伺服器那麼累,想要設法讓瀏覽器不要一直來跟伺服器端要一些不太可能會變動的資料,這時候我們就得需要瀏覽器的快取機制。瀏覽器快取機制(Browser Cache)自HTTP 1.0以來存在已久,但是許多前後端的程式設計師卻沒有對其有太大的重視,以我自己來說,最多也是實作到Last-Modifed而已,這也不是說不好,只是在某一些狀況下,還是要浪費一次的GET通訊,對於吞吐量大的網站來說,是一種效能上的負擔。
在說明前要先請大家了解,基本上Last-Modifed與Expires互成一對,ETag與Cache-Control互成一對。
Last-Modified動作
- Client端跟Server端要一個資源,Server回應HTTP 200 OK,並打下一個Last-Modified日期。
- 當客戶端再次要求同一個資源時,會先去檢查自己的Cache裡面有沒有相同的資料,有的話,會檢查是不是過期了(Expires or Cache-Control)。
- 沒有過期的話:取出自己的資源,停止對伺服器發送請求,流程結束。
- 如果已經過期,或者是根本就找不到與過期有關的標籤,那麼就對伺服器丟出If-Modified-Since標頭,標頭後面的日期當然是複製之前被伺服器端指定的日期了。
- 伺服器端收到If-Modified-Since的日期,就可以拿來判斷是否要重新丟或者拒絕丟新的資源,如果拒絕丟的話,就拋出HTTP 304 Not Modified,如果重新丟的話,就丟吧。
- Client端如果收到HTTP 304,就會乖乖的把Cache拿出來繼續用。
ETag動作
- Client端跟Server端要一個資源,Server回應HTTP 200 OK,並打下一個ETag雜湊。(雜湊的產生方式由程式設計師自己制定,這邊沒有太多的規範。)
- 當客戶端再次要求同一個資源時,會先去檢查自己的Cache裡面有沒有相同的資料,有的話,會檢查是不是過期了(Expires or Cache-Control)。
- 沒有過期的話:取出自己的資源,停止對伺服器發送請求,流程結束。
- 如果已經過期,或者是根本就找不到與過期有關的標籤,那麼就對伺服器丟出If-None-Match標頭,標頭後面的雜湊當然是複製之前被伺服器端指定的雜湊了。
- 伺服器端收到If-None-Match的雜湊,就可以拿來判斷是否要重新丟或者拒絕丟新的資源,如果拒絕丟的話,就拋出HTTP 304 Not Modified,如果重新丟的話,就丟吧。
- Client端如果收到HTTP 304,就會乖乖的把Cache拿出來繼續用。
由上面可以得知,Last-Modified、ETag這兩種機制的運作模式幾乎是一樣的。
再論Expires與Cache-Control
Expires是HTTP 1.0的產物,而Cache-Control是HTTP 1.1的產物,這兩個都是在描述資源的過期時間點,但是為何要創造出兩種的標頭呢?原因在於Expires是從伺服器端打下來的時間,這一點是比較有爭議的,尤其當Client端的時間進行比較大的變動時(例如出國換時區、電腦的時間跳掉),整個快取的機制就毀掉了。因此後來Cache-Control把機制改成計算制度(秒數),也就是說拿出Client端時間來相加此秒數,就等於是過期時間點。※註:事實上仔細一想,如果你的Client時間真的錯誤了,那麼用Cache-Control依然無法解決問題。
這邊會在導論出一個問題點,那就是ETag是以雜湊為基底的,在取得不到日期的情況下,如何拿來與Cache-Control的秒數來相加,並計算出過期時間呢?這一點我找了許多的文章,目前還沒有一定的解釋。我推測有可能是拿標頭的Date或Last-Modifed來運算,也有可能瀏覽器自己制定了一個計時的功能,或者是拿本地端的檔案存檔時間來算,這都有可能。所以我的作法選怎就算你打出ETag,還是會再幫忙打出一次Last-Modifed來進行輔助,以利計算過期時間。因此Google這篇使用瀏覽器快取功能的文章中,有關於「同時指定Last-Modified和ETag,都是多餘的行為。」這句話值得玩味,如果知道的網友也麻煩留言指教一下。
此外值得一提的是,在現代的瀏覽器中,如果你設定了Cache-Control而沒有設定Expires,則Cache-Control會蓋過Expires的設定。另外也要注意的是,RFC2616規範,Cache的設定不可以超過一年。
總結
如果你把這篇文章從頭看到尾,你就會發現使用Last-Modifed與Cache-Control這兩個標頭就好,其他都有如浮雲般的無意義,這個再次的證明,革新不代表一定就是好事。當然啦,也不能說ETag就沒有存在的意義,想想看,如果你的伺服器群共用某一個資源(例如你公司的Logo圖示),但是基於某些因素你沒有辦法讓這些伺服器群的時間同步,那麼選用ETag的機制來對此圖示進行雜湊,會是一個比較好的做法。
補充
在開發時期的環境下(localhost),有些瀏覽器會忽略Last-Modifed與Expires or Cache-Control的之間的關係,當每次運行頁面時盡管應該展現出Cache後的機制,但瀏覽器還是會強制送出Request到你的Localhost Server,這點請特別注意。
當你完成了上述的網頁快取機制時,可能會到瀏覽器的F12(DevTools)觀察,以Chrome瀏覽器來說,它會區分磁碟快取(Disk Cache)與記憶體快取(Memory Cache)兩種機制,這兩種機制的快取來源顧名思義就知道了,而出現的機制就是當瀏覽器全部關閉後第一次打開運行到你觀察的頁面,這時候如果所有的快取機制都是成立的,則瀏覽器會從磁碟拿出舊有的檔案,這時候稱為磁碟快取(Disk Cache),而當瀏覽器尚未關閉但卻又被你按下F5重新整理時,就會發現這時候的快取被洗成記憶體快取(Memory Cache),這就是Chrome瀏覽器為何會速度快(吃掉大量記憶體)的原因之一,畢竟他把所有的網頁資源全部往記憶體塞啊!