[實戰驗證] http快取(無代理伺服器)

小p發表於2019-01-24

一. 背景

最近看了不少關於客戶端快取機制的文章,大概弄明白了整個原理,但是對於中間一些細節一直有點迷糊,下面的內容是自己做的驗證


二. 準備工作

http快取的基本情況可以看看下這些文章

前端之瀏覽器快取,一次搞定

前端也要懂Http快取機制

常見的影響快取的配置有以下幾個:

  1. 原伺服器的Expires
  2. 原伺服器的Cache-Control
  3. 代理伺服器(nginx)Expires
  4. 代理伺服器(nginx)proxy_cache_valid
  5. 代理伺服器(nginx)proxy_cache_path中的inactive

當前這篇會先驗證無代理伺服器的情況,有代理的情況放在之後驗證

配置說明

原伺服器的Cache-Control

名稱 說明
private 客戶端可以快取,代理伺服器不可以快取
public 客戶端和代理伺服器都可以快取
max-age=t 快取內容將在t秒後失效
no-cache 使用協商緩
no-store 不使用快取

原伺服器的Expires

這是http 1.0的屬性,現在應該用的少了;該屬性設定的是一個過期時間,過期時間內命中強快取;過期時間外,協商快取


三. 驗證

以下是我準備驗證的問題:

  1. 原伺服器的Cache-Control不同屬性的實際情況
  2. 原伺服器的Expires的實際情況
  3. 不使用代理伺服器的情況下,原伺服器的ExpiresCache-Control同時存在,那個優先順序高

[1-24 更新] 增加一個驗證:

  1. 如果上述頭都未設定,快取情況是怎樣的?

1. 首先是無代理伺服器的情況

驗證方式是使用node的express啟動一個服務

const fs = require('fs');
const path = require('path');
const express = require('express');
const app = express();
const port = 3030;

app.use(express.static(path.resolve(__dirname, './')));

app.get('/index', (req, res) => {
  const html = fs.readFileSync(path.resolve(__dirname, './index.html'), 'utf-8');
  res.send(html)
})

// cache驗證
app.all('*', (req, res, next) => {
  // res.header('Cache-Control', 'private');
  // res.header('Cache-Control', 'public');
  res.header('Cache-Control', 'max-age=20');
  // res.header('Cache-Control', 'no-cache');
  // res.header('Cache-Control', 'no-store');
  next();
})

const questions = [
  {
    id: '000',
    name: 'Rose'
  },
  {
    id: '111',
    name: 'Jack'
  }
]

app.get('/api/data', (req, res) => {
  res.status(200);
  res.json(questions);
})

// 監聽埠
app.listen(port, () => {
  console.log(`success listen at ${port}`);
})

複製程式碼

1.1 Cache-Control的各屬性驗證

ps:因為不存在代理伺服器,所以public和private的區別現在是看不出來的,我們之後和有代理的情況一起驗證

// 單獨驗證Cache-Control
app.all('*', (req, res, next) => {
  res.header('Cache-Control', 'max-age=30');
  // res.header('Cache-Control', 'no-cache');
  // res.header('Cache-Control', 'no-store');
  next();
})
複製程式碼

以下驗證可以得出結論:

  1. max-age未過期 -> 命中強快取
  2. max-age過期 -> 資源未修改 -> 命中協商快取
  3. max-age過期 -> 資源已修改 -> 伺服器獲取資源
  4. no-cache -> 資源未修改 -> 命中協商快取
  5. no-cache -> 資源已修改 -> 伺服器獲取資源
  6. no-store -> 伺服器獲取資源
