HTTP协议特性之缓存Cache——NodeJs版

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/TDCQZD/article/details/81950576

说明:本篇文章以Chrome浏览器作为客户端,以Websrtom开启nodejs服务端,以请求图片资源示例做代码演示。

一、如何查看浏览器端的缓存

1、chrome://cache,如果出现如下界面,说明浏览器版本较高,不支持直接查看。请采用第二种方式。
这里写图片描述

2、使用chrome cache view工具
这里写图片描述

二、缓存控制

HTTP/1.1定义的 Cache-Control 头用来区分对缓存机制的支持情况, 请求头和响应头都支持这个属性。通过它提供的不同的值来定义缓存策略。

1、禁止进行缓存

缓存中不得存储任何关于客户端请求和服务端响应的内容。每次由客户端发起的请求都会下载完整的响应内容。

Cache-Control: no-store
Cache-Control: no-cache, no-store, must-revalidate

2、强制确认缓存

如下头部定义,此方式下,每次有请求发出时,缓存会将此请求发到服务器(译者注:该请求应该会带有与本地缓存相关的验证字段),服务器端会验证请求中所描述的缓存是否过期,若未过期(注:实际就是返回304),则缓存才使用本地缓存副本。

Cache-Control: no-cache

3、私有缓存和公共缓存

“public” 指令表示该响应可以被任何中间人(译者注:比如中间代理、CDN等)缓存。若指定了”public”,则一些通常不被中间人缓存的页面(译者注:因为默认是private)(比如 带有HTTP验证信息(帐号密码)的页面 或 某些特定影响状态码的页面),将会被其缓存。

而 “private” 则表示该响应是专用于某单个用户的,中间人不能缓存此响应,该响应只能应用于浏览器私有缓存中。

Cache-Control: private
Cache-Control: public

4、缓存过期机制

过期机制中,最重要的指令是 “max-age=”,表示资源能够被缓存(保持新鲜)的最大时间。相对Expires而言,max-age是距离请求发起的时间的秒数。针对应用中那些不会改变的文件,通常可以手动设置一定的时长以保证缓存有效,例如图片、css、js等静态资源。

详情看下文关于缓存有效性的内容。

Cache-Control: max-age=31536000

5、缓存验证确认

当使用了 “must-revalidate” 指令,那就意味着缓存在考虑使用一个陈旧的资源时,必须先验证它的状态,已过期的缓存将不被使用。详情看下文关于缓存校验的内容。

Cache-Control: must-revalidate

三、图片数据请求示例——不使用缓存

nodejs开启服务端,便于Chrome请求数据。
数据交互如下:
http://localhost:8080 :返回字符串
http://localhost:8080/img: 返回图片资源。

server.js

const http = require("http")
const host = "127.0.0.1";
const port = 8080
const server = http.createServer();
const fs = require("fs")

server.on("request", (requstMsg, respone) => {
    if (requstMsg.url === "/") {
        respone.writeHead(200, "OK", {"Content-Type": "text/html; charset=utf-8"});
        respone.end("请求根路径");
    } else if (requstMsg.url === "/img") {
        const readStream = fs.createReadStream("../imgs/yxc.jpg")
        respone.writeHead(200,"OK",{
            "Content-Type":"image/jpeg"
        })
        readStream.pipe(respone);

    }

})
server.listen(port, host, () => {
    console.log("服务启动了")
})

说明:后续缓存代码处理,会以此代码为基础修改。

四、图片数据请求示例——使用强缓存

1、如何使用强缓存?

响应头添加Cache-Control

 respone.writeHead(200,"OK",{
            "Content-Type":"image/jpeg",
            "Cache-Control":"max-age=5"
        })

Cache-Control 通用消息头被用于在http 请求和响应中通过指定指令来实现缓存机制。缓存指令是单向的, 这意味着在请求设置的指令,在响应中不一定包含相同的指令。

2、Cache-Control指令介绍

这里写图片描述
指令详解:

可缓存性指令
public
表明响应可以被任何对象(包括:发送请求的客户端,代理服务器,等等)缓存。
private
表明响应只能被单个用户缓存,不能作为共享缓存(即代理服务器不能缓存它),可以缓存响应内容。
no-cache
在释放缓存副本之前,强制高速缓存将请求提交给原始服务器进行验证。
only-if-cached
表明客户端只接受已缓存的响应,并且不要向原始服务器检查是否有更新的拷贝

