# http緩存機制
http緩存機制的主要手段有兩種:
* 緩存存儲:狀態碼為200 from cache/from store cache/from distory cache
* 緩存對比:狀態碼為304 Not Modified
## 解釋
* 200 OK (from cache) 是瀏覽器沒有跟服務器確認,直接用了瀏覽器緩存
* 304 Not Modified 是瀏覽器和服務器多確認了一次緩存有效性,再用的緩存。
它們都是在設置了緩存的情況下觸發的。
為什么有的緩存是 200 OK (from cache),有的緩存卻是 304 Not Modified 呢?
這是因為設置不同,請求方式不同,導致結果不同。
## 兩者觸發的時機有什么區別呢?
200 OK (from cache)
* 直接點擊鏈接訪問
* 輸入網址按回車訪問
* 掃描二維碼
304 Not Modified
* F5刷新頁面時觸發(注意:Ctrl+F5強制刷新是200 OK不是304哦)
* 設置了長緩存、但 Entity Tags 沒有移除時觸發
# 機制一:緩存對比(304 Not Modified)
## 重要屬性
* Last-Modified http1.0時期屬性 現在仍在使用
* ETag(Entity Tag) http1.1時期新加屬性 ,使用inode+mtime(以下有解釋)來計算。根據實體內容生成的一段hash字符串(類似于MD5或者SHA1之后的結果),可以標識資源的狀態。 當資源發送改變時,ETag也隨之發生變化。
>名詞解釋:
>* inode :包含文件的元信息,包括以下內容
>>文件的字節數、文件擁有者的User ID、文件的Group ID
>>文件的讀、寫、執行權限
>>文件的時間戳,共有三個:ctime指inode上一次變動的時間;mtime指文件內容上一次變動的時間;atime指文件上一次打開的時間。
>>鏈接數,即有多少文件名指向這個inode、 文件數據block的位置
>* mtime:指文件內容上一次變動的時間
## ETag
### 為什么http1.1又新推出ETag?
原因是`Last-Modified`存在以下的缺點:
* 某些服務器不能精確得到文件的最后修改時間, 這樣就無法通過最后修改時間來判斷文件是否更新了。
* 某些文件的修改非常頻繁,在秒以下的時間內進行修改. Last-Modified只能精確到秒。
* 一些文件的最后修改時間改變了,但是內容并未改變。 我們不希望客戶端認為這個文件修改了。
### ETag有哪些問題?
ETag也有它自己的問題,所以經常在使用中會關閉ETag。舉例來說,同一個文件在不同物理機上的inode是不同的,這就導致了在分布式的Web系統中,當訪問落在不同的物理機上時會返回不同的ETag,進而導致304失效,降級為200請求。解決辦法也有從ETag算法中剝離inode,只是使用mtime,但是這樣實際上和Last-Modified就一樣了。當然你也可以額外的做一些改進,使ETag對靜態資源的算法也是通過hash計算的。不過由于一般我們會使用CDN技術,因此集群部署中的ETag問題并不會造成太大的影響,所以折騰的人也不太是很多。


