11
头图

foreword

Hello everyone, I'm Lin Sanxin, uses the most and easy-to-understand words to describe the most difficult knowledge points. is my motto

background

Whether in development or interview, HTTP cache is very important, which is reflected in two aspects:

  • development : Reasonable use of HTTP cache can improve the performance of front-end pages
  • cache is a high-frequency question in the interview

So in this article, I will not talk nonsense, I will tell you the most HTTP cache Nodejs through the simple practice of , everyone will be able to understand and master it through this article! ! !

pre-preparation

Prepare

  • Create the folder cache-study and prepare the environment

    npm init
  • install Koa、nodemon

    npm i koa -D
    npm i nodemon -g
  • Create index.js, index.html, static folders
  • index.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link rel="stylesheet" href="./static/css/index.css">
    </head>
    <body>
    <div class="box">
    
    </div>
    </body>
    </html>
  • static/css/index.css

    .box {
    width: 500px;
    height: 300px;
    background-image: url('../image/guang.jpg');
    background-size: 100% 100%;
    color: #000;
    
    }
  • static/image/guang.jpg

  • index.js

    const Koa = require('koa')
    const fs = require('fs')
    const path = require('path')
    const mimes = {
    css: 'text/css',
    less: 'text/css',
    gif: 'image/gif',
    html: 'text/html',
    ico: 'image/x-icon',
    jpeg: 'image/jpeg',
    jpg: 'image/jpeg',
    js: 'text/javascript',
    json: 'application/json',
    pdf: 'application/pdf',
    png: 'image/png',
    svg: 'image/svg+xml',
    swf: 'application/x-shockwave-flash',
    tiff: 'image/tiff',
    txt: 'text/plain',
    wav: 'audio/x-wav',
    wma: 'audio/x-ms-wma',
    wmv: 'video/x-ms-wmv',
    xml: 'text/xml',
    }
    
    // 获取文件的类型
    function parseMime(url) {
    // path.extname获取路径中文件的后缀名
    let extName = path.extname(url)
    extName = extName ? extName.slice(1) : 'unknown'
    return mimes[extName]
    }
    
    // 将文件转成传输所需格式
    const parseStatic = (dir) => {
    return new Promise((resolve) => {
      resolve(fs.readFileSync(dir), 'binary')
    })
    }
    
    const app = new Koa()
    
    app.use(async (ctx) => {
    const url = ctx.request.url
    if (url === '/') {
      // 访问根路径返回index.html
      ctx.set('Content-Type', 'text/html')
      ctx.body = await parseStatic('./index.html')
    } else {
      const filePath = path.resolve(__dirname, `.${url}`)
      // 设置类型
      ctx.set('Content-Type', parseMime(url))
      // 设置传输
      ctx.body = await parseStatic(filePath)
    }
    })
    
    app.listen(9898, () => {
    console.log('start at port 9898')
    })

    start page

    Now you can enter nodemon index in the terminal and see the display below, which means the service has been successfully started

At this point, you can enter http://localhost:9898/ in the browser link, open and see the following page, which means the page access is successful! ! !

HTTP cache types

There are two common types of HTTP caches:

  • Strong cache: can be determined by one of these two fields

    • expires
    • cache-control (higher priority)
  • Negotiate cache: can be determined by one of these two pairs of fields

    • Last-Modified,If-Modified-Since
    • Etag, If-None-Match (higher priority)

Strong cache

Next we will start talking about strong cache

expires

We just need to set the time of expires in the response header to the current time of + 30s.

app.use(async (ctx) => {
  const url = ctx.request.url
  if (url === '/') {
    // 访问根路径返回index.html
    ctx.set('Content-Type', 'text/html')
    ctx.body = await parseStatic('./index.html')
  } else {
    const filePath = path.resolve(__dirname, `.${url}`)
    // 设置类型
    ctx.set('Content-Type', parseMime(url))
    // 设置 Expires 响应头
    const time = new Date(Date.now() + 30000).toUTCString()
    ctx.set('Expires', time)
    // 设置传输
    ctx.body = await parseStatic(filePath)
  }
})

Then we refresh the front-end page, we can see that there is an additional field of expires in the response header of the requested resource

And, within 30s, after we refresh, we see that all requests go to memory , which means that the time limit for setting a strong cache through expires is 30s. Within this 30s, the resources will go to the local cache without re-requesting

Note: Sometimes your Nodejs code has updated the aging time, but you find that the front-end page is still going through the aging time of the code. At this time, you can tick this Disabled cache , then refresh it, and then cancel the tick

cache-control

In fact, cache-control and expires have similar effects, but the values set in these two fields are different. The former sets the number of seconds to , while the latter sets the number of milliseconds to .

