1. 程式人生 > 實用技巧 >http快取

http快取

http快取

http快取分為強快取和協商快取

強快取:在訪問某個資源時,瀏覽器中有它的快取副本,且該副本有效,那麼瀏覽器可以直接從快取中獲取資源,不用在向伺服器發起請求

協商快取:在訪問某個資源時,瀏覽器中有它的快取副本,但是該快取已經失效,那麼瀏覽器就需要向伺服器發起http請求,詢問快取是否可以繼續使用。若該資源沒有變化快取可以用,伺服器就返回304狀態碼告知瀏覽器;若資源發生了變化,則會返回新的資源,狀態碼為200

強快取相關欄位

1.expires

  該欄位是一個響應欄位,它的值是一個時間戳,代表在此之前快取是有效的,過了這個時間之後快取便會失效。但是若伺服器的時間和本地的時間有差別,或者修改了本地時間,有可能會導致快取失效

2.cache-control

  由於expires的不足,在HTTP1.1中定義了cache-control欄位,優先順序高於expires。這是一個通用欄位,在請求或響應中都能使用。
  請求指令:
    1)no-cache 強制向伺服器再次驗證
    2)no-store 不快取
    3)max-age 響應的最大值
  響應指令:
    1)no-cache 快取,但必須向伺服器確認其新鮮度
    2)no-store 不快取
    3)max-age 響應的最大值,快取的有效時長
    4)private 只有客戶端可快取響應
    5.public 任意方都可快取響應

具體內容可見《圖解HTTP》



協商快取相關欄位

1.Last-Modified

  響應欄位,值代表最後修改時間。瀏覽器第一次向伺服器請求資源時,伺服器會在響應頭中攜帶該欄位,代表資源最後的修改時間

2.If-Modified-Since

  請求欄位,當快取失效後,瀏覽器再次請求該資源時,會向伺服器傳送一次請求,檢查快取的新鮮度。請求頭中會攜帶該欄位,值為Last-Modified的值。與伺服器資源的Last-Modified進行比較。若不相等則代表資源更新了,返回新的資源;若相等則代表資源沒有更新,返回304狀態碼,告訴瀏覽器快取可以用。

  不過該欄位存在缺點,若修改服務端資源的過程中,時長不超過1秒,則Last-Modified的值不會發生變化,也就不會檢測到資源的更新

3.ETag

  響應欄位,瀏覽器第一次向伺服器請求資源時,伺服器會在響應頭中攜帶該欄位,它的值是伺服器根據要請求的資源用演算法生成的,資源發生變化時其值也跟著發生變化

4.If-None-Match

  請求欄位,同樣當快取失效後,瀏覽器會在請求頭中攜帶該欄位,值為ETag的值,與伺服器資源的ETag進行比較。若不相等則代表資源更新了,返回新的資源;若相等則代表資源沒有更新,返回304狀態碼,告訴瀏覽器快取可以用。該欄位的優先順序高於Last-Modified

簡單模擬實現強快取和協商快取

新建server.js,用node開啟一個伺服器,監聽3000埠

var http = require('http')
var fs = require('fs')

var server = http.createServer()

server.on('request', function (req, res) {
 // 設定cors跨域 res.setHeader(
'Access-Control-Allow-Origin', '*')
 // 設定expires,代表這個時間點之後快取會失效 res.setHeader(
'expires', 'Wed, 07 Oct 2020 05:25:44 GMT')
 // 設定Last-Modified,檔案最後修改時間(這裡只是簡單的模擬,所以給賦予一個固定的值) res.setHeader(
'Last-Modified', 'Wed, 07 Oct 2020 04:59:26 GMT')
 if (req.headers['if-modified-since'] === 'Wed, 07 Oct 2020 04:59:26 GMT') { res.statusCode = 304 res.end() } else { if (req.url === '/a.html') { fs.readFile('./a.html', 'utf-8', function (err, data) { res.end(data) }) } } }) server.listen(3000, function () { console.log('server is open') })

在同一目錄下新建a.html

<div>aaaaa</div>


新建index.html,點選按鈕觸發ajax請求,訪問伺服器中的a.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <button onclick="fn()">aa</button>

  <script>
    function fn () {
      var xhr = new XMLHttpRequest()
      xhr.open('get', 'http://localhost:3000/a.html')
      xhr.onreadystatechange = function () {
        if (xhr.readyState === 4 && xhr.status === 200) {
          console.log(xhr.responseText)
        }
      }
      xhr.send()
    }

  </script>
</body>
</html>


開啟伺服器後,在本地開啟index.html,然後點選按鈕發起請求


可以看到在第二次訪問中,expires設定的時間大於Date,說明快取還沒有過期,所以直接訪問了快取中的資源



第三次的訪問中,快取已經失效,瀏覽器向伺服器發起的請求中攜帶了If-Modified-Since欄位來檢驗快取的新鮮度。由於這裡只是簡單的模擬,服務端程式碼中固定了Last-Modified的值,並返回304狀態碼

在server.js中加上Cache-Control和ETag

var http = require('http')
var fs = require('fs')

var server = http.createServer()

server.on('request', function (req, res) {
  res.setHeader('Access-Control-Allow-Origin', '*')
  res.setHeader('expires', 'Wed, 07 Oct 2020 06:22:44 GMT')
  res.setHeader('Last-Modified', 'Wed, 07 Oct 2020 04:59:26 GMT')
  res.setHeader('ETag', '123456abcdef')
  res.setHeader('Cache-Control', 'max-age=10')  
  if (req.headers['if-none-match'] === '123456abcdef') {
    res.statusCode = 304
    res.end()
  } else {
    if (req.url === '/a.html') {
      fs.readFile('./a.html', 'utf-8', function (err, data) {
        res.end(data)
      })
    }
  }
})

server.listen(3000, function () {
  console.log('server is open')
})


重啟伺服器(發起請求前先清除瀏覽器快取)



Cache-Control的優先順序大於expires,Cache-Control中設定max-age為10,由於第二次訪問時10秒還沒有過去,所以瀏覽器訪問了快取中的資源


第三次訪問時,10秒已經過去,快取失效。瀏覽器向伺服器發起的請求中攜帶了If-Modified-Since欄位和If-None-Match欄位來檢驗快取的新鮮度,ETag的優先順序大於Last-Modified。同樣因為是簡單模擬,所以ETag的值是隨便賦值的字串,然後服務端返回304狀態碼