Cache-Control在請求頭和響應頭裏的區別

一、問題的由來

筆者曾在掘金上瀏覽技術文章,來學習http cache的知識。講到的最多的固然是強緩存和協商緩存。關於各個字段的含義,咱們能夠找到這張圖: 0c82d0049c3f4f57bf66d8effcb25ed5_tplv-k3u1fbpfcp-watermark.imagejavascript

多數文章會告訴咱們Response Header裏的Cache-Control各個取值,表明着不一樣的含義。但不多有文章說起到Request Header裏的Cache-Control。有一天發現:css

image.png

這是chrome刷新中,選擇硬性從新加載,瀏覽器請求一個靜態js文件的請求頭。當時並不知道和響應頭裏的Cache-Control有啥區別。曾一度認爲請求頭裏的Cache-Control是無效的。直到某一天,本身用nodejs實現了一次緩存設置。html

二、背景知識

瀏覽器處理http cache的優先級

這裏簡單歸納下順序前端

  1. 先判斷資源是否命中強緩存,命中則直接從disk裏拿到資源;
  2. 若是沒有命中強緩存,判斷是否命中協商緩存,命中則走協商緩存;
  3. 若是命中了協商緩存,會發起請求,服務端根據Request Header裏的If-None-Match(對應Etag)和If-Modified-Since(對應Last-Modified)判斷資源是否過時,沒過時則返回304狀態碼,瀏覽器依舊用disk裏的資源。若是資源過時,則服務端會返回新的資源;
  4. 若是也沒有命中協商緩存,則這個請求不走緩存策略,發起真實的請求,從服務端拿資源;

chrome的Network面板

一個典型的截圖以下:html5

1620714357(1).png

Initiator理解爲發起請求的對象,有如下值:java

  • Parser :請求是由頁面的html解析時發送,通常顯示html的名稱
  • Redirect:請求是由頁面重定向發送,暫時沒遇到過
  • script :請求是由script腳本處理髮送,通常顯示script腳本文件的名稱
  • Other :請求是由其餘過程發送的,好比頁面裏的Link連接點擊。通常請求結果是html內容

Size的值理解爲請求內容的大小,Time理解爲請求消耗的時間:node

  • 174B表示發起了實際的請求,這種請求通常時間不會是0;
  • memory cache表示是從內存中讀取的緩存資源,速度極快,通常都是0ms;
  • disk cache表示從磁盤中讀取的緩存資源,速度比memory cache慢,但比發起實際的請求要快;

須要注意的是,memory cache和disk cache雖然沒有發起實際的請求,但都會有200的狀態碼,以下圖:ajax

微信圖片_20210511145346.png

微信圖片_20210511145427.png

三、瀏覽器訪問頁面的操做

刷新按鈕的三個選項

右鍵點擊刷新按鈕會出現三個選項,如圖:chrome

image.png

訪問頁面的4種方式

地址欄回車、頁面連接跳轉、打開新窗口/標籤頁、history前進後退

這種方式會從強緩存開始判斷,是用戶瀏覽網頁中最經常使用的方式。json

點擊刷新按鈕、頁面右鍵從新加載、f五、ctrl+R

這種方式會跳過強緩存,直接從協商緩存開始判斷。

但須要注意的是Initiator值爲Other的內容纔會走協商緩存(一般只有一個,是html內容)。其餘的內容,由於是從html裏引入的(如script,link,img等),或者從script文件動態引入的。他們的Initiator一般是一個html文件,或者script文件,這些資源仍是會依照本身的規則,從強緩存開始判斷;

這種方式會在Request Header裏添加Cache-Control:max-age=0,這是瀏覽器本身的行爲

硬性從新加載、Ctrl+f五、Ctrl+Shift+R、勾選Disable cache後點刷新

這種方式,全部的資源(不論Initiator的值),都會跳過緩存判斷,發起真實的請求,從服務端拿資源。但本地的緩存資源(如disk裏的緩存)並無刪除。 這種方式會在Request Header裏添加Cache-Control:no-cache和Pragma: no-cache,也是瀏覽器本身的行爲

清空緩存並硬性從新加載

這種方式,至關於先刪除緩存(如disk裏的緩存),再執行硬性從新加載

四、不一樣之處

