http缓存机制分为两种:强制缓存和协商缓存。并且在不同刷新方式下和浏览器环境下对策略的采取会有所区别。
强制缓存
初次请求,服务器返回资源并在响应中携带cache-control头。
如果服务端觉得这个资源可以被缓存的话就加cache-control,如果服务端觉得这个资源没法被缓存或者不适合被缓存,则不加cache-control。
再次请求,先看本地缓存的时间判断是否靠谱,如果是的话就在本地找这个资源,不会请求服务器。
时间长了,缓存失效了,就去请求服务器,然后重新返回资源和cache-control
cache-control头可以设置如下字段:
- max-age:缓存的最大过期时间(秒)(常见)
- no-cache:不用强制缓存,交给服务端来处理,服务端怎么做不管 (常见)
- no-store:不用强制缓存,而且不用服务端的一些缓存措施,让服务端简单粗暴地将资源再翻过来就行 (不常见)
- private:只允许最终用户做缓存
- public:允许中间的一些路由 、代理做缓存
协商缓存
服务端来判断某个资源能不能用缓存的内容,如果能,304。 服务端判断客户端资源,是否和服务端资源一样(如果一样,没必要再来一份) 一致返回304,否则返回200和最新的资源
资源返回时,会携带Etag和Last-Modified两个响应头。
- Etag,资源的指纹标识
- Last-Modified,资源的最后修改时间
当再次请求此资源时,浏览器会在请求头中通过If-None-Match和If-Modified-Since将之前请求资源获得的这两个字段再次发给服务器,供服务器判断。
- If-None-Match:Etag
- If-Modified-Since:Last-Modified
二者都存在的时候优先采用Etag进行判断,因为Etag是根据文件内容来的,而Last-Modified只能精确到秒。如果资源被重复生成,而内容不变,则Etag更精确(如果资源一秒生成一次,一秒生成一次,内容不变,用Last-Modified去对比的话每次都会过期不断获取新的资源,Etag是根据内容计算的)
缓存策略
向服务器请求资源,首先看本地有没有缓存,如果有的话,先通过强制缓存(cache-control:max-age或者expires)判断本地缓存资源能否继续使用;如果能的话,直接冲本地读取资源即可;否则向服务器请求,服务器通过If-None-Match和If-Modified-Since请求头判断资源能否继续使用;如果能,响应中设置304状态码,继续使用本地资源,否则,重新返回服务器端的资源。
刷新页面对http缓存的影响
刷新页面可以分为三种:
- 正常操作 : 地址栏输入url ,跳转链接,前进后退等 ; 两种缓存都有效
- 手动刷新 : F5 ,点击刷新按钮,右击菜单刷新 ; 仅协商缓存有效
- 强制刷新 : ctrl +f5 cmd + r ; 两种缓存策略都无效,重新请求资源
实操
使用express框架编写一个简单的返回静态资源的服务端程序
// config.js 配置
const path = require('path');
module.exports = {
port: 9527, // 服务端口
root: process.cwd(), // 根目录
static: path.resolve(process.cwd(), "static"), // 静态资源目录
maxAge: 10, // 强制缓存时间
enableEtag: true, // 是否使用Etag进行判断
enableLastModified: true, // 是否使用Last-Modified进行判断
}
// cache.js 中间件,处理缓存相关
const {maxAge, enableEtag, enableLastModified, root, static} = require("../../config/config");
const path = require('path');
const etag = require('etag');
const fs = require('fs')
const mimes = { // MIME类型
css: 'text/css',
less: 'text/css',
gif: 'image/gif',
html: 'text/html',
ico: 'image/x-icon',
jpeg: 'image/jpeg',
jpg: 'image/jpeg',
js: 'text/javascript',
json: 'application/json',
pdf: 'application/pdf',
png: 'image/png',
svg: 'image/svg+xml',
swf: 'application/x-shockwave-flash',
tiff: 'image/tiff',
txt: 'text/plain',
wav: 'audio/x-wav',
wma: 'audio/x-ms-wma',
wmv: 'video/x-ms-wmv',
xml: 'text/xml',
}
function handleCache(req, res, next){
const extName = path.extname(req.url); // 取请求后缀名,没有就是空, 有就是 “.jpg”之类
if(mimes.hasOwnProperty(extName.slice(1))){
res.setHeader('Cache-Control', `public,max-age=${maxAge}`);
if(!enableEtag && !enableLastModified){
// 不使用协商缓存
res.statusCode = 200;
}
const {url, headers} = req; // req.url: /后面的内容
const filePath = path.join(static, url);
if (enableLastModified) {
// 使用last-modified进行协商缓存
const lastModified = headers['if-modified-since'];
const mtime = fs.statSync(filePath).mtime.toUTCString();
res.setHeader('Last-Modified', mtime);
res.statusCode = lastModified === mtime ? 304 : 200;
}
if (enableEtag) {
// 使用etag进行协商缓存
const reqEtag = headers['if-none-match'];
// 使用同步方法读取响应内容,计算 etag
const resEtag = etag(fs.readFileSync(filePath));
res.setHeader('ETag', resEtag);
res.statusCode = reqEtag === resEtag ? 304 : 200;
}
}
next();
}
module.exports = handleCache
// server.js 启动服务
const express = require('express');
const events = require('events');
const fs = require('fs');
const path = require('path');
const {port, static} = require('../config/config')
const app = express();
const route = express.Router();
const userRouter = require('./router/user');
const fileRouter = require('./router/file');
const cache = require('./cache/cache')
const bodyParser = require('body-parser')
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.all('*',function (req, res, next) { // 跨域
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Headers', 'Content-Type, Content-Length, Authorization, Accept, X-Requested-With');
res.header('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTIONS');
if (req.method === 'OPTIONS') {
res.send(200);
}
else {
next();
}
});
app.use(cache);
// 设置静态资源目录
app.use(express.static(static))
app.listen(port, () => {
console.log('server start');
})
在静态资源目录下随便放个什么文件,我就搞了个图片timg3.jpg
启动服务,在浏览器目录中输入http://localhost:9527/timg3.jpg, 可以获得图片。
第一次是200,并且可以获得缓存的相关响应头(红框)
在max-age规定的秒数内,再次请求图片,发现这里是直接使用本地的图片
点击浏览器刷新按钮,按照刷新规则,这里是只采用协商缓存,跳过强缓存。结果却是是采用了协商缓存,返回304。
如果细心一些会发现,此时浏览器发送的请求中带了一个Cache-Control头,其中将max-age设置为0,可就是说资源默认就是陈旧的,所以跳过了强缓存,直接使用协商缓存。
然后我研究了一下,发现这个情况在chrome和火狐中是相同的,刷新url设置max-age=0跳过强缓存。但是在IE(1909版本)中没有此现象,点击刷新按钮后没有设置max-age=0,强缓存依然生效,只有在max-age之后才进行协商缓存。