缓存到期指令
max-age=<seconds>
设置缓存存储的最大周期,超过这个时间缓存被认为过期(单位秒)。与Expires相反,时间是相对于请求的时间。
s-maxage=<seconds>
覆盖max-age 或者 Expires 头,但是仅适用于共享缓存(比如各个代理),并且私有缓存中它被忽略。
max-stale[=<seconds>]
表明客户端愿意接收一个已经过期的资源。 可选的设置一个时间(单位秒),表示响应不能超过的过时时间。
min-fresh=<seconds>
表示客户端希望在指定的时间内获取最新的响应。
stale-while-revalidate=<seconds>
表明客户端愿意接受陈旧的响应,同时在后台异步检查新的响应。秒值指示客户愿意接受陈旧响应的时间长度。
stale-if-error=<seconds>
表示如果新的检查失败,则客户愿意接受陈旧的响应。秒数值表示客户在初始到期后愿意接受陈旧响应的时间。

重新验证和重新加载
must-revalidate
缓存必须在使用之前验证旧资源的状态,并且不可使用过期资源。
proxy-revalidate
与must-revalidate作用相同,但它仅适用于共享缓存(例如代理),并被私有缓存忽略。
immutable
表示响应正文不会随时间而改变。资源(如果未过期)在服务器上不发生改变,因此客户端不应发送重新验证请求头(例如If-None-Match或If-Modified-Since)来检查更新,即使用户显式地刷新页面。在Firefox中,immutable只能被用在 https:// transactions.
其他指令
no-store
缓存不应存储有关客户端请求或服务器响应的任何内容。
no-transform
不得对资源进行转换或转变。Content-Encoding, Content-Range, Content-Type等HTTP头不能由代理修改。例如,非透明代理可以对图像格式进行转换,以便节省缓存空间或者减少缓慢链路上的流量。 no-transform指令不允许这样做。

2、 max-age 介绍

max-age单位是秒。当HTTP协议的返回头中包含Cache- Control字段时,浏览器会对当前的资源进行缓存。缓存的时效由max-age决定。在max-age设定的时间内对当前资源发起访问后使用的都是浏览器内部的缓存!超过max-age设定的时间后浏览器会选择使用协商缓存。

no-cache&no-store区别
no-cache
使用缓存前必须先确认其有效性。
no-store
不缓存请求或响应的任何内容。

no-cache:会缓存资源到缓存中,但不使用缓存中资源,重新请求资源
no-store:不会缓存资源到缓存中,直接重新请求资源。

3、代码示例:

const http = require("http")
const host = "127.0.0.1";
const port = 8080
const server = http.createServer();
const fs = require("fs")

server.on("request", (requstMsg, respone) => {
    if (requstMsg.url === "/") {
        respone.writeHead(200, "OK", {"Content-Type": "text/html; charset=utf-8"});
        respone.end("请求根路径");
    } else if (requstMsg.url === "/img") {
        const readStream = fs.createReadStream("../imgs/yxc.jpg")
        /*
        * 使用强缓存请求
        * */
        respone.writeHead(200,"OK",{
            "Content-Type":"image/jpeg",
            "Cache-Control":"max-age=5"
        })
        readStream.pipe(respone);

    }

})
server.listen(port, host, () => {
    console.log("服务启动了")
})

4、强缓存使用的问题

1)设置的强缓存时间失效后,再次请求同一张照片(即刷新操作),客户端会重新发送请求,向服务器请求图片。
但是既然请求的是同一张图片,缓存中又存在该图片,那么从缓存中获取图片不是
更好吗?如何解决?
可以使用协商缓存解决。

2)设置的强缓存时间较长比如100年,第一次请求图片(yxc.jpg)后,某段数据间图片资源已经修改(yys.jpg)。在强缓存有效期内重新发送请求,此时客户端会从缓存获取图片,获取的资源修改前的图片(yxc.jpg)。
但是此时,我想要的是修改后的图片(yys.jpg),如何解决?
可以使用协商缓存解决。

五、图片数据请求示例——使用协商缓存

1、协商缓存实现原理

通过使用实体首部字段last-modified(响应)和 请求首部字段if-modified-since监控JS文件的变化。

如果JS文件修改了,通过使用控件自动更新last-modifiedif-modified-since值。

如果last-modifiedif-modified-since值相等,则返回304状态码,由浏览器从缓存中获取资源;如果值不相等,就重新请求资源。

2、 last-modified & if-modified-since

实体首部字段last-modified(响应)& 请求首部字段if-modified-since