從訪問頁面的4種方式裏,能夠看出。在點刷新按鈕、或者硬性從新加載等時,由於瀏覽器本身的行爲,會在Request Header里加入對應的Cache-Control值。

須要弄清楚的是: 請求頭裏的Cache-Control影響的是當前這一次請求,響應頭裏的Cache-Control是告訴瀏覽器這樣存儲,下次依照這樣來。影響的是下一次請求。 通常以上邊的方式1(如url回車)訪問時,默認是按照上次給的規則來。但瀏覽器在真正的發起此次請求時,能夠有本身的行爲,好比硬性從新加載,不走緩存

Request Header裏Cache-Control的取值

Cache-Control:max-age=0

這個值表示,這個請求按照協商緩存的規則走,必定會發出真實的請求。這裏和響應頭裏的max-age=0有不一樣

Cache-Control:no-cache

這個值通常會附帶Pragma: no-cache,是爲了兼容http1.0。表示此次請求不會讀緩存資源,即使緩存沒有過時,或者資源並無修改,這樣的請求不會有返回304的可能。這一點和響應頭裏的Cache-Control:no-cache是有區別的。

Request Header裏Cache-Control只有這兩個值可取,設置其餘的值無效,好比設置Cache-Control:no-store是沒有用的,這一點要和響應頭裏的Cache-Control區分開。

Request Header裏的Cache-Control只有在瀏覽器刷新,硬性從新加載。這兩種瀏覽器本身的行爲中會被添加。 若是是一個常規的,設置了協商緩存(響應頭裏Cache-Control:no-cache),和不緩存(響應頭裏Cache-Control:no-cache)的請求,它在正常的,經過上文方式1訪問時,是不會在請求頭裏添加Cache-Control值的。

五、前端對緩存策略的一個應用

單頁面應用中,

  • 入口文件index.html設置爲協商緩存,每次都向服務器發起請求,肯定資源是否過時。
  • 其餘的資源,css,js這些都會設置成強緩存。由於這些文件名在打包以後會帶上hash值,若是修改了內容,那麼打包以後由於hash值變化,因此文件名也是會變化的。這些文件在index.html裏引入

六、如何設置請求頭裏的Cache-Control

在前邊的介紹裏,請求頭裏Cache-Control:max-age=0和Cache-Control:no-cache都是瀏覽器本身的行爲,由於加載這個靜態資源,如html、js、image等,用戶是沒法設置響應的Request Header的。

<meta>標籤

開發者可否本身設置html等靜態資源的請求頭呢,目前沒有發現這類方法。值得一提的是html裏的<meta>標籤,在舊版本里或許能夠設置響應的請求頭如:

<meta http-equiv="cache-control" content="max-age=180" />
<meta http-equiv="cache-control" content="no-cache" />
<meta http-equiv="expires" content="0" />
<meta http-equiv="expires" content="Tue, 01 Jan 1980 1:00:00 GMT" />
<meta http-equiv="pragma" content="no-cache" />
複製代碼

但這些方法在html5以後,是不會生效的。

setRequestHeader

有沒有能設置請求頭的請求呢,咱們會想到js發起的ajax請求,這個請求的Initiator通常是一個js文件。咱們能夠經過xhr.setRequestHeader()設置一些請求頭,好比Cache-Control的值。另外,瀏覽器對js設置請求頭的功能是有限制的,好比hostcookieuser-agent這些是沒法被js修改的。

通過筆者的測試

  • post這種請求,是沒法設置Cache-Control和Etag這些值的,在響應頭裏設置無效,相應的在請求頭設置Cache-Control也是沒啥用的,不會有緩存。
  • 只發現get請求能夠設置,能夠在請求頭裏設置Cache-Control:max-age=0和Cache-Control:no-cache,來實現對應的操做。get請求時能夠緩存的,只不過這樣的操做並不常見。

七、附上測試用的文件

node環境運行的http.js的內容:

const http = require('http'); //引入http模塊
const fs = require('fs'); //引入文件模塊
const TIME = [60, 120];//第一個時間是Etag改變的時間,第二個max-age過時時間
const MAP = new Map([
    [0, '123456789'],
    [1, '987654321'],
]); //生成兩個可選的Etag值

