http緩存

前言

http的緩存是老生常談的問題,基本面試專用的,看到的文章挺多的,但都是一些原理性的文章,基本沒有真正實踐過怎么緩存,就會形成一種道理大家都懂,但誰真正實踐過呢,從而到用的時候,卻發(fā)現不會怎么做緩存,如何配置呢,也一無所知。所以本文從理論和實踐上試了一下怎么認識緩存。這里有借鑒前輩們的經驗文章,但對于生產環(huán)境上服務器的緩存還是懵懂的,請各位大佬賜教,同時歡迎各位大佬指正。

緩存的規(guī)則

我們知道HTTP的緩存屬于客戶端緩存,后面會提到為什么屬于客戶端緩存。所以我們認為瀏覽器存在一個緩存數據庫,用于儲存一些不經常變化的靜態(tài)文件(圖片、css、js等)。我們將緩存分為強制緩存和協商緩存。下面我將分別詳細的介紹這兩種緩存的緩存規(guī)則。

強制緩存

當緩存數據庫中已有所請求的數據時。客戶端直接從緩存數據庫中獲取數據。當緩存數據庫中沒有所請求的數據時,客戶端的才會從服務端獲取數據。

協商緩存

又稱對比緩存,客戶端會先從緩存數據庫中獲取到一個緩存數據的標識,得到標識后請求服務端驗證是否失效(新鮮),如果沒有失效服務端會返回304,此時客戶端直接從緩存中獲取所請求的數據,如果標識失效,服務端會返回更新后的數據。

小貼士:

我們可以看到兩類緩存規(guī)則的不同,強制緩存如果生效,不需要再和服務器發(fā)生交互,而對比緩存不管是否生效,都需要與服務端發(fā)生交互。
兩類緩存規(guī)則可以同時存在,強制緩存優(yōu)先級高于對比緩存,也就是說,當執(zhí)行強制緩存的規(guī)則時,如果緩存生效,直接使用緩存,不再執(zhí)行對比緩存規(guī)則。

緩存的方案

上面的內容讓我們大概了解了緩存機制是怎樣運行的,但是,服務器是如何判斷緩存是否失效呢?我們知道瀏覽器和服務器進行交互的時候會發(fā)送一些請求數據和響應數據,我們稱之為HTTP報文。報文中包含首部header和主體部分body。與緩存相關的規(guī)則信息就包含在header中。boby中的內容是HTTP請求真正要傳輸的部分。舉個HTTP報文header部分的例子如下:

HTTP/1.1 200 OK
Server: nginx/1.14.0
Date: Sat, 09 Mar 2019 02:59:02 GMT
Content-Type: application/javascript; charset=utf8
Content-Length: 1544092
Last-Modified: Sat, 09 Mar 2019 00:52:32 GMT
Connection: keep-alive
ETag: "5c830e50-178f9c"
Accept-Ranges: bytes
Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Connection: keep-alive
Host: xlink-iot.qinyuan.cn
If-Modified-Since: Fri, 08 Mar 2019 06:56:18 GMT
If-None-Match: "5c821212-178f9c"
Referer: https://xlink-iot.qinyuan.cn/iot/
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36

接下來我們將對HTTP報文中出現的與緩存規(guī)則相關的信息做出詳細解釋。(我們依舊分為強制緩存和協商緩存兩個方面來介紹)

強制緩存

對于強制緩存,服務器響應的header中會用兩個字段來表明——Expires和Cache-Control

Expires

Exprires的值為服務端返回的數據到期時間。當再次請求時的請求時間小于返回的此時間,則直接使用緩存數據。但由于服務端時間和客戶端時間可能有誤差,這也將導致緩存命中的誤差,另一方面,Expires是HTTP1.0的產物,故現在大多數使用Cache-Control替代。

Cache-Control

Cache-Control有很多屬性,不同的屬性代表的意義也不同。
private:客戶端可以緩存
public:客戶端和代理服務器都可以緩存
max-age=t:緩存內容將在t秒后失效
no-cache:需要使用協商緩存來驗證緩存數據
no-store:所有內容都不會緩存。

協商緩存