## 辨別條件請求
在客戶端向服務端發送http請求時,若返回狀態碼為304 Not Modified 則表明此次請求為條件請求。這就表明了客戶端中所請求資源的緩存仍然是有效的,也就是說該資源從上次緩存到現在并沒有被修改過。條件請求可以在確保客戶端的資源是最新的同時避免因每次都請求完整資源給服務器帶來的性能問題。
在請求頭中有兩個請求參數:If-Modified-Since 和 If-None-Match
當客戶端緩存了目標資源但不確定該緩存資源是否是最新版本的時候, 就會發送一個條件請求。在進行條件請求時,客戶端會提供給服務器一個If-Modified-Since請求頭,其值為服務器上次返回響應頭中Last-Modified值,還會提供一個If-None-Match請求頭,值為服務器上次返回的ETag響應頭的值。
服務器會讀取到這兩個請求頭中的值,判斷出客戶端緩存的資源是否是最新的,如果是的話,服務器就會返回HTTP/304 Not Modified響應頭, 但沒有響應體.客戶端收到304響應后,就會從本地緩存中讀取對應的資源。 所以:當訪問資源出現304訪問的情況下其實就是先在本地緩存了訪問的資源。
另一種情況是,如果服務器認為客戶端緩存的資源已經過期了,那么服務器就會返回HTTP/200 OK響應,響應體就是該資源當前最新的內容.客戶端收到200響應后,就會用新的響應體覆蓋掉舊的緩存資源.只有在客戶端緩存了對應資源且該資源的響應頭中包含了Last-Modified或ETag的情況下,才可能發送條件請求.如果這兩個頭都不存在,則必須無條件(unconditionally)請求該資源,服務器也就必須返回完整的資源數據。
## 產生304的流程
1. 瀏覽器首次請求服務器
2. 服務器返回狀態碼200,同時在響應頭中添加:`Last-Modified(或ETag)`,瀏覽器獲取到這兩個請求頭后將其緩存下來
3. 瀏覽器再次請求服務器,就會將緩存的值放到請求頭中:`Last-Modified`的值放在`If-Modified-Since`請求頭中;`ETag`的值放在`If-None-Match`請求頭中
4. 服務器根據上面的兩個請求頭判斷資源是否有修改,有修改返回200,未修改返回304
## 為什么要使用條件請求
當用戶訪問一個網頁時,條件請求可以加速網頁的打開時間(因為可以省去傳輸整個響應體的時間),但仍然會有網絡延遲,因為瀏覽器還是得為每個資源生成一條條件請求,并且等到服務器返回HTTP/304響應,才能讀取緩存來顯示網頁.更理想的情況是,服務器在響應上指定Cache-Control或Expires指令,這樣客戶端就能知道該資源的可用時間為多長,也就能跳過條件請求的步驟,直接使用緩存中的資源了.可是,即使服務器提供了這些信息,在下列情況下仍然需要使用條件請求:
? 在超過服務器指定的過期時間之后
? 如果用戶執行了刷新操作的話
在上節給出的圖片中,請求頭中包含了一個Pragma: no-cache.這是由于用戶使用F5刷新了網頁.如果用戶按下了CTRL-F5 (有時稱之為“強刷-hard refresh”),你會發現瀏覽器省略了If-Modified-Since和If-None-Match請求頭,也就是無條件的請求頁面中的每個資源.
## 避免條件請求
通常來說,緩存是個好東西.如果你想提高自己網站的訪問速度,緩存是必須要考慮的.可是在調試的時候,有時候需要阻止緩存,這樣才能確保你所訪問到的資源是最新的.
你也許會有個疑問:“如果不改變網站內容,我怎么才能讓瀏覽器不返回304而返回一個包含響應體的HTTP/200響應呢?”
你可以在瀏覽器控制臺設置取消緩存即可。以Chrome為例:

如果選中了 Disable cache,則請求資源時,請求頭中的Cache-Control為no-cache,表明不使用緩存,則會直接獲取服務器資源。另外,若沒選中Disable cache,Cache-Control有二種情況:
1. `max-age > 0` 時直接從游覽器緩存中提取?
2. `max-age<= 0` 時向服務器發送http請求,該資源是否有修改有的話返回200 ,無的話返回304.
# 機制二:緩存存儲(200 from cache)
## 重要屬性
* Pragma : no-cache http1.0時期的屬性 為了兼容會使用
* Expires:0 http1.0時期的屬性
* Cache-Control http1.1 中加入的新屬性,它有以下常用參
* Public/Private 私有緩存/共有緩存
* no-cache:不建議使用本地緩存,但仍然會緩存到本地
* no-store:不會在客戶端緩存任何數據
* max-age:比較特殊,是一個混合屬性,替代了Expires的過期時間
舉個栗子:如果要設置客戶端不緩存,并兼容http1.0的方式可以這樣寫:
```
Pragma : no-cache
Expires:0
Cache-Control:no-store
```
等價于
```
Pragma : no-cache // Pragma為了兼容http1.0
Cache-Control:max-age=0 // 去掉了Expires屬性(下面名詞解釋會說到為什么被去掉),合并到max-age中
```
> 名詞解釋:
**私有緩存**:《HTTP權威指南》里面講到了私有緩存的一種就是在瀏覽器里面輸入 about:cache 可以查看自己瀏覽器緩存的內容,會給出一個顯示了緩存內容“磁盤緩存統計”頁面,這個可以看看還挺有意思,能展示不少信息
**Expires**:過時期限值,GMT格式,是Web服務器響應消息頭字段,在響應http請求時告訴瀏覽器在過期時間前瀏覽器可以直接從瀏覽器緩存取數據,而無需再次請求。不過Expires 是HTTP 1.0的東西,現在默認瀏覽器均默認使用HTTP 1.1,所以它的作用基本忽略。Expires 的一個缺點就是,返回的到期時間是服務器端的時間,這樣存在一個問題,如果客戶端的時間與服務器的時間相差很大(比如時鐘不同步,或者跨時區),那么誤差就很大,所以在HTTP 1.1版開始,使用Cache-Control: max-age=秒替代。
# 200 vs 304

## 緩存命中速度
緩存命中 > 緩存再驗證成功 > 緩存未命中 = 緩存再驗證失敗
## 緩存命中優先級
Cache-Control http1.1 > Expires > Pragma http1.0來決定是否 (200 from cache)
## 緩存再驗證成功
根據Last-Modified http1.0 和 ETaghttp1.1 來驗證是否返回 (304 Not Modified) 兩者都有,就必須同時驗證,并且兩者都滿足才會返回304;