http强制缓存、协商缓存、指纹ETag详解
每个浏览器都有一个自己的缓存区,使用缓存区的数据有诸多好处,减少冗余的数据传输,节省网络传输。减少服务器负担, 提高网站的性能。加快客户端加载网页的速度等,而这里指的缓存,指代的静态文件的缓存,动态数据缓存需要走redis。今天我们使用node搭建服务,简单演示一下几种缓存的设置及配合使用。
缓存分为disk cache
和 memory cache
两种,浏览器自行处理,代码层面无法控制。而我们一般在用的时候都是在nginx
层做处理,但核心是一样的,都是设置header
简单说一下,Chrome浏览器的缓存文件位置在哪,感兴趣的同学可以自己找一找:
chrome浏览器地址栏中输入:
chrome://version/
找到个人资料路径(我的是):
C:\Users\Lenovo\AppData\Local\Google\Chrome\User Data\Default
计算机中找到对应的目录,可以在这个目录下查看到
Cache
和Code Cache
目录,这个就是缓存文件目录进入对应的目录,可以进行手动删除
实操目录及步骤
初始化package.json: npm init -y
下载第三方模块:npm i mime
.
│
└─cache
├─node_modules
├─public // 静态文件目录
├─1.js // 请求的文件资源
├─index.html
├─1.cache.js // 强制缓存 完整代码案例
├─2.cache.js // 协商缓存 完整代码案例
├─3.cache.js // 指纹对比 完整代码案例
缓存分类
强制缓存
:直接缓存至浏览器中,不会再次向服务器发送请求;对比缓存
:也叫协商缓存,客服各执一份文件修改时间
,相互对比,若相同用客户端缓存指纹Etag
:为解决对比缓存存在的一些问题,客服各执一份文件签名
,相互对比,若相同用客户端缓存
强制缓存
服务器与浏览器约定一个缓存的最大存活时间,如10s,那么10s内,浏览器请求相同的资源便不会在请求服务器,会默认走浏览器的缓存区,并且响应码依然为
200
如果返回的是一个html,其中又引用了其他资源,还会继续向服务器发送请求。
不对首次访问的路径做处理,也就是第一次访问时,不走强制缓存的,必然会请求到服务器端,因为如果连首页都走缓存了,那么在断网或服务器宕机的情况下也可以访问该网站,显然是不合理的
可以根据不同的文件后缀,设置不同的强制缓存的时间
在缓存数据生效期间,可以直接使用缓存数据,在没有缓存数据时,浏览器向服务器请求数据,服务器会将数据和缓存规则一并返回,缓存规则信息包含在响应头中。
强制缓存常用的
两种响应头设置
// 10s 表示当前时间 + 10s,属于相对时间 (用于新版浏览器)
res.setHeader('Cache-Control', 'max-age=10'); // 设置 绝对时间 (用于旧版浏览器或IE老版本 或 http1.0)
// 设置header的值只能是数字,字符串或数组,不能为对象,new Date()返回的是对象,所以需要转一下。
res.setHeader('Expires', new Date(Date.now() + 10 * 1000).toUTCString());
完整代码:
const http = require('http')
const url = require('url')
const path = require('path');
const fs = require('fs');
const mime = require('mime'); const server = http.createServer((req, res) => {
let { pathname } = url.parse(req.url, true)
let filepath = path.join(__dirname, 'public', pathname); // 访问路径拼接 public // 10s 表示当前时间 + 10s,属于相对时间 (用于新版浏览器)
res.setHeader('Cache-Control', 'max-age=10');
// 设置 绝对时间 (用于旧版浏览器或IE老版本 或 http1.0)
// 设置header的值只能是数字,字符串或数组,不能为对象,new Date()返回的是对象,所以需要转一下。
res.setHeader('Expires', new Date(Date.now() + 10 * 1000).toUTCString()); fs.stat(filepath, function (err, statObj) {
if (err) { // 获取文件信息报错,则则响应 404
res.statusCode = 404;
res.end('Not Found!')
} else {
// 如果是文件,设置对应类型的响应头,并返响应文件内容
if (statObj.isFile()) {
res.setHeader('Content-Type', mime.getType(filepath) + ';charset=utf-8');
fs.createReadStream(filepath).pipe(res);
} else {
// 如果是目录,需要找目录下的 index.html
let htmlPath = path.join(filepath, 'index.html') // 拼接路径
fs.access(htmlPath, function (err) {
if (err) { // 查看文件的可访问性,如不能访问则响应 404
res.statusCode = 404;
res.end('Not Found!')
} else {
res.setHeader('Content-Type', 'text/html;charset=utf-8');
fs.createReadStream(htmlPath).pipe(res)
}
})
}
}
})
}); // 服务监听 3000 端口
server.listen(3000, function () {
console.log('server is running....');
})
对比缓存
浏览器首次请求资源时,服务器会将
缓存标识(文件修改时间)
与资源一同返回给浏览器。再次请求时,客户端请求头会携带
缓存标识(If-Modified-Since)
,并在服务端对比两个时间。若相等,直接返回
304状态码
,读取浏览器的缓存中对应缓存文件;若不相等,返回最新内容,并给文件设置新的修改时间。
对比缓存
不管是否生效,都需要与服务端发生交互
强制缓存和对比缓存可以配合使用,如10s内强制缓存,超过10s走对比缓存,同时在设置10s的强制缓存
响应头设置
// no-cache: 需要使用对比缓存验证数据,会向服务器发送请求,且数据会存到浏览器的缓存中
res.setHeader('Cache-Control', 'no-cache'); // 设置响应头,文件的最后修改时间
res.setHeader('Last-Modified',ctime)
Last-Modify & If-Modified-Since
完整代码:
const http = require('http')
const url = require('url')
const path = require('path');
const fs = require('fs');
const mime = require('mime'); const server = http.createServer((req, res) => {
let { pathname } = url.parse(req.url, true)
let filepath = path.join(__dirname, 'public', pathname);
// 强制缓存和对比缓存配合使用,10s内走强制缓存,超过10s会走对比缓存,同时在设置10s的强制缓存
// res.setHeader('Cache-Control', 'max-age=10'); res.setHeader('Cache-Control', 'no-cache'); fs.stat(filepath, function (err, statObj) {
if (err) {
res.statusCode = 404;
res.end('Not Found!')
} else {
// 如果是文件
if (statObj.isFile()) {
const ctime = statObj.ctime.toGMTString();
// 判断请求头存储的时间与服务器端文件的最后修改时间是否相等
if(req.headers['if-modified-since'] === ctime){
res.statusCode = 304; // 设置响应状态码,浏览器默认会自动解析,从缓存中读取对应文件
res.end(); // 表示此时服务器没有响应结果
}else{
// 设置响应头,文件的最后修改时间
res.setHeader('Last-Modified',ctime)
// 设置对应类型的响应头,并返响应文件内容
res.setHeader('Content-Type', mime.getType(filepath) + ';charset=utf-8');
fs.createReadStream(filepath).pipe(res);
}
} else {
// 如果是目录,需要找目录下的index.html
let htmlPath = path.join(filepath, 'index.html') // 拼接路径
fs.access(htmlPath, function (err) {
if (err) { // 查看文件的可访问性,如不能访问则响应 404
res.statusCode = 404;
res.end('Not Found!')
} else {
res.setHeader('Content-Type', 'text/html;charset=utf-8');
fs.createReadStream(htmlPath).pipe(res)
}
})
}
}
})
}); // 服务监听 3000 端口
server.listen(3000, function () {
console.log('server is running....');
})
指纹 Etag
在讲指纹
之前,还需要介绍一下摘要算法
及加密算法
,crypto
是node中提供好的用于加密的模块,各种摘要算法和加密算法。
摘要及加密算法
MD5:常见的MD5算法,也叫hash算法或者摘要算法,具有以下特点:
- 不能反解,不可逆,
- 相同的内容,摘要出的结果相同
- 不同的内容,摘要出长度是相同的
- 不同的内容,摘要的结果完全不同 (也称雪崩效应,有一点不一样,结果就完全不一样)
- 网上在线解密MD5其实只是通常意义的撞库
- 撞库不叫解密,为了安全,可以将一个md5值多次加密,一般三次以上就无法破解了md5(md5(md5(xxx))),
sah1/sha256:加盐算法,是真正的加密算法,设定一个盐值(秘钥)
,内容一致,盐值不同,结果不同
const crypto = require('crypto');
/** md5*/
// 摘要的内容 摘要的格式
let r1 = crypto.createHash('md5').update('abcd').digest('base64');
// 分开摘要, 如果内部使用了流,可以读一点摘要一点
let r2 = crypto.createHash('md5').update('a').update('b').update('cd').digest('base64');
console.log(r1, r2);
/** sha256 */
const crypto = require('crypto');
let r3 = crypto.createHmac('sha256','n').update('ab')..update('cd').digest('base64');
let r4 = crypto.createHmac('sha256','h').update('a')..update('bcd').digest('base64');
console.log(r3, r4);
进入正题,对比缓存使用的最后修改时间方案也存在一定的问题:
- 某些服务器不能精确得到文件的最后修改时间, 这样就无法通过最后修改时间来判断文件是否更新了。
- 某些文件的修改非常频繁,在秒以下的时间内进行多次修改,而Last-Modified只能精确到秒。
- 一些文件的最后修改时间改变了,但是内容并未改变(典型吃了吐)。 因此不希望被认为是修改。
- 如果同样的一个文件位于多个CDN服务器,内容虽然一样,修改时间不一样。
Etag的出现,可以在一定程度上解决这个问题,但不能说完全解决,他也存在他的问题,接下来分析一下他的实现原理:
ETag(实体标签),根据
摘要算法
将实体内容生成的一段hash字符串,文件改变,ETag也随之改变但是对于大文件,不会直接全量比对,可以用文件的大小,开头、或某一段生成一个指纹
浏览器首次请求资源时,服务器会将
ETag
与资源一同返回给浏览器。再次请求时,客户端请求头会携带
签名标识(If-None-Match)
,并在服务端对比两个签名。若相等,直接返回
304状态码
,读取浏览器的缓存中对应缓存文件;若不相等,返回最新内容,并给文件设置新的修改时间。
ETag
不管是否生效,都需要与服务端发生交互
响应头设置
// no-cache: 需要使用对比缓存验证数据,会向服务器发送请求,且数据会存到浏览器的缓存中
res.setHeader('Cache-Control', 'no-cache'); // 设置响应头,文件的最后修改时间
res.setHeader('Last-Modified',ctime)
ETag & If-None-Match
完整代码:
const http = require('http')
const url = require('url')
const path = require('path');
const fs = require('fs');
const mime = require('mime');
const crypto = require('crypto'); const server = http.createServer((req, res) => {
let { pathname } = url.parse(req.url, true)
let filepath = path.join(__dirname, 'public', pathname); fs.stat(filepath, function (err, statObj) {
if (err) {
res.statusCode = 404;
res.end('Not Found!')
} else {
// 如果是文件
if (statObj.isFile()) {
let content = fs.readFileSync(filepath);
let etag = crypto.createHash('md5').update(content).digest('base64');
// 判断请求头存储的签名与服务端文件的生成的签名是否相等
if(req.headers['if-none-match'] === etag){
res.statusCode = 304; // 设置响应状态码,浏览器默认会自动解析,从缓存中读取对应文件
res.end() // 表示此时服务器没有响应结果
}else{
// 设置响应头,签名
res.setHeader('Etag',etag)
// 设置对应类型的响应头,并返响应文件内容
res.setHeader('Content-Type', mime.getType(filepath) + ';charset=utf-8');
fs.createReadStream(filepath).pipe(res);
}
} else {
// 如果是目录,需要找目录下的index.html
let htmlPath = path.join(filepath, 'index.html') // 拼接路径
fs.access(htmlPath, function (err) {
if (err) { // 查看文件的可访问性,如不能访问则响应 404
res.statusCode = 404;
res.end('Not Found!')
} else {
res.setHeader('Content-Type', 'text/html;charset=utf-8');
fs.createReadStream(htmlPath).pipe(res)
}
})
}
}
})
}); // 服务监听 3000 端口
server.listen(3000, function () {
console.log('server is running....');
})
缓存总结
强制缓存如果生效,不会再和服务器发生交互
,而对比缓存不管是否生效,都需要与服务端发生交互
缓存规则可以同时存在,
强制缓存优先级高于对比缓存
,也就是说,当强制缓存规则生效时,直接使用缓存,不再执行对比缓存规则可以设置不同的匹配规则,采用不同的缓存方式
重要代码:
// 第一次发送文件,先设置强制缓存,在执行强制缓存时,默认不会执行对比缓存,因为不走服务器
res.setHeader('Cache-Control','max-age=10');
res.setHeader('Expires',new Date(Date.now() + 10 * 1000).toGMTString()); // 每次强制缓存时间到了,就会走对比缓存,然后在变成强制缓存
const lastModified = statObj.ctime.toGMTString();
const etag = crypto.createHash('md5').update(readFileSync(requestFile)).digest('base64');
res.setHeader('Last-Modified',lastModified);
res.setHeader('Etag',etag); let ifModifiedSince = req.headers['if-modified-since'];
let ifNoneMatch = req.headers['if-none-match'];
// 如果文件修改时间不一样,就直接返回最新的
if(lastModified !== ifModifiedSince){ // 有可能时间一样,但是内容不一样
return createReadStream(requestFile).pipe(res);;
}
if(etag !== ifNoneMatch){ // 一般情况,指纹生成不会是根据文件全量生成,有可能只是根据文件大小等
return createReadStream(requestFile).pipe(res);;
}
res.statusCode = 304;
return res.end();
http强制缓存、协商缓存、指纹ETag详解的更多相关文章
- php缓存技术——memcache常用函数详解
php缓存技术——memcache常用函数详解 2016-04-07 aileen PHP编程 Memcache函数库是在PECL(PHP Extension Community Library)中, ...
- HTTP缓存——协商缓存(缓存验证)
协商缓存 所谓"协商",可以理解为:客户端和服务端双方商量着来. 客户端检查资源超过有效期.强缓存命中失败的情况下,则发出请求"询问"服务器是否资源真的过期了, ...
- 高并发架构系列:Redis缓存和MySQL数据一致性方案详解
一.需求起因 在高并发的业务场景下,数据库大多数情况都是用户并发访问最薄弱的环节.所以,就需要使用redis做一个缓冲操作,让请求先访问到redis,而不是直接访问MySQL等数据库. 这个业务场景, ...
- 缓存架构中的服务详解!SpringBoot中二级缓存服务的实现
创建缓存服务 创建缓存服务接口项目 创建myshop-service-redis-api项目,该项目只负责定义接口 创建项目的pom.xml: <?xml version="1.0&q ...
- 页面缓存之Meta http-equiv属性详解
http-equiv顾名思义,相当于http的文件头作用,它可以向浏览器传回一些有用的信息,以帮助正确和精确地显示网页内容,与之对应的属性值为content,content中的内容其实就是各个参数的变 ...
- HTTP 强制缓存和协商缓存
Web 缓存能够减少延迟与网络阻塞,进而减少显示某个资源所用的时间.借助 HTTP 缓存,Web 站点变得更具有响应性. 缓存优点: 减少不必要的数据传输,节省带宽 减少服务器负担,提升网站性能 加快 ...
- 浏览器缓存_HTTP强缓存和协商缓存
浏览器缓存 浏览器缓存是浏览器在本地磁盘对用户最近请求过的文档进行存储,当访问者再次访问同一页面时,浏览器就可以直接从本地磁盘加载文档. 所以根据上面的特点,浏览器缓存有下面的优点: 减少冗余的数据传 ...
- HTTP强缓存和协商缓存
一.浏览器缓存 Web 缓存能够减少延迟与网络阻塞,进而减少显示某个资源所用的时间.借助 HTTP 缓存,Web 站点变得更具有响应性. (一).缓存优点: 减少不必要的数据传输,节省带宽 减少服务器 ...
- 浏览器相关,关于强缓存、协商缓存、CDN缓存。
强缓存和协商缓存 在介绍缓存的时候,我们习惯将缓存分为强缓存和协商缓存两种.两者的主要区别是使用本地缓存的时候,是否需要向服务器验证本地缓存是否依旧有效. 顾名思义,协商缓存,就是需要和服务器进行协商 ...
随机推荐
- 【BUAA软工】Beta阶段设计与计划
一.需求再分析 根据用户反馈,是否发现之前的需求分析有偏差?为什么会出现这种偏差?beta阶段你们是否能真的分析清楚用户需求?如何做到? 根据alpha阶段同学们以及课程组老师和助教的使用反馈,总结起 ...
- Let's go!
第一次开通博客 心情还是很激动的,而且做出了这么好看的页面虽然都是用的别人的组件,自己不是很知道原理但是也很开心,以后会将自己学习的东西写成笔记发在上面
- 搭建LAMP环境部署Ecshop电商网站
实战-部署Ecshop电商网站 实验环境 Centos7 ip:192.168.121.17 一.关闭防火墙和selinux [root@localhost ~]# systemctl stop fi ...
- docker总结复习
一.概念 1.容器( container-based )虚拟化方案,充分利用了操作系统本身已有的机制和特性,以实现轻量级的虚拟化(每个虚拟机安装的不是完整的虚拟机),甚至有人把他称为新一代的虚拟化技术 ...
- STM32之HAL库、标准外设库、LL库(STM32 Embedded Software)-(转载)
STM32 Embedded Software 工作以来一直使用ST的STM32系列芯片,ST为开发者提供了非常方便的开发库.到目前为止,有标准外设库(STD库).HAL库.LL库 三种.前两者都是 ...
- python基础之面向对象(一)(概念、实例、魔法方法)
一.面向对象概念理解 1.面向对象和面向过程 面向过程:核心过程二字,过程即解决问题的步骤,就是先干什么后干什么 基于该思想写程序就好比在这是一条流水线,是一种机械式的思维方式 优点:复杂的过程流程化 ...
- C语言编程 菜鸟练习100题(31-40)
[练习31]判断质数 0. 题目: 判断质数 1. 分析: 质数(prime number),指大于 1的.且除 1 和本身以外没有其他因数的自然数. 2. 程序: #include <stdi ...
- 《Matlab实用案例》系列Matlab从入门到精通实用100例案例教程目录(持续更新)
目录 1. 专栏简介 2. 专栏地址 3. 专栏目录 1. 专栏简介 2. 专栏地址 「 刘一哥与GIS的故事 」之<Matlab使用案例> 3. 专栏目录 [MATLAB统计分析与应用1 ...
- 常用Python第三方库简介
如果说强大的标准库奠定了Python发展的基石,丰富的第三方库则是python不断发展的保证,随着python的发展一些稳定的第三库被加入到了标准库里面,这里有6000多个第三方库的介绍 下表中加粗并 ...
- XShell本地上传文件到Ubuntu上及从Ubuntu下载文件到本地
使用XShell本地上传文件到Ubuntu上及从Ubuntu下载文件到本地. 1.第一种方法是最常用的 :如果下载了Xshell和Xftp,Ctrl+Alt+F就可以选择文件的互传了!(虚拟机/云服务 ...