http 缓存学习

231 阅读4分钟

分类

网站缓存按照存放的地点不同,可以分为客户端缓存、服务端缓存。

客户端有包括一下两种:

  1. 浏览器缓存
  2. 代理服务器缓存

1.客户端缓存

1.1 浏览器缓存

是最靠近用户的缓存,如果启用缓存,用户在访问同一个页面时,将不再从服务器下载页面,而是从本机的缓存目录中读取页面,然后再浏览器中展现这个页面。

浏览器缓存的控制,可以设置meta标签,可以设置数字,也可以设置时间,如下:

<Meta http-equiv=”ExpiresContent=”3600″>
<Meta http-equiv=”Expires” Content=”Wed, 26 Feb 1997 08:21:57 GMT”>
HTTP头信息如下:
HTTP/1.1 200 OK
Date: Fri, 30 Oct 1998 13:19:41 GMT
Server: Apache/1.3.3 (Unix)
Cache-Control: max-age=3600, must-revalidate
Expires: Fri, 30 Oct 1998 14:19:41 GMT
Last-Modified: Mon, 29 Jun 1998 02:28:12 GMT

缓存流程

  1. 先读取内存缓存信息
  2. 读取硬盘缓存信息
  3. 重新发起网络请求,获取信息

常见的状态码

状态码描述
200 from memory cache状态码是灰色的,从内存中读取之前已经加载过的资源,不请求服务器,页面关闭时,资源就会被内存释放,再次打开相同页面不会出现此类情况,在同一页面刷新才会出现。一般脚本、字体、图片会存在内存当中
200 from disk cache状态码是灰色的,从磁盘中读取之前已经加载过的资源,不请求服务器,页面关闭不会被释放,这部分资源存在电脑磁盘里,只有用户手动清除浏览器缓存的时候才会释放。一般非脚本会存在内存当中,如css等
200从服务器下载最新资源
304访问服务器,发现资源没有更新,使用本地资源。

缓存策略

1.强缓存
http 1.0
//不请求服务器,直接返回200 
//通过  expires: Thu, 03 Jan 2019 11:43:04 GMT
//只要没有超过日期都取本地,
//存在问题 可能客户端日期手动调整,导致日期判断错误
 res.setHeader('expires', 'Thu, 03 Jan 2019 11:43:04 GMT') 
http 1.1
//使用 cache-control : 'max-age=5' 
//相当于5秒后过期
 res.setHeader('Cache-Control', 'max-age=5') 
2.协商缓存
cache-control 参数设置
响应指令
key描述
public完全缓存(客户端+ 代理服务器) 即客户端可以永久缓存
private只有客户端缓存(默认) 即客户端可以永久缓存
no-cache使用协商缓存
max-age=xxxxxx秒后失效
no-store完全不缓存
协商的方式
key描述
last-modified比较上次时间
etag通过页面生成hash摘要对比

1.last-modified + if-modified-since使用

//每次页面返回都设置 res.setHeader('last-modified', new Date().toUTCString())
//通过 req.headers['if-modified-since'] 获取上次的时间与当前系统比较
//没有超过则返回 340 命中
// 过期返回数据 + 200
res.setHeader('Cache-Control', 'no-cache')
res.setHeader('last-modified', new Date().toUTCString())
//如果在3秒内命中
if (new Date(req.headers['if-modified-since']).getTime() + 3 * 1000 > Date.now()) {
    console.log('协商缓存命中....')
    res.statusCode = 304
    res.end()
    return
}
  1. etag + If-None-Match 使用
//每次页面返回都设置 res.setHeader('Etag',hash)
//通过 req.headers['If-None-Match'] 获取上次的Etag比较 是否一样
//没有超过则返回 340 命中
// 过期返回数据 + 200
res.setHeader('Cache-Control','no-cache')
//生成摘要
const crypto =  require('crypto')
const hash = crypto.createHash('sha1').update(content).digest('hex')
//返回给客户端保存的指纹
res.setHeader('Etag',hash)

if(req.headers['If-None-Match'] === hash){ //对比摘要
    console.log('协商缓存命中....')
    res.statusCode = 304
    res.end()
    return
}

扩展知识:

'pragma':'no-cache' 
'Cache-Control','no-cache'
//意思一样 pragma是http1.0历史产物