app.use(async (ctx) => {
  const url = ctx.request.url
  if (url === '/') {
    // 访问根路径返回index.html
    ctx.set('Content-Type', 'text/html')
    ctx.body = await parseStatic('./index.html')
  } else {
    const filePath = path.resolve(__dirname, `.${url}`)
    // 设置类型
    ctx.set('Content-Type', parseMime(url))
    // 设置 Cache-Control 响应头
    ctx.set('Cache-Control', 'max-age=30')
    // 设置传输
    ctx.body = await parseStatic(filePath)
  }
})

The front-end page response header has an additional field of cache-control , and the local cache is used within 30s, and the server will not be requested.

Negotiate cache

Different from the strong cache, the strong cache is within the aging time, not the server, but only the local cache; while the negotiation cache is to go to the server, if you request a resource, when you request the server, you will find hits the cache, it will return 304 , otherwise, it will return the requested resource, so how does count as a cache hit? Next

Last-Modified,If-Modified-Since

Simply put:

  • When the resource is requested for the first time, the server will send the last modification time of the requested resource as the value of Last-Modified in the response header to the browser and store it in the browser
  • When the resource is requested for the second time, the browser will take the time just stored as the value of If-Modified-Since in the request header and pass it to the server. The server will get this time and compare it with the last modification time of the requested resource.
  • If the comparison results are the same, it means that the resource has not been modified, that is, hits the cache, then returns 304 , if not, it means that the resource has been modified, then does not hit the cache, and returns after modification new resources
// 获取文件信息
const getFileStat = (path) => {
  return new Promise((resolve) => {
    fs.stat(path, (_, stat) => {
      resolve(stat)
    })
  })
}

app.use(async (ctx) => {
  const url = ctx.request.url
  if (url === '/') {
    // 访问根路径返回index.html
    ctx.set('Content-Type', 'text/html')
    ctx.body = await parseStatic('./index.html')
  } else {
    const filePath = path.resolve(__dirname, `.${url}`)
    const ifModifiedSince = ctx.request.header['if-modified-since']
    const fileStat = await getFileStat(filePath)
    console.log(new Date(fileStat.mtime).getTime())
    ctx.set('Cache-Control', 'no-cache')
    ctx.set('Content-Type', parseMime(url))
    // 比对时间,mtime为文件最后修改时间
    if (ifModifiedSince === fileStat.mtime.toGMTString()) {
      ctx.status = 304
    } else {
      ctx.set('Last-Modified', fileStat.mtime.toGMTString())
      ctx.body = await parseStatic(filePath)
    }
  }
})

On the first request, in the response header:

In the second request, in the request header:

Since the resource has not been modified, the cache is hit and 304 is returned:

At this point we modify index.css

.box {
  width: 500px;
  height: 300px;
  background-image: url('../image/guang.jpg');
  background-size: 100% 100%;
  /* 修改这里 */
  color: #333;
}

Then we refresh the page, index.css changed, so will miss the cache, return 200 and the new resource, and guang.jpg has not been modified, then hits the cache and returns 304:

Etag,If-None-Match

In fact, Etag,If-None-Match is roughly the same as Last-Modified,If-Modified-Since , the difference is:

  • The latter is to compare the last modification time of the resource to determine whether the resource has been modified
  • The former is to compare the content of the resource to determine whether the resource is modified

So how do we compare resource content? We only need to read the content of the resource, convert it into a hash value, and compare it before and after! !

const crypto = require('crypto')

app.use(async (ctx) => {
  const url = ctx.request.url
  if (url === '/') {
    // 访问根路径返回index.html
    ctx.set('Content-Type', 'text/html')
    ctx.body = await parseStatic('./index.html')
  } else {
    const filePath = path.resolve(__dirname, `.${url}`)
    const fileBuffer = await parseStatic(filePath)
    const ifNoneMatch = ctx.request.header['if-none-match']
    // 生产内容hash值
    const hash = crypto.createHash('md5')
    hash.update(fileBuffer)
    const etag = `"${hash.digest('hex')}"`
    ctx.set('Cache-Control', 'no-cache')
    ctx.set('Content-Type', parseMime(url))
    // 对比hash值
    if (ifNoneMatch === etag) {
      ctx.status = 304
    } else {
      ctx.set('etag', etag)
      ctx.body = fileBuffer
    }
  }
})

The verification method is the same as that of Last-Modified,If-Modified-Since , and will not be repeated here. . .

Summarize

References

Epilogue

I'm Lin Sanxin, an enthusiastic front-end rookie programmer. If you are motivated, like the front-end, and want to learn the front-end, then we can make friends and fish together haha, touch the fish group, add me, please note [Si No]

image.png


Sunshine_Lin
2.1k 声望7.1k 粉丝