web缓存及其实现,附简单实现源码

661 阅读3分钟

概述

http缓存的实现方式有

1.Cache-Control/Expires。Cache-Control的Max-age属性设置的绝对过期时间,单位为秒。比如max-age:300,表示文件会缓存在本地300s之后过期.Expires代表的是绝对缓存时间,表示缓存在指定的时间点过期,格式为Expires:Thu,20 Apr 2019 20:00:00 GMT。这个时间是由服务器设置的,但本地浏览器和服务器之间可能存在时差,所以有时这个值不准确。当Cache-Control的max-age和expires同时存在时,max-age的优先级高。

2.Last-Modify/If-Modify-Since。这两个是成对存在的,代表文件最后的修改时间, Last-Modified 是由服务器往浏览器发送的 HTTP头,每次发送的值会写进浏览器的If-Modify-Since中。If-Modified-Since是由浏览器往服务器发送的头,浏览器每次请求服务器都会带上这个头。

3.Etag/If-None-Match.这也是成对存在,功能和(2)类似。不过在开发中往往是对服务器的文件求Hash,并把求出的hash值赋给Etag。当Etag和Last-Modify同时存在的时候服务端对两种都要校验。

4.Etag和Last-Modify对比。
(a)、服务器周期性动态生成的文件,他的内容并不改变,但修改时间变了这时我们应该用Etag (b)、对于修改非常频繁的文件,Etag可以检测到秒级以下,If-Modified-Since只能能检查到s级的。

5.Last-Modified,Etag,Expire 混合使用
(a)、通常 Last-Modified,Etag,Expire 是一起混合使用的,特别是 Last-Modified 和 Expire 经常一起使用,因为 Expire 可以让浏览器完全不发起 Http 请求,而当浏览器强制 F5 的时候又有 Last-Modified ,这样就很好的达到了浏览器段缓存的效果。
(b)、Etag 和 Expire 一起使用时,先判断 Expire ,如果已经过期,再发起 Http 请求,如果 Etag 也过期,则返回 200 响应。如果 Etag 没有过期则返回 304 响应。
(c)、Last-Modified,Etag,Expires 三个同时使用时。先判断 Expire ,然后发送 Http 请求,服务器先判断 last-modified ,再判断 Etag ,必须都没有过期,才能返回 304 响应

实现

上面说了那么一大堆,现在用node来实现一个Last-Modify/If-Modify-Since的缓存

一、实现一个简单服务器

const http = require('http');
const fs = require('fs');
const urlLib = require('url');

http.createServer((req, res) => {
 let { pathname } = urlLib.parse(req.url, true);
 if (pathname != '/index.html') {
    res.writeHead(302, { 'Location': 'http://localhost:9090/index.html' });
        res.end();
 } else {
  let rs = fs.createReadStream(`./${pathname}`);
  rs.pipe(res);
 }

}).listen(9090);
console.log('server start at http://localhost:9090/index.html')

可以看到无论怎么刷新返回的Status都是200,并且响应头中也没有If-Modify-Since这项

二、读取文件的修改时间并设置服务端Last-Modified

http.createServer((req, res) => {
    let { pathname } = urlLib.parse(req.url, true);
    if (pathname != '/index.html') {
        res.writeHead(302, { 'Location': 'http://localhost:9090/index.html' });
        res.end();
     } else {
  +     fs.stat(`./${pathname}`, (err, stat) => {
            if (err) {
                console.log('请在根目录下创建一个index.html文件');
            } else {
   +              if (req.headers['if-modified-since']) {
   +                 console.log(req.headers['if-modified-since'])
                } 
   +             res.setHeader('Last-Modified', stat.mtime.toGMTString());
                let rs = fs.createReadStream(`./${pathname}`);
                rs.pipe(res);
               
                }
           })
  }
}).listen(9090);

现在我们可以看到浏览器的请求头多了一个If-Modified-Since,且服务器可以获取到该值

三、用If-Modified-Since的值判断服务器文件是否过期

fs.stat(`./${pathname}`, (err, stat) => {
            if (err) {
                console.log('请在根目录下创建一个index.html文件');
            } else {
                if (req.headers['if-modified-since']) {
              +     let date = new Date(req.headers['if-modified-since']);
              +     let client_time = Math.floor(date.getTime() / 1000);
              +     let server_time = Math.floor(stat.mtime.getTime() / 1000);
              +      if (client_time < server_time) {
              +         sentFileToClient();
              +     } else {
              +         res.writeHead(304);
              +         res.write('Not Modified');
              +         res.end();
                    }
                } else {
                    sentFileToClient();
                }

                function sentFileToClient() {
                    res.setHeader('Last-Modified', stat.mtime.toGMTString());
                    let rs = fs.createReadStream(`./${pathname}`);
                    rs.pipe(res);
                }

            }

        })

我们可以看到第一次请求status是200,然后刷新都是304更改文件后status又变成了200
代码地址:github.com/fanxuewen/e…