完整 学习例子

const http = require('http')
function updateTime() {
    this.timmer = this.timmer || setInterval(() => this.time = new Date().toUTCString(), 4 * 1000) //没4秒刷新一次内容
    return this.time
}
http.createServer((req, res) => {
    const { url } = req
    if ('/' === url) {
        res.end(`
            <html>
                <!-- <meta http-equiv="Refresh" content="5" /> -->
                Html Update Time: ${updateTime()}
                <script src='main.js'></script>
               
            </html>
            `)
    } else if (url === '/main.js') {
        const content = `document.writeln('<br>JS 11  Update Time:${updateTime()}')`

        //  强缓存(1) Expires 浏览器提示:200 from memory cache
        // res.setHeader('Expires', new Date(Date.now() + 4 * 1000).toUTCString()) 
         // 强缓存(2) Cache-Control  浏览器提示:200 from memory cache
        // res.setHeader('Cache-Control', 'max-age=5')  

        // 协商缓存 Last Modified 浏览器提示:304  
        // res.setHeader('Cache-Control', 'no-cache')
        // res.setHeader('last-modified', new Date().toUTCString())
        // if (new Date(req.headers['if-modified-since']).getTime() + 3 * 1000 > Date.now()) {
        //     console.log('协商缓存命中....')
        //     res.statusCode = 304
        //     res.end()
        //     return
        // }

        // 协商缓存 Etag If-None-Match
        res.setHeader('Cache-Control', 'no-cache')
        const crypto = require('crypto');
        const hash = crypto.createHash('sha1').update(content).digest('hex')
        console.log('hash',hash)
        res.setHeader('Etag', hash)
        if(req.headers['if-none-match'] === hash ){
            console.log('Etag协商缓存命中.....')
            res.statusCode = 304
            res.end()
            return 
        }

        res.statusCode = 200
        res.end(content)
    } else if (url === '/favicon.ico') {
        res.setHeader('Cache-Control', 'max-age=100')
        res.end('')
    }
})
.listen(3000, () => {
    console.log('Http Cache Test at:' + 3000)
})

同理ajax 跟<script src='main.js'></script> 引用采用一样的缓存策略

if ('/' === url) {
    res.end(`
        <html>
            <!-- <meta http-equiv="Refresh" content="5" /> -->
            Html Update Time: ${updateTime()}
            <script src='main.js'></script>
            //新增ajax测试
            <script>
            let xhr = new XMLHttpRequest()
            xhr.onreadystatechange = () => {
                if (xhr.readyState==4){
                    console.log('request ' + xhr.status + ' ' +xhr.responseText)                 
                }
            }

            setInterval(() => {
                xhr.open('GET', 'http://127.0.0.1:3000/main.js', true);
                xhr.send()
            },1000)

        </script>

        </html>
        `)
   }

1.2.网关或代理服务器缓存

网关或代理服务器缓存是将网页缓存中网关服务器上,多用户访问同一个页面时,将直接从网关服务器把页面传送给用户。

不过现在的网站为了保证用户访问到最新的内容,一般很少采用浏览器缓存,取而代之的是更加灵活的服务器缓存。

2.服务端缓存

服务端缓存分为:页面缓存、数据缓存、数据库缓存

3.页面缓存

缓存好处

  • 提高加载速度
  • 减轻服务器负担
  • 减少客户端请求网络的流量

例子

nginx项目为什么发布后,用户访问项目会提示资源找不到?

  1. 由于使用现代脚手架打包的项目一般都会js和css都会带上xxxx+hash.js,xxxx+hash.css。资源文件一般没有问题,
  2. 主要问题再index.html。因为访问的地址一般是固定不变的,浏览器就会进行缓存策略。
  3. 如果nginx默认没有配置,就会使用默认的缓存策略 nginx配置代码
server {
        listen       80;
        server_name  域名;
        root   文件目录;
        index  index.html;

        location / {  # 不加这一句,会出现nginx欢迎页面,无法正确加载资源 
          try_files $uri /index.html;
        } 
        location ~ .*.(html)$ {  # 对html文件限制缓存
          add_header Cache-Control no-store;  # 不缓存 或者no-cache 协商缓存
        }
}