協商緩存需要進行對比判斷是否可以使用緩存。瀏覽器第一次請求數據時,服務器會將緩存標識與數據一起響應給客戶端,客戶端將它們備份至緩存中。再次請求時,客戶端會將緩存中的標識發(fā)送給服務器,服務器根據此標識判斷。若未失效,返回304狀態(tài)碼,瀏覽器拿到此狀態(tài)碼就可以直接使用緩存數據了。
對于協商緩存來說,緩存標識我們需要著重理解一下,下面我們將著重介紹它的兩種緩存方案。

Last-Modified

Last-Modified
服務器在響應請求時,會告訴瀏覽器資源的最后修改時間。

if-Modified-Since:
瀏覽器再次請求服務器的時候,請求頭會包含此字段,后面跟著在緩存中獲得的最后修改時間。服務端收到此請求頭發(fā)現有if-Modified-Since,則與被請求資源的最后修改時間進行對比,如果一致則返回304和響應報文頭,瀏覽器只需要從緩存中獲取信息即可。
從字面上看,就是說:從某個時間節(jié)點算起,是否文件被修改了

  1. 如果真的被修改:那么開始傳輸響應一個整體,服務器返回:200 OK
  2. 如果沒有被修改:那么只需傳輸響應header,服務器返回:304 Not Modified

Last-Modified 有個問題,因為如果在服務器上,一個資源被修改了,但其實際內容根本沒發(fā)生改變,會因為Last-Modified時間匹配不上而返回了整個實體給客戶端(即使客戶端緩存里有個一模一樣的資源)。為了解決這個問題,HTTP1.1推出了Etag。

Etag

Etag:
服務器響應請求時,通過此字段告訴瀏覽器當前資源在服務器生成的唯一標識(生成規(guī)則由服務器決定)

If-None-Match:
再次請求服務器時,瀏覽器的請求報文頭部會包含此字段,后面的值為在緩存中獲取的標識。服務器接收到次報文后發(fā)現If-None-Match則與被請求資源的唯一標識進行對比。

  1. 不同,說明資源被改動過,則響應整個資源內容,返回狀態(tài)碼200。
  2. 相同,說明資源無心修改,則響應header,瀏覽器直接從緩存中獲取數據信息。返回狀態(tài)碼304.

緩存的優(yōu)點

  1. 減少了冗余的數據傳遞,節(jié)省寬帶流量
  2. 減少了服務器的負擔,大大提高了網站性能
  3. 加快了客戶端加載網頁的速度
    這也正是HTTP緩存屬于客戶端緩存的原因。

問題

強緩存

1.服務器怎么返回數據到期時間,怎么緩存(需要服務器進行配置)

Cache-Control

let http = require('http')
let url = require('url')
let fs = require('mz/fs')
let path = require('path');
let p = path.resolve(__dirname);
http.createServer(async function(req, res) {

    let {pathname} = url.parse(req.url);
    let realPath = path.join(p, pathname);
    console.log(realPath, '來請求服務了')
    try{
        let statObj = await fs.stat(realPath);
        res.setHeader('Cache-Control','max-age=180')  //強制緩存 180s內不需要再次請求服務器
        //res.setHeader('Cache-Control','no-cache')

        res.setHeader('Content-Type',require('mime').getType(realPath)+';charset=utf8')
       fs.createReadStream(realPath).pipe(res)
    }catch(e) {
        res.statusCode = 404;
        res.end('404')
    }
}).listen(3000)
// 我們請求一個本地的文件index.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>
    <link rel="stylesheet" href="./index.css">
    <!-- <meta http-equiv="Cache-control" content="no-cache"> -->
</head>
<body>
    
    <img src="/he.png"/>
</body>
<script src="./index.js"></script>
</html>

返回頭:

Cache-Control: max-age=180
Connection: keep-alive
Content-Type: text/html;charset=utf8
Date: Sat, 09 Mar 2019 06:42:58 GMT
Transfer-Encoding: chunked

上述中的文件,針對強制緩存,除html文件外其他的資源在180s內均在緩存中讀取,另外強調一點:主網頁只有對比緩存沒有強制緩存,html文件每次都是重新請求服務器文件即使設置了<meta http-equiv="Cache-Control" content="max-age=180" />

expires
//Expires:Sun, 22 Jul 2018 02:43:42 GMT
//備注:如果Cache-Control和Expires同時存在,Cache-Control說了算
res.setHeader('Expires', new Date(Date.now() + 10*1000).toGMTString()) //強制緩存的另一種方式

