代码实操-http缓存

502 阅读5分钟

一、http缓存分类

分为2类,一类是强缓存,一类是协商缓存

实现强缓存的方式有2种:一种是在响应头里设置expires字段,一种是在响应头里设置Cache-Control字段。

相应的,实现协商缓存的方式也有2种:一种是在响应头里设置last-modified字段,一种是在响应头里设置tag字段。

接下来我们进行代码实操。

二、搭建node项目环境

按次序执行下面命令:

    1、随便建一个文件夹(命名:koa-project),在文件夹下输入:npm init -y
    2、npm install koa koa-router nodemon

在该文件夹下建立相应文件与文件夹,使得项目结构与下面的保持一致:

    | - koa-project
        | - node_modules
        | - src
            | - file
                | - a.txt
                | - b.txt
            | - main.js
        | - package-lock.json
        | - package.json

在package.json文件里编写启动命令:

{
    // 省略默认配置...
    "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1",
        "dev": "nodemon ./src/main.js"
    }
    // 省略默认配置...
}

main.js文件编写如下:

    let Koa = require('koa');
    let Router = require('koa-router');
    let crypto = require('crypto');
    let fs = require('fs');
    
    let app = new Koa();
    let KoaRouter = new Router();
    
    KoaRouter.get('/', function (ctx, next){
        ctx.body = 'hello home';
    });
    
    app.use(KoaRouter.routes()).use(KoaRouter.allowedMethods());
    
    app.listen(3000,()=>{
        console.log('我们启动了一个3000端口的服务')
    });

在命令行里输入:npm run dev,然后在浏览器端访问:localhost:3000,如果出现浏览器端出现 'hello home'字样,那么就代表我们的服务已经起来了。

三、强缓存实操

编写a.txt文件,文件内容越多越好,比如我的文件内容就是如下:

    a-强缓存1
    a-强缓存2
    a-强缓存3
    a-强缓存4
    a-强缓存5
    a-强缓存6
    a-强缓存7
    a-强缓存8
    a-强缓存9
    a-强缓存10
    a-强缓存11
    a-强缓存12
    a-强缓存13

此时,我们修改一下main.js文件:

    // 其他代码都不变...
    
    // 强缓存路由 +++++++++++
    KoaRouter.get('/getFile', async function (ctx, next){
        const getFileResult = () => {
            return new Promise((resolve, reject) => {
                fs.readFile('src/file/a.txt', 'utf-8',(err, data) => {
                    if (err){
                        resolve(err)
                    } else {
                        resolve(data)
                    }
                });
            });
        }
        let result = await getFileResult(); // 读取文件内容
        ctx.body = result;
        ctx.set('Cache-Control', 'max-age=180'); // 设置浏览器缓存时间为180s,也就是3分钟
    });
    
    // 在app.listen代码前添加...

此时我们再访问一下:http://localhost:3000/getFile

http-缓存.jpg

http-缓存1.jpg

从上面的图里,我们可以重点关注一下响应时间(13ms)响应头里的Cache-Control字段size字段

然后我们重新打开一个页签,再次访问该地址,返回情况如下图:

http-缓存4.png

我们会发现,时间极大的减小了,响应时间(0ms)size字段(disk cache)

我们的设置起了作用,从上述代码来看,我们发现强缓存的流程如下:

  • 第一次向后端发送请求时,后端会在响应头里设置 expires 或者 Cache-Control字段。
  • 因为上一次请求对应的响应里存在expires 或者 Cache-Control字段,所以在n s以内,如果再次请求的话,浏览器会从本地的磁盘里读取相应的结果。

四、协商缓存实操

4.1、last-modified

继续编辑main.js文件:

    // 协商缓存路由-Last-Modified ++++++++++
    KoaRouter.get('/getFileConsult'async function (ctx, next){
        // 读取文件信息
        const getFileInformation = () => {
            return new Promise((resolve, reject) => {
                fs.stat('src/file/b.txt'function (err, stat){
                    if (err){
                        return resolve('读取文件失败');
                    } else {
                        return resolve(stat);
                    }
                });
            })
        }

        // 读取文件内容
        const getFileContent = () => {
            return new Promise((resolve, reject) => {
                fs.readFile('src/file/b.txt''utf-8'(err, data) => {
                    if (err){
                        resolve(err)
                    } else {
                        resolve(data)
                    }
                });
            });
        }

        let ifModifiedSince = ctx.header['if-modified-since']; // 从浏览器端发送的请求中获取上一次的文件修改时间
        if (ifModifiedSince){
            console.log('协商缓存里的标识:', ctx.header);
        }
        let fileInformation = await getFileInformation();
        ctx.set('Cache-Control''no-cache');
        // 开始对比时间
        if (ifModifiedSince === fileInformation.mtime.toGMTString()){
            // 如果时间一致,返回304
            ctx.status = 304;
        } else {
            ctx.set('Last-Modified', fileInformation.mtime.toGMTString());
            let fileContent = await getFileContent();
            ctx.body = fileContent;
        }
    });

验证方式跟 强缓存 一样。

4.2、etag

继续编辑main.js如下:

    // 协商缓存路由-tag
    KoaRouter.get('/getFileConsultTag'async function (ctx, next){
        // 读取文件信息
        const getFileInformation = () => {
            return new Promise((resolve, reject) => {
                fs.stat('src/file/b.txt'function (err, stat){
                    if (err){
                        return resolve('读取文件失败');
                    } else {
                        return resolve(stat);
                    }
                });
            })
        }

        // 读取文件内容
        const getFileContent = () => {
            return new Promise((resolve, reject) => {
                fs.readFile('src/file/b.txt''utf-8'(err, data) => {
                    if (err){
                        resolve(err)
                    } else {
                        resolve(data)
                    }
                });
            });
        }

        let ifNoneMatch = ctx.header['if-none-match']; // 从浏览器端发送的请求中获取上一次的文件对应的hash
        if (ifNoneMatch){
            console.log('协商缓存里的标识:', ctx.header);
        }
        let fileContent = await getFileContent();
        // 创建基于md5算法的hash对象
        let md5HashObject = crypto.createHash('md5');
        md5HashObject.update(fileContent);
        let etag = `${md5HashObject.digest('hex')}`;
        ctx.set('etag', etag);
        ctx.set('Cache-Control''no-cache');
        // 开始对比时间
        if (ifNoneMatch === etag){
            // 如果时间一致,返回304
            ctx.status = 304;
        } else {
            ctx.body = fileContent;
        }
    });

验证方式跟 强缓存 一样。

现在对协商缓存的流程总结如下:

  • 第一次向后端发送请求时,后端会在响应头里设置 last-modified 或者 etag 字段。
  • 因为在上一次的响应里存在 last-modified 或者 etag 字段,所以发送下一次请求时,浏览器会自动带上 if-modified-since(取值为上一次响应里的last-modified字段对应的值) 或者 if-none-match(取值为上一次响应里的etag字段对应的值)字段。
  • 后端再次收到请求,会拿if-modified-since字段 与 对应请求资源的最后修改时间进行比较,或者拿if-none-match字段与 对应请求资源的hash值进行对比,如果不相等,则返回最新资源;如果相等,则直接返回304,浏览器拿到304状态码后,从本地缓存里取出相应资源。
  • 协商缓存命中后,不会再返回报文主体,只返回header部分,这是导致它响应时间缩短的原由。

五、最后

本次文章到这里就结束啦,下次再见......