快取頭Cache-Control的含義和使用

littlebirdflying發表於2018-09-22

Cache-Control 的含義

可快取性

public,http 請求返回的過程當中,在 cache-control 中設定這個值,代表 http 請求返回的內容所經過的任何路徑當中(包括中間一些http代理伺服器以及發出請求的客戶端瀏覽器),都可以對返回內容進行快取操作。

private,代表只有發起請求的瀏覽器才可以進行快取

no-cache,可以在本地進行快取,但每次發請求時,都要向伺服器進行驗證,如果伺服器允許,才能使用本地快取。

到期

max-age=,快取多少秒後過期,過期之後瀏覽器才會再次傳送請求。

s-maxage=,瀏覽器基本用不到,會代替 max-age,但只有在代理伺服器中才會生效。在代理伺服器中,如果都設定了 max-age,s-maxage,還是會讀取 s-maxage。

max-stale=,瀏覽器基本用不到,當 max-age 過期後,如果返回資源中有 max-stale 的設定。max-stale 是發起請求方主動攜帶的頭,即使 max-age 過期,只要 max-stale 沒過期,可以繼續使用快取資源,不需要重新請求。瀏覽器主動設定這個頭,只有在發起端才有用。

重新驗證

must-revalidate,瀏覽器可能會用到,如果 max-age 過期,需要重新傳送請求,獲取這部分資料,再來驗證資料是否真的過期,而不能直接使用本地快取。

proxy-revalidate,用在快取伺服器中,指定快取伺服器過期後,必須向源伺服器重新請求,不能直接使用本地快取。

其他

no-store,本地和代理伺服器都不可以儲存快取,每次都要重新請求,拿到內容。

no-transform,主要是用在 proxy 伺服器,不允許進行格式轉換。

Cache-Control 的使用

瀏覽器快取

通過 Cache-Control 以及 max-age 設定,達到長快取的效果。

啟動伺服器 node server.js,在 localhost:8888 開啟,檢視network,當設定 max-age 後,重新整理頁面,瀏覽器直接從快取中進行讀取,不去要再向伺服器請求,達到快取靜態資源的目的。

存在的問題,服務端修改返回內容,客戶端沒有載入新的內容,因為請求 url 沒變,瀏覽器會直接從快取讀取,不需要經過服務端驗證,導致靜態資源更新後,沒有及時更新到客戶端。

解決方案,打包靜態資源時,根據內容進行 hash 計算,生成檔名的 hash 碼。內容變,hash 碼變,請求資源 url 變,瀏覽器重新請求載入資源,達到更新快取的目的。

// server.js
const http = require('http')
const fs = require('fs')

http.createServer(function (request, response) {
  console.log('request come', request.url)

  if (request.url === '/') {
    const html = fs.readFileSync('test.html', 'utf8')
    response.writeHead(200, {
      'Content-Type': 'text/html'
    })
    response.end(html)
  }

  if (request.url === '/script.js') {
    response.writeHead(200, {
      'Content-Type': 'text/javascript',
      'Cache-Control': 'max-age=200' // 瀏覽器快取時間
    })
    response.end('console.log("script loaded twice")')
  }
}).listen(8888)

console.log('server listening on 8888')
複製程式碼
// test.html
<script src="/script.js"></script>
複製程式碼

max-age可以接收很多值,如 'Cache-Control': 'max-age=200, public'

快取驗證Last-Modified和Etag的使用

資源驗證

快取頭Cache-Control的含義和使用

驗證頭

Last-Modified

Etag

Last-Modified

上次修改時間。

配合If-Modified-Since或If-Unmodified-Since使用,通常瀏覽器使用前者。

伺服器對比上次修改時間以驗證資源是否需要更新。

Etag

資料簽名,資源內容會對應有一個唯一的簽名,如果資源資料更改,簽名也會變。

配合If-Match或者If-None-Match使用,其值就是服務端返回的 Etag 值

對比資源的簽名判斷是否使用快取

驗證頭的使用

伺服器設定 Last-Modifed 和 Etag 的值,瀏覽器請求會攜帶這兩個頭,在請求頭中,會有 If-Modified-since: Last-Modifed值 和 If-None-Match: Etag值。

這時 response 中是有內容的,這裡希望伺服器不返回實際的內容,只需要告訴瀏覽器直接讀取快取即可。通過在伺服器端進行判斷。

這時檢視 respones 發現還是有內容,這個內容是 Chrome 瀏覽器 從快取中讀取顯示出來的,伺服器沒有返回內容。

如何判斷服務端通過驗證,但是從快取讀取的呢,通過伺服器設定 HTTP Code 304,Not Modified 表示資源沒有修改,直接讀快取,這時就會忽略服務端返回的內容。

Chrome 瀏覽器 控制檯 勾上 Disable cache,重新整理頁面,傳送的請求中就不包括和快取相關的頭了

// server.js
const http = require('http')
const fs = require('fs')

http.createServer(function (request, response) {
  console.log('request come', request.url)

  if (request.url === '/') {
    const html = fs.readFileSync('test.html', 'utf8')
    response.writeHead(200, {
      'Content-Type': 'text/html'
    })
    response.end(html)
  }

  if (request.url === '/script.js') {
    console.log(request.headers)
    const etag = request.headers['if-none-match']
    if(etag === '777') {
      response.writeHead(304, {
        'Content-Type': 'text/javascript',
        'Cache-Control': 'max-age=2000000, no-cache',
        'Last-Modified': '123',
        'Etag': '777'
      })
      response.end('') // 這裡不傳任何內容,即使有內容,瀏覽器也不會讀取
    } else {
      response.writeHead(200, {
        'Content-Type': 'text/javascript',
        'Cache-Control': 'max-age=2000000, no-cache', // 通過 no-cache,即使沒過期瀏覽器也要向伺服器驗證,不會從快取讀取。
        'Last-Modified': '123', // 隨便設的值
        'Etag': '777'
      })
      response.end('console.log("script loaded twice")')
    } 
  }
}).listen(8888)

console.log('server listening on 8888')
複製程式碼
// test.html
<script src="/script.js"></script>
複製程式碼

no-cache

不從快取讀取

'Cache-Control': 'max-age=2000000, no-cache', // 通過 no-cache,即使沒過期瀏覽器也要向伺服器驗證,不會從快取讀取。
複製程式碼

no-store

設定 no-store,即使伺服器下發了快取相關頭,瀏覽器也會忽略任何和快取相關的資訊,傳送請求不會攜帶相關頭,直接去請求最新的資料。

Chrome瀏覽器->右上角->更多工具->清理瀏覽器快取

'Cache-Control': 'max-age=2000000, no-store'
複製程式碼

相關文章