2.客戶端需要如何設置

沒有,暫時找不到

協商緩存

//對比緩存 為了更加明顯的看到對比緩存,我們將在以下的代碼中都將強制緩存關閉
//res.setHeader('Cache-Control','no-cache')

//響應頭設置了res.setHeader('Last-Modified',statObj.ctime.toGMTString())
//請求頭就會帶上req.headers['if-modified-since']

let http = require('http')
let url = require('url')
let util = require('util')
let fs = require('mz/fs')
let stat = util.promisify(fs.stat);
let path = require('path');
let p = path.resolve(__dirname);
http.createServer(async function(req, res) {
    let {pathname} = url.parse(req.url);
    let realPath = path.join(p, pathname);
    console.log(realPath)
    try{
        let statObj = await fs.stat(realPath);
        console.log(statObj)
        // res.setHeader('Cache-Control','max-age=10')  //強制緩存  10s內不需要再次請求服務器
        res.setHeader('Cache-Control','no-cache')
        res.setHeader('Content-Type',require('mime').getType(realPath)+';charset=utf8')
        res.setHeader('Expires', new Date(Date.now() + 10*1000).toGMTString()) //強制緩存 因為上面設置了no-cache,所以這里的設置其實無效
        let since = req.headers['if-modified-since'];
        if (since === statObj.ctime.toGMTString()) {
            res.statusCode = 304                      //服務器的緩存
            res.end();
        } else {
            res.setHeader('Last-Modified',statObj.ctime.toGMTString())
            fs.createReadStream(realPath).pipe(res)
        }
    }catch(e) {
        res.statusCode = 404;
        res.end('404')
    }
}).listen(3000)

返回頭

Cache-Control: no-cache
Connection: keep-alive
Content-Type: text/html;charset=utf8
Date: Sat, 09 Mar 2019 07:22:17 GMT
Last-Modified: Sat, 09 Mar 2019 07:04:41 GMT
Transfer-Encoding: chunked

在瀏覽器打開localhost:3000/index.html 刷新看到就是304,我們返回狀態(tài)碼304,瀏覽器就乖乖地去讀緩存中的文件了。我們稍微改動一下index.html就可以看到 200

Etag

//對比緩存
//Etag內容的標識 
// 響應頭設置了res.setHeader('Etag',statObj.size.toString());  這里設置的是文件大小
//請求頭就會帶上req.headers['if-none-match'];
let http = require('http');
let util = require('util');
let fs = require('fs');
let stat = util.promisify(fs.stat);
let url = require('url');
let path = require('path');
let p = path.resolve(__dirname);

// 比較內容 stat.size
// 第一次請求Etag:內容的標識  
// 第二次在請求我的時候 if-none-match 

http.createServer(async function(req,res){
    let {pathname} = url.parse(req.url);
    let realPath = path.join(p,pathname);
    try{
        let statObj = await stat(realPath);
        console.log(realPath) 
        res.setHeader('Cache-Control','no-cache');
        let match = req.headers['if-none-match'];
        if(match){
            if(match === statObj.size.toString()){
                res.statusCode = 304;
                res.end();
            }else{
                res.setHeader('Etag',statObj.size.toString());
                fs.createReadStream(realPath).pipe(res);
            }
        }else{
            res.setHeader('Etag',statObj.size.toString());
            fs.createReadStream(realPath).pipe(res);
        }
       
    }catch(e){
        res.statusCode = 404;
        res.end(`not found`);
    }
}).listen(3000);

兩種緩存的區(qū)別

  1. 強制緩存
  • 設置強制緩存的方式就是 res.setHeader('Cache-Control','max-age=10')
  • res.setHeader('Expires', new Date(Date.now() + 10*1000).toGMTString())
  • 以上兩種以第一種方式取決定作用
  1. 對比緩存
  • 通過時間對比 Last-Modified ---- if-modified-since
  • 通過標識對比 Etag ---- if-none-match

總結

緩存只能服務端配置?。。?!,目前暫時沒有找到客戶端能做的事,html5的meta標簽好像在也沒什么用,感謝大佬指正

參考文檔

聊聊web緩存那些事!

HTTP----HTTP緩存機制

?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容