express-强缓存与协商缓存
静态资源的强缓存及协商缓存
强缓存 200-from-cache
强缓存是利用Expires或者Cache-Control实现的,它们都表示资源在客户端缓存的有效时间Expires是http1.0提出的一个表示资源过期时间的header,描述的是一个绝对时间由服务器返回,GMT格式的字符串表示,如:Expires:Thu, 31 Dec 2037 23:55:55 GMT,当浏览器对某个资源的请求命中了强缓存时,返回的http状态200,chrome f12查看network中size会显示为 from cache。
Expires缓存过程
- 浏览器第一次跟服务器请求一个资源,服务器在返回这个资源的同时,在respone的header加上Expires的header。
- 浏览器在接收到这个资源后,会把这个资源连同所有response header一起缓存下来。
- 浏览器再请求这个资源时,先从缓存中寻找,找到这个资源后,拿出它的Expires跟当前的请求时间比较,如果请求时间在Expires指定的时间之前,命中缓存。
- 如果缓存没有命中,浏览器直接从服务器加载资源时,Expires Header在重新加载的时候会被更新。
Cache-Control缓存过程
- 浏览器第一次跟服务器请求一个资源,服务器在返回这个资源的同时,在respone的header加上Cache-Control的header。
- 浏览器在接收到这个资源后,会把这个资源连同所有response header一起缓存下来。
- 浏览器再请求这个资源时,先从缓存中寻找,找到这个资源后,根据它第一次的请求时间和Cache-Control设定的有效期,计算出一个资源过期时间,再拿这个过期时间跟当前的请求时间比较,如果请求时间在过期时间之前,就能命中缓存。
Expires与Cache-Control 区别
Expires是一个较老的强缓存管理的header,由于它是服务器返回的一个绝对时间,如果服务器时间与客户端时间相差较大时,缓存管理容易出现问题。如:修改本地时间可以影响缓存的命中。而Cache-Control描述的是一个相对时间(max-age),在缓存命中的时候,都是利用客户端的时间进行判断,相对较Expires有效可靠一些。
这两个header可以只启用一个,或者可以同时启用,如果同时启用优先使用Cache-Control。
协商缓存 304-not-modified
当浏览器对某个资源的请求没有命中强缓存,就就发一个请求到服务器,验证协商缓存是否命中,如果协商缓存命中,响应返回的http状态码304并且会显示一个Not Modified的字符串。
协商缓存是利用的是 【Last-Modified,If-Modified-Since】和 【ETag,If-None-Match】这两对Header来管理的。
【Last-Modified,If-Modified-Since】缓存过程
- 浏览器第一次跟服务器请求一个资源,服务器在返回这个资源的同时,在respone的header加上Last-Modified的header,这个header表示这个资源在服务器上的最后修改时间。
- 浏览器再次跟服务器请求这个资源时,在request的header上加上If-Modified-Since的header,这个header的值就是上一次请求时返回的Last-Modified的值。如果没有变化则返回304 Not Modified,但是不会返回资源内容。
- 浏览器收到304的响应后,资源内容直接从本地请求。
- 如果协商缓存没有命中,浏览器直接从服务器加载资源时,Last-Modified Header在重新加载的时候会被更新。
【ETag,If-None-Match】缓存过程
- 浏览器第一次跟服务器请求一个资源,服务器在返回这个资源的同时,在respone的header加上ETag的header,这个header是服务器根据当前请求的资源生成的一个唯一标识,这个唯一标识是一个字符串,只要资源有变化这个串就不同,跟最后修改时间没有关系。
- 浏览器再次跟服务器请求这个资源时,在request的header上加上If-None-Match的header,这个header的值就是上一次请求时返回的ETag的值。
- 服务器再次收到资源请求时,根据浏览器传过来If-None-Match和然后再根据资源生成一个新的ETag,如果这两个值相同就说明资源没有变化,否则就是有变化;如果没有变化则返回304 Not Modified,但是不会返回资源内容;如果有变化,就正常返回资源内容。
- 浏览器收到304的响应后,就会从缓存中加载资源。
【Last-Modified,If-Modified-Since】与【ETag,If-None-Match】区别
【Last-Modified,If-Modified-Since】都是根据服务器时间返回的header,一般来说,在没有调整服务器时间和篡改客户端缓存的情况下,这两个header配合起来管理协商缓存是非常可靠的,但是有时候也会服务器上资源其实有变化,但是最后修改时间却没有变化的情况,而当这种情况出现的时候,就会影响协商缓存的可靠性。【ETag,If-None-Match】则是根据请求的资源作为依据可靠性会好一些。
express.static
4.x 版本 Express已经不再依赖Connect,除了express.static,Express以前内置的中间件现在已经全部单独作为模块安装使用。express.static 是Express唯一内置的中间件,用于托管静态资源。
// root提供静态资源的根目录 使用方法: express.static(root,[options]) options属性如下:
- dotfiles 是否对外输出文件名以(.)开头的文件 默认值:’ignore’, 其他可选值:’allow’、’deny’
- etag 是否启用etag生成 默认值:true
- extensions 设置扩展名备份选项 默认值:[]
- index 发送目录索引文件,设置为false禁用 默认值:”index.html”
- lastModified 设置Last-Modified 头文件在操作系统上的最后修改日期 默认值:true
- maxAge 以毫秒或者其字符串格式设置 Cache-Control头的max-age属性 默认值:0
- redirect 当路径为目录时,重定向至”/”
-
setHeaders 设置HTTP头已提供文件的函数
var express = require('express'); var app = express(); app.get('/expires-p', function (req, res) { res.sendFile(__dirname + '/expires.html') }); app.get('/cache-p',function(req,res){ res.sendFile(__dirname + '/cache.html') }); app.get('/etag-p',function(req,res){ res.sendFile(__dirname + '/etag.html') }); app.get('/modified-p',function(req,res){ res.sendFile(__dirname + '/modified.html') }); app.use(express.static('expires', { dotfiles: 'ignore', etag: false, extensions: ['html', 'htm', 'css', 'png', 'gif', 'jpg', 'js', 'tpl'], index: 'index.html', lastModified: false, maxAge: 0, redirect: true, setHeaders: function (res,path,stat) { res.setHeader('Expires', new Date(Date.now() + 345600000).toUTCString()); } })); app.use(express.static('cache-control', { dotfiles: 'ignore', etag: false, extensions: ['html', 'htm', 'css', 'png', 'gif', 'jpg', 'js', 'tpl'], index: 'index.html', lastModified: false, maxAge: '10000', redirect: true })); app.use(express.static('etag', { dotfiles: 'ignore', etag: true, extensions: ['html', 'htm', 'css', 'png', 'gif', 'jpg', 'js', 'tpl'], index: 'index.html', lastModified: false, maxAge: 0, redirect: true })); app.use(express.static('modified', { dotfiles: 'ignore', etag: false, extensions: ['html', 'htm', 'css', 'png', 'gif', 'jpg', 'js', 'tpl'], index: 'index.html', lastModified: true, maxAge: 0, redirect: true })); var server = app.listen(3000, '', function () { var host = server.address().address; var port = server.address().port; });