当前位置: 首页 > 工具软件 > last-blog > 使用案例 >

关于HTTP缓存验证Last-Modified和Etag的使用

涂浩皛
2023-12-01

关于资源验证

  • 如果给Cache-Control设置no-cache之后,每次浏览器发起对设置了Cache-Control的资源请求的时候
  • 都要去到服务器端进行一个资源的验证, 验证如果资源可以使用,那么才会读取本地的缓存

缓存验证流程

  • 1 ) 浏览器创建了一个请求, 首先请求到达的地方是在本地缓存, 当然是建立在有Cache-Control头的情况下
    • 如果在本地缓存里查找,如果找到,则直接返回给浏览器渲染页面
    • 这种情况下, 不会经过任何网络的传输,也就是from memory cache的效果
  • 2 ) 如果没有找到,就会去网络请求,在网络请求中,如果经过代理服务器(代理缓存)
    • 那么首先会在代理服务器上查找相关缓存信息, 如果找到就会返回给客户端
    • 经过本地缓存,再返回给浏览器渲染页面
  • 3 ) 如果在代理服务器上没有找到该缓存信息,那么就直接去源服务器上查找资源
    • 获取了新的内容之后,再逐级向下进行返回和二次缓存
    • 最终返回给浏览器进行页面的渲染

如何进行缓存验证

  • 在HTTP里主要有两个验证的HTTP头:Last-Modified, Etag
  • Last-Modified: 上次修改时间
    • 也就是说给这个资源设置了上次是什么时间修改的
    • 主要配合If-Modified-Since或If-Unmodified-Since使用
    • 如果请求了一个资源,其返回头信息中有Last-Modified字段,其指定了一个时间
    • 在下一次浏览器发起请求的时候,就会带上这个Last-Modified字段信息
    • 通过If-Modified-Since或If-Unmodified-Since
    • 通常浏览器的实现会选择If-Modified-Since
    • 在请求中将其带到服务器上, 服务器就可以读取头信息中的If-Modified-Since对比该资源上次修改时间
    • 如果时间一样,那么代表资源没有被重新修改过, 服务器就可以告诉浏览器可以直接使用缓存的信息
    • 这就是对比上次修改时间以验证资源能否使用缓存,是否需要更新的过程
  • Etag: 是更加严格的一个验证
    • 其验证主要是通过数据签名:对资源内容产生一个唯一的签名
    • 如果资源数据进行了修改, 签名就会变成一个新的,任何修改就会造成两次签名不一样
    • 最典型的方法是对资源内容进行哈希计算, 得到一个唯一值作为签名来标记这个资源
    • 下一次浏览器发起请求的时候,就会带上If-Match或If-Non-Match字段,其值就是服务端返回的Etag值
    • 对比资源签名,判断是否使用缓存

程序示例

  • test.html

    <script src="/script.js"></script>
    
  • server.js

    const http = require('http');
    const fs = require('fs');
    
    http.createServer((req, res) => {
        console.log('req come: ', req.url);
        if(req.url === '/') {
            const html = fs.readFileSync('test.html', 'utf8');
            // 默认是下面这个writeHead设置,不写也可以
            res.writeHead(200, {
                'Content-Type': 'text/html'
            });
            res.end(html);
        }
        if(req.url === '/script.js') {
            res.writeHead(200, {
                'Content-Type': 'text/javascript',
                'Cache-Control': 'max-age=2000000000, no-cache', // 设置一个较长的值和no-cache
                'Last-Modified': '12345',
                'Etag': '666'
            });
            res.end("alert('script loaded')");
        }
    }).listen(8000, () => {
        console.log('server is running on port 8000');
    });
    
  • 启动程序:$ node server.js

  • 访问:http://localhost:8000/

  • 检查浏览器关于script.js中返回的头信息:Cache-Control, Last-Modified, Etag测试成功

  • 刷新浏览器,通过面板检查发现这个script.js还是发送了请求,并没有从memery cache中读取

  • 而且它的Request Headers请求头上携带了If-Modified-Since:12345, If-Non-Match:666这样的信息

  • 这就是在服务器设置了Cache-Control:no-cache, 'Last-Modified': '12345', 'Etag': '666'

  • 浏览器在下次发送请求的时候把对应的验证缓存的头信息给带上, 以验证是否需要更新的策略

  • 但是我们发现浏览器的Response是有内容的, 而且我们知道内容并没有任何的更改

  • 这时候我们当然希望服务器不需要返回实际的内容,而是直接从缓存中读取信息

  • 为了更好解释这一策略,我们稍微修改下程序

    if(req.url === '/script.js') {
        // 通用字段的设置
        let ct = 'text/javascript';
        let ccv = 'max-age=2000000000, no-cache'; // 设置一个很长的时间, 同时设置no-cache
        let lm = '12345'; // 先随便设置一个值, 测试浏览器返回头中是否携带
        let et = '666'; // 同上
        // 读取请求头信息的判断返回内容
        const etag = req.getHeader['if-none-match'];
        // 根据请求信息判断返回内容
        if(etag === '777') {
            // 此条件没有修改,使用缓存设置
            res.writeHead(304, {
                'Content-Type': ct,
                'Cache-Control': ccv,
                'Last-Modified': lm,
                'Etag': et
            });
            res.end(""); // 不更新任何东西, 即使返回任何东西,此处都不会真正起作用(不会返回给浏览器客户端),因为设置了304的HTTP Code
        } else {
            // 更新
            res.writeHead(200, {
                'Content-Type': ct,
                'Cache-Control': ccv,
                'Last-Modified': lm,
                'Etag': et
            });
            res.end("alert('script loaded')"); // 返回js脚本内容
        }
    }
    
  • 修改完程序,重启服务,刷新浏览器一次进行程序的初始化,当第二次刷新浏览器时(也就是再次请求服务端时)

  • 关于script.js的请求信息中的状态码就变成了304 Not Modified

  • 而且发送请求头上的信息有If-Modified-SinceIf-Non-Match字段

  • 而且其Response还是之前的内容,和服务端程序中res.end("")的设置无关(即便设置res.end("xxxyyyzzz")也还是原来的内容)

  • 如果在浏览器检查面板上勾选上Disable cache, 那么在Request Headers请求头上不会发送任何缓存验证相关的头信息了

  • 这是Chrome浏览器Disable cache的作用

扩展

  • 1 ) 同样的,如果去除了no-cache的Cache-Control设置
    • 经过重启服务,清理浏览器缓存,刷新浏览器等一系列初始化的工作
    • 当再次刷新浏览器时, script.js文件就会直接从memory cache中读取(即:from memory cache)
  • 2 ) 如果设置了no-store的Cache-Control
    • 即:'Cache-Control': no-store
    • 它会忽略任何和缓存相关的设置,每次访问都是200, 直接从服务器端请求最新的数据
  • 3 ) 至于服务端如何真正做缓存验证,这就涉及到服务端设计的问题了,不涉及HTTP的内容了,此处不再详解
    • 关于Last-Modified这个时间字段
      • 可以使用文件本身的属性,如上次修改时间属性
      • 如果是存储在服务器中的资源,可以通过设置一个字段,如update_at用于标识和更新即可
    • 关于Etag
      • 每次从服务端读取完数据之后, 都做一个签名以及和接收到请求的签名进行对比,这是最简单的方式
      • 当然还会有其他更好的方式来提升服务器的性能, 看你怎么设计了
 类似资料: