1.按缓存位置分类
按缓存位置分类,可以分为:Memory Cache(内存缓存),Disk Cache(磁盘缓存)
1.1Memory Cache(内存缓存)
memory cache 叫内存缓存,内存缓存具有两个特点,分别是快速读取和时效性:
- 快速读取:内存缓存会将编译解析后的文件,直接存入该进程的内存中,占据该进程一定的内存资源,以方便下次运行使用时的快速读取
- 时效性:一旦该进程关闭(网页关闭),则该进程的内存则会清空。
在浏览器中,浏览器会在js和图片等文件解析执行后直接存入内存缓存中,那么当刷新页面时只需直接从内存缓存中读取(from memory cache);当然内存缓存是有存储限制的,一旦超过限制,则超过的数据会从磁盘缓存获取。
1.2Disk Cache (磁盘缓存)
disk cache 也叫 磁盘缓存。它允许相同的资源在跨会话,甚至跨站点的情况下使用,例如两个站点都使用了同一张图片。
该机制的作用是:当一个页面打开发送请求时,(磁盘缓存) 会严格根据 HTTP 头部中的各类字段来进行缓存。当命中缓存之后,浏览器会从硬盘中读取资源,虽然比起从内存中读取慢了一些,但比起网络请求还是快了不少的。
凡是持久性存储都会面临容量增长的问题,disk cache 也不例外。浏览器会根据自身算法自动清理“最老的”或者“最可能过时的”资源。
1.3查找资源的优先级
memory cache(内存缓存) ---------> disk cache(磁盘缓存) ---------> 发送请求
1.5如何查看数据是否来自缓存?
使用google浏览器
内存缓存
磁盘缓存
这是强缓存
这是协商缓存
,状态码为304
2.http缓存
浏览器有个缓存数据库,当请求的数据被缓存过,那么请求的数据直接从缓存数据库拿取。
缓存类型分为强缓存
与协商缓存
2.1强缓存
浏览器第一次请求的数据被服务器设置成有强缓存时间的数据,那么浏览器第二次请求的时候,如果时间未过期,那么请求将不会发送到服务器,数据直接从浏览器缓存数据库获取。(从google浏览器可以看到是from disk cache)
2.1.1设置的方法:
响应头中设置Expires
Expires: Thu, 10 Nov 2017 08:45:11 GMT // gmt 绝对时间
与响应头中的date时间进行对比,如果Expires(本地浏览器时间) < date(服务器时间),则过期。
未过期则不需要发送请求。
但是本地浏览器的时间与服务器的时间有很大的误差,通常不准。
响应头中设置 Cache-control
Cache-Control: max-age=2592000
与响应头中的date时间进行对比,在date的max-age秒后,强缓存将失效。
但在该时间内,客户端不需要向服务器发送请求。
Cache-Control: no-cache
可以在本地和代理服务器缓存,但是这个缓存需要服务器验证才可以使用,即直接进入协商缓存阶段。
Cache-Control: no-store
真正意义上的“不要缓存”,不进行任何形式的缓存,每次都从服务器获取
Cache-Control: private
客户端可以缓存,代理服务器不能缓存。例如,用户的浏览器可以缓存包含用户私人信息的HTML网页,但CDN不能缓存
Cache-Control: public
客户端和代理服务器都可以缓存
Cache-Control: must-revalidate
must-revalidate告诉缓存,在事先没有跟原始服务器进行再验证的情况下,不能提供这个对象的陈旧副本,
缓存仍然可以随意提供新鲜的副本。
如果在缓存进行must-revalidate新鲜度检查时,原始服务器不可用,缓存就必须返回一条504错误。
Cache-Control: s-maxage
s-maxage是针对代理服务器的缓存时间
同时设置cache-control 跟 Expires
如果 cache-control 跟 Expires 同时存在,Expires 会被 cache-control 覆盖 。 http1.1已经把它抛弃了,基本没用了。
2.1.2实践代码
const express = require('express');
const app = express();
const port = 8080;
const fs = require('fs');
const path = require('path');
const moment = require('moment');
app.get('/',(req,res) => {res.send(`<!DOCTYPE html><html lang="en"><head><title>Document</title></head><body>Http Cache Demo<input type='button' onclick='getData()' value='获取数据'><script src="/demo.js"></script><script >function getData(){const xhr = new XMLHttpRequest()xhr.onreadystatechange = () => {if (xhr.readystate == 4) {if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {alert(xhr.responseText)} else {alert("Request was unsuccessful: " + xhr.status)}}}xhr.open("get", "/demo1.js", true)xhr.send(null)}</script></body></html>`)})function getGLNZ(){ return moment().utc().add(2,'m').format('ddd, DD MMM YYYY HH:mm:ss')+' GMT';}
app.get('/demo1.js',(req, res)=>{ let jsPath = path.resolve(__dirname,'./static/js/demo1.js'); let cont = fs.readFileSync(jsPath); res.setHeader('Cache-Control', 'public') res.setHeader('Cache-Control', 'max-age=31536000') console.log(123) res.end(cont)})
app.listen(port,()=>{ console.log(`listen on ${port}`)})
结果:
2.2协商缓存
网页发送资源请求时,发现强缓存的时间过期了,但服务器的资源并没有发生改变
,此时服务器会下发状态码为304的响应请求,告诉浏览器还是从磁盘缓存中获取数据,这就是协商缓存。
2.2.1强缓存与协商缓存的工作流程
当强缓存的时间过期
, 该请求会从缓存的请求里面找到Etag的值
,如果Etag的值存在,则将该值赋给请求头的If-None-Match
。当请求发送到服务器时,服务器会取出If-None-Match的值,并与服务器文件的Etag进行比较,如果一致,则响应304状态码,告诉浏览器继续使用缓存。如果不一致,则返回200状态码,并更新Etag的值。
如果Etag的值不存在,则从缓存的请求里面找到Last-Modified的值
,则将该值赋给请求头的If-Modifed-Since
,当请求发送到服务器时,服务器会取出If-Modifed-Since的值并于服务器文件的最后修改时间进行比对,如果一致,则响应304状态码,告诉浏览器继续使用缓存。如果不一致,则返回200状态码,并更新Last-Modified的值。
2.2.2实际代码
const express = require('express');
const app = express();
const port = 8080;
const fs = require('fs');
const path = require('path');
const moment = require('moment');
app.get('/',(req,res) => {res.send(`<!DOCTYPE html><html lang="en"><head><title>Document</title></head><body>Http Cache Demo<input type='button' onclick='getData()' value='获取数据'><script src="/demo.js"></script><script >function getData(){const xhr = new XMLHttpRequest()xhr.onreadystatechange = () => {if (xhr.readystate == 4) {if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {alert(xhr.responseText)} else {alert("Request was unsuccessful: " + xhr.status)}}}xhr.open("get", "/demo2.js", true)xhr.send(null)}</script></body></html>`)})function getGLNZ(){return moment().utc().add(2,'m').format('ddd, DD MMM YYYY HH:mm:ss')+' GMT';}app.get('/demo2.js',(req, res)=>{ let jsPath = path.resolve(__dirname,'./static/js/demo1.js');
let cont = fs.readFileSync(jsPath);
let status = fs.statSync(jsPath)
let lastModified = status.mtime.toUTCString()
if(lastModified === req.headers['if-modified-since']){ res.writeHead(304, 'Not Modified') res.end() }
else { res.setHeader('Cache-Control', 'public') res.setHeader('Cache-Control', 'max-age=5') res.setHeader('Last-Modified', lastModified) res.writeHead(200, 'OK') res.end(cont)}})
app.listen(port,()=>{console.log(`listen on ${port}`)})
3.更新和废弃缓存的方法
将资源文件更改成hash名称,迫使请求路径发生变化,从而开始下载新的资源。
4.缓存应用
不常变化的资源
Cache-Control: max-age=31536000
经常变化的资源
Cache-Control: no-cache