let FLAG = false; //狀態,是否修改
const timer = setTimeout(() => {
    FLAG = !FLAG; //必定時間後,修改狀態
}, TIME[0] * 1000);

const server = http.createServer((req, res) => {
    console.log(req.url);
    //瀏覽器默認還會請求/favicon.ico這個ico資源,因此須要對url作監聽限制
    if(['/', '/get1'].includes(req.url)) {
        res.setHeader('Cache-Control', `public, max-age=${TIME[1]}`);
        //設置reponse header的Cache-Control,失效時間
        console.log(req.headers['if-none-match'], '請求頭裏的Etag');
        const nowEtag = MAP.get(FLAG ? 1 : 0); //當前的Etag值

        if (req.headers['if-none-match'] === nowEtag) {
            console.log(123,867);
            //檢查request header裏的if-none-match和當前的Etag是否一致
            //簡單處理
            res.statusCode = 304;
            res.end();
        } else {
            res.setHeader('Etag', nowEtag); //設置協商緩存的Etag值
            if(req.url === '/'){
                const nowHtml = FLAG ? 'tempNew.html' : 'temp.html'; //當前應該渲染的頁面
                fs.createReadStream(nowHtml).pipe(res); //指定返回temp.html文件
            }else if(req.url === '/get1'){
                res.end(JSON.stringify({
                    "a": Date.now(),
                    "b": "qwer"
                }))
            }
        }
    }
})

server.listen(3333, '127.0.0.1', () => {
    console.log(`Server running at http://127.0.0.1:3333/`);
})
複製代碼

node中處理一個post請求,在這裏作一個備份,之後學習會用到

if (req.url === '/post1') {
    if (req.method == 'POST') {
        let postData = ''
        req.on('data', chunk => {
            postData += chunk.tostring()
        })
        req.on('end', () => {
            res.end(JSON.stringify({
                "a": Date.now(),
                "b": "qwer"
            }))
        })
    }
}
複製代碼

temp.html的內容:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <META HTTP-EQUIV="Pragma" CONTENT="no-cache">
        <meta http-equiv="cache-control" content="no-cache">
        <title>test page</title>
    </head>
    <body>
        <div>這是一個指定的首頁</div>
        <ul>
            <li><button id="btn">GET</button></li>
            <li><button id="btn1">GET no-cache</button></li>
            <li><button id="btn2">GET max-age=0</button></li>
            <li><button id="btn3">GET no-store</button></li>
        </ul>
        <script type="text/javascript">
            window.onload = function() {
                const GET = ty => {
                    const options = { method: 'GET', };
                    ty && (options['headers'] = new Headers({ 'Cache-Control': ty }));
                    return fetch('/get1', options);
                    //使用fetch主要是簡潔,XMLHttpRequest要寫太多代碼
                }

                document.addEventListener('click', function(e) {
                    if(e.target.id === 'btn'){
                        //no Cache-Control
                        GET()
                        .then(res => {
                            console.log(res.json());
                        })
                    }else if (e.target.id === 'btn1') {
                        //no-cache
                        GET('no-cache')
                        .then(res => {
                            console.log(res.json());
                        })
                    }else if(e.target.id === 'btn2') {
                        //max-age=0
                        GET('max-age=0')
                        .then(res => {
                            console.log(res.json());
                        })
                    }else if(e.target.id === 'btn3') {
                        //no-store
                        GET('no-store')
                        .then(res => {
                            console.log(res.json());
                        })
                    }
                })
            }
        </script>
    </body>
</html>

複製代碼

啓動服務後,能夠分別作下列兩種操做:

  • 點一次普通GET的btn,而後作一次硬性從新加載,再點一次普通GET的btn,
  • 點一次普通GET的btn,而後作一次清空緩存並硬性從新加載,再點一次普通GET的btn,

這兩組操做效果是不同的,可驗證這兩種刷新的區別。 另外,點no-store對應的GET的btn,效果是和普通的btn一致,由於在請求頭裏設置Cache-Control:no-store是無效的。請求頭裏的Cache-Control:no-cache已經能夠作到此次請求不走緩存了。

node.js方便了前端開發調試問題,在沒有後端的狀況下,進行mock數據,緩存設置等,確實很方便。

相關文章
相關標籤/搜索