首部字段last-modified指明资源最终修改的时间,一般来说,这个值就是资源被修改的时间。

首部字段if-modified-since:第一次请求资源时,资源在响应头中种入last-modified字段,并随着响应体一起存到缓存中下一次需要再发送请求时,请求体中会将上一次修改时间(last-modified)种入if-modified-since字段中带到服务端。若在if-modified-since字段值之后对应的资源都没有更新过,则返回304 Not Modified状态码
若在if-modified-since字段值之后对应的资源有过更新则希望服务器能重新处理。 则返回 成功 200失败 500
此处的客户端与服务端协商的缓存策略一般与cache-control一块使用。需要在cache-control失效后再走这种缓存策略

3、如何使用协商缓存?

响应头添加last-modified

 respone.writeHead(200, "OK", {
                "Content-Type": "image/jpeg",
                "Cache-Control": "max-age=5",
                "last-modified": data
            })

4、缓存策略中客户端做了什么?

首先应该明确,只有服务端是无法处理缓存的,那么客户端浏览器做了哪些功能?

1)更新if-modified-since
客户端通过服务端响应,判断last-modified是否变化,并更新if-modified-since。

2)客户端通过判断响应头状态,决定是否从缓存中获取资源
如果响应头状态是304,客户端就会从缓存中获取资源。而不是重新请求资源。

5、缓存策略代码:

const http = require("http")
const host = "127.0.0.1";
const port = 8080
const server = http.createServer();
const fs = require("fs")
//data :JS文件更新标识符,通常采用更新时间(由控件更新),监控js文件变化,如果js文件修改,data更新

const data = "Wed, 22 Aug 2020 03:45:18 GMT"
server.on("request", (requstMsg, respone) => {
    if (requstMsg.url === "/") {
        respone.writeHead(200, "OK", {"Content-Type": "text/html; charset=utf-8"});
        respone.end("请求根路径");
    } else if (requstMsg.url === "/img") {
        /*
         * 使用协商缓存请求
         * */
        if (requstMsg.headers["if-modified-since"] === data) {
            respone.writeHead(304, "keep cache", {
                "Content-Type": "image/jpeg",
                "Cache-Control": "max-age=5",
                "last-modified": data
            })
            respone.end();
        } else {
            const readStream = fs.createReadStream("../imgs/yxc.jpg")

            respone.writeHead(200, "OK", {
                "Content-Type": "image/jpeg",
                "Cache-Control": "max-age=5",
                "last-modified": data
            })
            readStream.pipe(respone);

        }
    }
})

server.listen(port, host, () => {
    console.log("服务启动了")
})

6、如何告诉浏览器一直使用协商缓存?

当第二次请求资源时,使用协商策略,那么此后所有的请求都应该使用协商策略,而不使用强缓存策略。如何实现?即如何告诉浏览器一直使用协商缓存?
解决方案:修改Cache-Control

 "Cache-Control": "max-age=1000",

修改为

   "Cache-Control": "max-age=1000, no-cache",

即可。
因为强缓存优先级小于协商缓存,所以修改Cache-Control后即可告诉客户端一直使用协商缓存。

修改后代码示例:

const http = require("http")
const host = "127.0.0.1";
const port = 8080
const server = http.createServer();
const fs = require("fs")
//data :更新时间(由控件更新),监控js文件变化,如果js文件修改,data更新
const data = "Wed, 22 Aug 2020 03:45:18 GMT"
server.on("request", (requstMsg, respone) => {
    if (requstMsg.url === "/") {
        respone.writeHead(200, "OK", {"Content-Type": "text/html; charset=utf-8"});
        respone.end("请求根路径");
    } else if (requstMsg.url === "/img") {
        /*
         * 使用强缓存+协商缓存请求
         * 如果走到协商缓存,代表强缓存失效,以后一直使用协商缓存
         * 告诉浏览器一直使用协商缓存?no-cache
         * 强缓存优先级小于协商缓存。
         * */
        if (requstMsg.headers["if-modified-since"] === data) {
            respone.writeHead(304, "keep cache", {
                "Content-Type": "image/jpeg",
                "Cache-Control":"no-cache",
                "last-modified": data
            })
            respone.end();
        } else {
            const readStream = fs.createReadStream("../imgs/yxc.jpg")

            respone.writeHead(200, "OK", {
                "Content-Type": "image/jpeg",
                "Cache-Control": "max-age=10000, no-cache",
                "last-modified": data
            })
            readStream.pipe(respone);

        }
    }
})