Cache-Control 請求延遲時間 資源是否改變 If-None-Match(request) ETag(respanse) Status Code 結論
max-age=30 10s W/"37-W4vHvR7+YshQyC9DoyT14egVUqo" W/"37-W4vHvR7+YshQyC9DoyT14egVUqo" 200 OK (from disk cache) 強快取未過期,命中強快取
max-age=30 60s W/"37-W4vHvR7+YshQyC9DoyT14egVUqo" W/"3a-LT60UfEg/Jmv4cmkNOAvZSUh6Qo" 200 OK 強快取過期,資源被修改,重新從伺服器獲取資源
max-age=30 10s W/"37-W4vHvR7+YshQyC9DoyT14egVUqo" W/"37-W4vHvR7+YshQyC9DoyT14egVUqo" 200 OK (from disk cache) 強快取未過期,命中強快取
max-age=30 60s W/"37-W4vHvR7+YshQyC9DoyT14egVUqo" W/"37-W4vHvR7+YshQyC9DoyT14egVUqo" 304 Not Modified 強快取過期,資源未修改,命中協商快取
no-cache 30s W/"37-W4vHvR7+YshQyC9DoyT14egVUqo" W/"3a-LT60UfEg/Jmv4cmkNOAvZSUh6Qo" 200 OK 資源被修改,未命中協商快取,從伺服器獲取資源
no-cache 30s W/"37-W4vHvR7+YshQyC9DoyT14egVUqo" W/"37-W4vHvR7+YshQyC9DoyT14egVUqo" 304 Not Modified 資源未修改,命中協商快取
no-store 30s W/"3a-LT60UfEg/Jmv4cmkNOAvZSUh6Qo" 200 OK 不使用快取,直接從服務端獲取資源
no-store 30s W/"37-W4vHvR7+YshQyC9DoyT14egVUqo" 200 OK 不使用快取,直接從服務端獲取資源

1.2 Expires驗證

ps1:如果這個Expires欄位後端沒有處理過的話,返回的應該是GMT的標準時間Wed, 23 Jan 2019 09:52:55 GMT,也就是格林威治的標準時間;而我們一般使用的是本地時間Wed Jan 23 2019 18:06:15 GMT+0800 (中國標準時間),所以要做一下適當的處理

ps2:我是在chrome上用localhost上測試的,但不知道為什麼設定Expires後,不管過沒過期,不管有沒有同時設定Cache-ControlStatus Code狀態一直是304的,聽說好像是因為用了localhost的關係,這個與線上並不一定完全一致;這裡關於Expires的測試我是加了nginx代理了之後的結果,不過代理的快取沒有設定

ps3:Expires的結果就僅供參考吧,以上

const moment = require('moment');
// cache驗證
app.all('*', (req, res, next) => {
  res.header('Expires', getGLNZ());
  next();
})
// 轉換格林威治時間
function getGLNZ() {
  return moment().utc().add(30, 's').format('ddd, DD MMM YYYY HH:mm:ss') + ' GMT';
}
複製程式碼

以下驗證可以得出結論:

  1. Expires未過期 -> 命中強快取
  2. Expires過期 -> 資源未修改 -> 命中協商快取
  3. Expires過期 -> 資源已修改 -> 伺服器獲取資源
Expires 請求延遲時間 資源是否改變 If-None-Match(request) ETag(respanse) Status Code 結論
當前時間+30s 10s W/"37-W4vHvR7+YshQyC9DoyT14egVUqo" 200 OK (from disk cache) 強快取未過期,命中強快取
當前時間+30s 60s W/"37-W4vHvR7+YshQyC9DoyT14egVUqo" W/"37-W4vHvR7+YshQyC9DoyT14egVUqo" 304 Not Modified 強快取過期,資源未修改,命中協商快取
當前時間+30s 10s W/"3a-LT60UfEg/Jmv4cmkNOAvZSUh6Qo" 200 OK (from disk cache) 強快取未過期,命中強快取
當前時間+30s 60s W/"37-W4vHvR7+YshQyC9DoyT14egVUqo" W/"3a-LT60UfEg/Jmv4cmkNOAvZSUh6Qo" 200 OK 強快取過期,資源被修改,重新從伺服器獲取資源

1.3 Expires與Cache-Control優先順序

結論:Cache-Control優先順序比Expires優先順序高

