缓存的进化阶段
1 无缓存
优点:简单
缺点:每次的请求资源服务器都返回原始文件,浪费带宽
2 有缓存无更新
第一次请求后,服务器发送完整的文件,浏览器将返回的完整文件存到本地缓存,下次再发送同意请求之后,就从本地获取。
优点:节省带宽
缺点:若服务器的文件更新,浏览器本地缓存任然无更新
3 缓存加上更新(GMT时间格式)
服务器在发送问响应的时候,在响应报文加上了额外的字段:过期时间如Expires: Mon,10 Dec 1990 02:25:22GMT
,告诉浏览器此次发送的资源什么时候过期,如果过期,需要重新给服务器发送请求。
优点:缓存可控
缺点:控制功能单一,这种格式更改本地时间后就相当于失效了
4 缓存控制
响应头附带额外信息Cache-Control: max-age=300;
字段可对应不同的值,使得缓存控制功能强大
- Public表示响应可被任何中间节点缓存:如 Browser <-- proxy1 <-- proxy2 <-- Server,中间的proxy可以缓存资源,比如下次再请求同一资源proxy1直接把自己缓存的东西给 Browser 而不再向proxy2要。
- Private表示中间节点不允许缓存:对于Browser <-- proxy1 <-- proxy2 <-- Server,proxy 会老老实实把Server 返回的数据发送给proxy1,自己不缓存任何数据。当下次Browser再次请求时proxy会做好请求转发而不是自作主张给自己缓存的数据。
- no-cache表示不使用: Cache-Control的缓存控制方式做前置验证,而是使用 Etag 或者Last-Modified字段来控制缓存
no-store ,真正的不缓存任何东西。浏览器会直接向服务器请求原始文件,并且请求中不附带 Etag 参数(服务器认为是新请求)。 - max-age,表示当前资源的有效时间,单位为秒。
缺点:如果资源时间上过期,但是服务器实际并未更新,服务器仍然会发送新的完整资源给浏览器,同样浪费带宽
所以资源过期后,先判断是否真的有更新,没有的话就只返回一个未更改
的消息就行了。
5 缓存控制的升级
响应头的格外信息中添加Etag
Etag
是 对资源的编码,如果资源在服务端未被修改,这个值就不会变
Cache-Control: max-age=300;
ETag:W/"e-cbxLFQW5zapn79tQwb/g6Q"
时间过期的时候,发送请求给服务器,附带上Etag
,服务器接收到请求后,先对Etag进行比对,不相等才发送更新的资源
与 ETag 类似功能是Last-Modified/If-Modified-Since
- 当资源过期时(max-age超时),发现资源具有Last-Modified声明,则再次向web服务器请求时带上头 If-Modified-Since,表示上次服务器告知的文件修改的时间,
- web服务器收到请求后发现有头If-Modified-Since 则与被请求资源的最后修改时间进行比对
- 若最后修改时间较新,说明资源又被改动过,则响应整片资源内容(200)
- 若最后修改时间较旧,说明资源无新修改,则响应HTTP 304 ,告知浏览器继续使用所保存的cache。
nodejs写一个简单的静态服务器
/* 一个最简单的静态服务器 */
/* 访问 http://localhost:8080/index.html */
const http = require('http')
const fs = require('fs')
const path = require('path')
http.createServer((req, res) => {
console.log(req)
fs.readFile(path.join(__dirname, req.url), (err, data) => {
if (err) {
res.writeHead(404, 'not found')
res.end('Oh, Not Found')
} else {
res.writeHead(200, 'OK')
res.end(data)
}
})
}).listen(8080)
console.log('Visit http://localhost:8080')
1 expires 与pragma : no-cache (http 1.0)
expires GMT
const http = require('http')
const fs = require('fs')
const path = require('path')
http.createServer((req, res) => {
let filePath = path.join(__dirname, req.url)
fs.readFile(filePath, (err, data) => {
if (err) {
res.writeHead(404, 'not found')
res.end('Oh, Not Found')
} else {
// 发送文件之前加设置响应头
// GTM 时间格式
let date = new Date(Date.now() + 1000 * 15).toGMTString()
res.setHeader('Expires', date)
// 同时写 no-cache优先级更高
//res.setHeader('Expires', 'Wed, 23 Jan 2019 07:40:51 GMT')
//res.setHeader('Pragma', 'no-cache')
res.writeHead(200, 'OK')
res.end(data)
}
})
}).listen(8080)
console.log('Visit http://localhost:8080')
开启服务器,响应头成功加上GMT字段
pragma : no-cache 不保存缓存
res.setHeader('Pragma', 'no-cache')
每次返回响应之后清除浏览器缓存,不会出现 form-memory或disk这种字段
no-cache的优先级要高
同时设置,仍然执行no-cache
2 Cache-Control
请求报文
- Cache-Control: max-age=10 //hi,(代理)服务器我想要
- Cache-Control: no-cache // hi,(代理)服务器,不要给我缓存的东西,我要新鲜完整的内容
- Cache-Control: no-store // hi,(代理)服务器, 这是机密请求,别缓存数据,给我最新的
响应报文
- Cache-Control: max-age=10 //hi,浏览器,把这个文件信息保存起来。当再次需要它时,如果是在10秒以内发起的请求则直接使用缓存(200, from memory cache),否则重新发起网络请求找我要(200)
- Cache-Control: no-cache // hi,浏览器(代理服务器),你可以缓存,但每次需要向我确认一下
- Cache-Control: no-store // hi,浏览器(代理服务器),这是机密信息,别缓存
max-age=10
const http = require('http')
const fs = require('fs')
const path = require('path')
http.createServer((req, res) => {
let filePath = path.join(__dirname, req.url)
fs.readFile(filePath, (err, data) => {
if (err) {
res.writeHead(404, 'not found')
res.end('Oh, Not Found')
} else {
// max-age=10 表示发出的资源期限是10s
res.setHeader('Cache-Control', 'max-age=10')
res.writeHead(200, 'OK')
res.end(data)
}
})
}).listen(8080)
console.log('Visit http://localhost:8080')
- 再次请求index时 浏览器会在请求头设置max-age: 0 告诉浏览器不要拿缓存,而是直接请求服务器拿新资源,而其他资源没有这个
- 如果浏览器不加这个字段,下次访问,index仍然走本地缓存,页面就无法更新
no-cache 再次请求不会再访问缓存
no-store 告诉浏览器,不再保存到缓存
直接进行浏览器和服务器之间的资源转换
no-cache 实际上是保存在本地缓存里的,只是不访问
3 Last-Modified 字段对缓存的控制
完整步骤:
- 第一次请求,服务器获取文件修改时间,并设置在响应头中
- 后几次请求,比较新旧文件修改时间,不同则修改,相同则返回状态码304.
const http = require('http')
const fs = require('fs')
const path = require('path')
http.createServer((req, res) => {
let filePath = path.join(__dirname, req.url)
fs.readFile(filePath, (err, data) => {
if (err) {
res.writeHead(404, 'not found')
res.end('Oh, Not Found')
} else {
// 获取文件的修改时间
let mtime = Date.parse(fs.statSync(filePath).mtime)
//10秒内,浏览器直接从自己本地拿,10秒后找服务器要。如果没修改,告诉浏览器没修改就行,如果修改了,给浏览器最新的
res.setHeader('Cache-Control', 'max-age=10')
// 第一次访问:服务器判断请求头上有没有字段,没有就加上字段Last-Modified 代表修改时间
if (!req.headers['if-modified-since']) {
res.setHeader('Last-Modified', new Date(mtime).toGMTString())
res.writeHead(200, 'OK')
res.end(data)
} else { // 后几次访问,
let oldMtime = Date.parse(req.headers['if-modified-since'])
if (mtime > oldMtime) { // 判断最新的修改时间大于上一次修改时间,表示这个文件已经被修改过了,要重新发一次新文件
res.setHeader('Last-Modified', new Date(mtime).toGMTString())
res.writeHead(200, 'OK')
res.end(data)
} else { // 否则就不修改
res.writeHead(304)
res.end()
}
}
}
})
}).listen(8080)
console.log('Visit http://localhost:8080')
拆分步骤
1 获取文件修改时间,并设置到响应头
// 步骤一:设置文件修改时间
let stat = fs.statSync(filePath)
console.log(stat.mtime)
let modifyDate = new Date(Date.parse(stat.mtime)).toGMTString()
console.log(modifyDate)
res.setHeader('Cache-control', 'no-cache')
res.setHeader('Last-Modified', modifyDate)
res.writeHead(200, 'OK')
res.end(data)
现在修改下文件
文件最后修改时间就更新了
2 初次请求时设置字段,之后的请求只需对比文件的修改时间
console.log(req.headers)
let mtime = Date.parse(fs.statSync(filePath).mtime)
if (!req.headers['if-modified-since']) {
res.setHeader('Cache-control', 'no-cache')
res.setHeader('Last-Modified', new Date(mtime).toGMTString())
res.writeHead(200, 'OK')
res.end(data)
} else {
let oldMtime = Date.parse(req.headers['if-modified-since'])
if (mtime > oldMtime) {
res.setHeader('Last-Modified', new Date(mtime).toGMTString())
res.writeHead(200, 'OK')
res.end(data)
} else {
res.writeHead(304)
res.end()
}
}
首次请求先关掉缓存,便于测试
之后的请求,文件并未修改,返回304,并且response里仍然有响应体,这就是从缓存中拿到的
单独修改css文件之后,只有css返回200,表示已重新请求响应
存在的问题
- 缓存全由浏览器控制,因为加了字段 cache-control:no-cache
- 浏览器无法控制缓存
面试问题
cache-control:no-cache
LastModified: GMT时间格式
服务器完全控制缓存,服务起来判断给不给浏览器更新页面
4 Etag字段对缓存的控制
- 第一次请求设置etag字段:字段里存放的是文件的md5值(通过base64展示)
- 每次文件修改,对应md5值也将修改
- 所以对比每次的md5值相同时,就返回304,不相同才请求到服务器,更新文件,并更换新文件的md5值
const http = require('http')
const fs = require('fs')
const path = require('path')
const crypto = require('crypto')
http.createServer((req, res) => {
let filePath = path.join(__dirname, req.url)
fs.readFile(filePath, (err, data) => {
if (err) {
res.writeHead(404, 'not found')
res.end('Oh, Not Found')
} else {
console.log(req.headers['if-none-match'])
let oldEtag = req.headers['if-none-match']
if (!oldEtag) {
let md5 = crypto.createHash('md5')
// 得到内容的md5值,每次文件内容改变之后,md5值一定会改变
console.log(md5.update(data).digest('base64'))
res.setHeader('Etag', md5.update(data).digest('base64'))
res.writeHead(200, 'OK')
res.end(data)
} else {
let newEtag = crypto.createHash('md5').update(data).digest('base64')
if (oldEtag !== newEtag) {
// 更换掉新的tag值
res.setHeader('Etag', newEtag)
res.writeHead(200, 'OK')
res.end(data)
} else {
res.writeHead(304)
res.end()
}
}
}
})
}).listen(8080)
console.log('Visit http://localhost:8080')
修改css文件之后