server.listen(port, host, () => {
    console.log("服务启动了")
})

7、协商缓存使用的问题即last-modified缺陷

1)某些服务端没有办法获取精确的修改时间,导致last-modified有问题
2)文件时间修改了,但文件内容却没有变

通俗来讲,last-modified的值就是JS文件更新标识符,如果JS文件变化了,last-modified的值就会动态更新。 它只认文件的最后修改时间。
可如果JS文件修改了但资源文件并没有修改或者值错了,按照当前的协商缓存策略,客户端应该是重新请求图片,而不是从缓存中获取。因此需要优化协商缓存策略.

六、图片数据请求示例——协商缓存优化

1、实现原理:

通过使用响应首部字段Etag & 请求首部字段if-None-Match监控JS文件资源的变化。
如果S文件资源修改了,通过使用控件自动更新etag 和if-none-match值。
如果etag 和if-none-match值相等,表示资源未修改,则返回304状态码,由浏览器从缓存中获取资源;
如果etag 和if-none-match值不相等,通过使用实体首部字段last-modified(响应)和 请求首部字段if-modified-since监控JS文件的变化。
如果JS文件修改了,通过使用控件自动更新last-modified和if-modified-since值。
如果last-modified和if-modified-since值相等,则返回304状态码,由浏览器从缓存中获取资源;如果值不相等,就重新请求资源。

2、 Etag&if-None-Match

响应首部字段etag & 请求首部字段if-None-Match
响应首部字段etag:它可以告知客户端实体标识,它是一种可以将资源以字符串做唯一标识的方式,服务器会为每份资源分配对应的Etag值。另外当资源更新时,etag的值也需要更新,这个唯一标识的生成没有规定统一的算法,由服务器自行决定
请求首部字段if-None-Match:机制和if-modified-since相似,当if-None-Match字段与etag不一致时,就告知服务器该处理这请求
此处的客户端与服务端协商的缓存策略一般与cache-control一块使用。需要在cache-control失效后再走这种缓存策略。

3、代码示例

const http = require("http")
const host = "127.0.0.1";
const port = 8080
const server = http.createServer();
const fs = require("fs")
//data :JS文件更新标识符,通常采用更新时间(由控件更新),监控js文件变化,如果js文件修改,data更新
const data = "Wed, 22 Aug 2020 03:45:18 GMT"
//etag :JS文件资源更新标识符(由控件更新),监控js文件资源(图片资源)变化,如果js文件文件资源(图片资源)修改,etag更新
const etag = "12346";
server.on("request", (requstMsg, respone) => {
    if (requstMsg.url === "/") {
        respone.writeHead(200, "OK", {"Content-Type": "text/html; charset=utf-8"});
        respone.end("请求根路径");
    } else if (requstMsg.url === "/img") {
        /*
         * 使用强缓存+协商缓存请求
         * 如果走到协商缓存,代表强缓存失效,以后一直使用协商缓存
         * 告诉浏览器一直使用协商缓存?no-cache
         * 强缓存优先级小于协商缓存。
         * */
        if(requstMsg.headers["if-none-match"] === etag){
            respone.writeHead(304,"keep cache",{
                "Content-Type":"image/jpeg",
                "Cache-Control":"no-cache",
                "last-modified":data,
                "Etag":etag
            })
            //告诉浏览器处理完毕,客户端忽略内容
            respone.end();

        } else if (requstMsg.headers["if-modified-since"] === data) {
            respone.writeHead(304, "keep cache", {
                "Content-Type": "image/jpeg",
                "Cache-Control":"no-cache",
                "last-modified": data,
                "Etag":etag
            })
            respone.end();
        } else {
            const readStream = fs.createReadStream("../imgs/yxc.jpg")
            respone.writeHead(200, "OK", {
                "Content-Type": "image/jpeg",
                "Cache-Control": "max-age=10000, no-cache",
                "last-modified": data,
                "Etag":etag
            })
            readStream.pipe(respone);

        }
    }
})

server.listen(port, host, () => {
    console.log("服务启动了")
})

七、Chrome缓存的一个注意点

重新请求(刷新)一个URL时,RequestHeaders总是莫名多一个Cache-Control: max-age=0。相当于工具栏中勾选Disable cahce.表示禁用缓存。
这里写图片描述

八、总结

这里写图片描述

展开阅读全文

没有更多推荐了,返回首页