const moment = require('moment');
// cache驗證
app.all('*', (req, res, next) => {
  res.header('Cache-Control', 'max-age=60');
  // res.header('Cache-Control', 'no-cache');
  // res.header('Cache-Control', 'no-store');
  res.header('Expires', getGLNZ());
  next();
})

function getGLNZ(){
  return moment().utc().add(30, 's').format('ddd, DD MMM YYYY HH:mm:ss') + ' GMT';
}
複製程式碼

以下驗證可以看出,在Cache-ControlExpires同時設定的情況下,Expires是失效的

Expires Cache-Control 請求延遲時間 資源是否改變 If-None-Match(request) ETag(respanse) Status Code 結論
當前時間+30s max-age=60 10s W/"37-W4vHvR7+YshQyC9DoyT14egVUqo" 200 OK (from disk cache) Expires與Cache-Control都未過期,命中強快取
當前時間+30s max-age=60 40s W/"37-W4vHvR7+YshQyC9DoyT14egVUqo" 200 OK (from disk cache) Expires過期,Cache-Control未過期,命中強快取
當前時間+30s max-age=60 100s W/"37-W4vHvR7+YshQyC9DoyT14egVUqo" W/"37-W4vHvR7+YshQyC9DoyT14egVUqo" 304 Not Modified Expires與Cache-Control都過期,資源未修改,命中協商快取
當前時間+30s no-cache 10s W/"37-W4vHvR7+YshQyC9DoyT14egVUqo" W/"37-W4vHvR7+YshQyC9DoyT14egVUqo" 304 Not Modified Expires未過期,但是命中協商快取
當前時間+30s no-cache 60s W/"37-W4vHvR7+YshQyC9DoyT14egVUqo" W/"37-W4vHvR7+YshQyC9DoyT14egVUqo" 304 Not Modified Expires過期,命中協商快取
當前時間+30s no-store 10s W/"37-W4vHvR7+YshQyC9DoyT14egVUqo" 200 OK Expires未過期,直接獲取服務端資源
當前時間+30s no-store 60s W/"37-W4vHvR7+YshQyC9DoyT14egVUqo" 200 OK Expires過期,直接獲取服務端資源

1.4 Expires與Cache-Control都未設定

以下驗證結論:

  1. If-None-MatchETag相同 -> 協商快取
  2. If-None-MatchETag不相同 -> 伺服器獲取資源
資源是否改變 If-None-Match(request) ETag(respanse) Status Code 結論
W/"3a-LT60UfEg/Jmv4cmkNOAvZSUh6Qo" W/"37-W4vHvR7+YshQyC9DoyT14egVUqo" 200 OK 伺服器獲取資源
W/"37-W4vHvR7+YshQyC9DoyT14egVUqo" W/"37-W4vHvR7+YshQyC9DoyT14egVUqo" 304 Not Modified 命中協商快取

四. 總結:

1. 只設定Cache-Control

  • max-age未過期 -> 命中強快取
  • max-age過期 -> 資源未修改 -> 命中協商快取
  • max-age過期 -> 資源已修改 -> 伺服器獲取資源
  • no-cache -> 資源未修改 -> 命中協商快取
  • no-cache -> 資源已修改 -> 伺服器獲取資源
  • no-store -> 伺服器獲取資源

2. 只設定Expires

  • Expires未過期 -> 命中強快取
  • Expires過期 -> 資源未修改 -> 命中協商快取
  • Expires過期 -> 資源已修改 -> 伺服器獲取資源

3. Cache-ControlExpires同時存在

  • 只有Cache-Control生效

4. Cache-ControlExpires都未設定

  • 對比request中的If-None-Matchresponse中的ETag
  • If-None-MatchETag相同 -> 協商快取
  • If-None-MatchETag不相同 -> 伺服器獲取資源
  • 感覺實際上就是走的Cache-Control no-cahce

以上,驗證了無代理伺服器的情況下,http快取常用的兩個配置的結果;下一篇驗證加了代理伺服器的情況

相關文章