preface

In this era of rapidly improving user experience, the caching strategy in the browser has been a common topic for a long time. Today we will take a look at some of the secrets and implement a series of caching operations using NodeJS

Let’s start with the application scenarios and benefits of caching

Browser cache refers to the cache of some resource files such as JS, CSS, image and so on that we have accessed to ensure that the next access can be directly from the cache, without going to the server to re-request so this brings benefits. One is to reduce the server request pressure, the second is from the cache access resources relative to the server request resources in the speed is certain to be fast, so that the user’s experience is also a great improvement

One of the questions is where do we cache resources?

There are several main locations in the browser cache

Memory catch is the memory that caches resources into the browser, It’s very fast but when we close the current TAB the cache will fail (which is similar to sessionStorage). Disk catch is to cache resources to hard disk and because it’s cached on hard disk it must have a much larger capacity. Service Sorker is a separate thread running on the back of the browser and can be used to implement caching. Push cache is a product of Http2.0 and will only be used if none of the above three caches are hit. It exists only in sessions and is released once the session ends and is cached for a short time

Caching strategies

Strong Cache Expires and cache-control

A strong Cache is the first Cache the browser looks for. If it is used directly and the status code returns 200, the Expires field will be set to Greenwich Mean time to compare with the local time of the client. The cache-control field will be much larger, as described in 🌰 below. Another point is that cache-control has a higher priority than Expires

Negotiation cache [last-modify, if-modify-since], [ETag, if-none-match]

1. The negotiation cache is found when the strong cache is not hit. The server will carry last-modify 2 when the browser first requests the resource. This field is the Last time the current resource was modified. The browser will then bring the last-modify value back to the server for comparison. If available, it will return a 304 status code indicating a cache hit

3. The second set of negotiation caches is designed to solve some of the disadvantages of the first set. The first set of negotiation caches can be used in theory if the resource changes during the cycle and then changes back to the original form

4. The second negotiation cache uses Etag to compare with if-none-match. The comparison process is similar to that of Etag, which is higher than last-modify

In Baidu to find a cache request flow chart can refer to understand

In field started

<! -- Create an HTML template for the demo -->
<! DOCTYPEhtml>
<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>
</head>
<body>
    <h2 id="client"></h2>
    <h2 id="server"></h2>
    <script>
        client.innerHTML = 'Uncached timeThe ${new Date().toTimeString()}`
    </script>
    <script src='catch'></script>
</body>
</html>
Copy the code
/ / the node services
const http = require('http')
const fs = require('fs')

http.createServer((req, res) = > {
    const { url } = req
    if (url === '/') {
        const html = fs.readFileSync('./index.html')
        res.end(html)
    } else if (url === '/favicon.ico') {
        res.end(' ')}else if (url === '/catch') {
        / / cache
        const data = 'server.innerhtml =' Cache time of 10msThe ${new Date().toTimeString()}Flowers' `
        res.statusCode = 200
        // Set the strong cache to 10 seconds
        res.setHeader('Expires'.new Date(Date.now() + 10 * 1000).toUTCString())
        res.end(data)
    }
}).listen(5000.() = > console.log('Starting... '))
Copy the code

Strong cache

If the browser’s state does not change significantly after that, you can look at both graphs

We can see if the current resource is cached by looking at the size of the request. If it’s a memory catch or a disk catch or something like that, it means it’s cached. It’s the first time the request has been made, so it’s not cached So what WE’re going to do is we’re going to refresh in place and we’re going to see, and you can see that it’s out of the cache and if you run the demo you’re going to see that the amount of time it’s cached hasn’t changed in ten seconds

So if we set the cache-control to 15 seconds it’s going to look exactly the same, but the expiration time is 15 seconds so you can see that cache-control oversets expires

// Set cache-control to 15 seconds under Expires
res.setHeader('Cache-Control'.'max-age=15')
Copy the code

We’re trying out cache-control and other values, because the rest of the properties don’t show up very well on the page so we don’t post the browser graph so you can try it out for yourself

No-store disables caching. The page is not cached and the cache returned by the server changes with each refresh

res.setHeader('Cache-Control'.'no-store')
Copy the code

If expires is not set and is a no-catch, it will be cached directly. If expires is not set, it will be cached directly

res.setHeader('Cache-Control'.'no-catch')
Copy the code

Public can be cached by all ends

res.setHeader('Cache-Control'.'public')
Copy the code

Private can only be cached by the client and cannot be cached by the other side of the resource. This is different from public

res.setHeader('Cache-Control'.'private')
Copy the code

The catch-control values are here and there are a few others that you can try out for yourself

Cache-Control instructions
public All content will be cached (both client and proxy servers can cache)
private Content is only cached in a private cache (the client can cache)
no-cache The negotiation cache is required to validate the cached data
no-store Nothing is cached
must-revalidation/proxy-revalidation If the cached content is invalid, the request must be sent to the server/proxy for revalidation
max-age=xxx (xxx is numeric) Cached contents will expire after XXX seconds. This option is only available in HTTP 1.1 and has a higher priority if used with last-Modified

Negotiate the cache

Test last-modify, if-modify-since

res.setHeader('Cache-Control'.'no-catch')
res.setHeader('last-modified'.new Date().toUTCString())
let ifres = new Date(req.headers['if-modified-since']).getTime()
// Simulate a negotiated cache hit
if (ifres + 1000 > Date.now()) {
  console.log('Negotiate cache hits')
  res.statusCode = 304
  res.end(' ')
  return
}
Copy the code

The browser status shows that 304 is returned and the time is updated

Test ETag, if-none-match

res.setHeader('Cache-Control'.'no-catch')
// Write a dead hash here
res.setHeader('Etag'.'9527')
if (req.headers['if-none-match'= = ='9527') {
  console.log('Negotiate cache hits')
  res.statusCode = 304
  res.end(' ')}Copy the code

You can see that 304 is still returned and that Etag and if-none-match are used according to the browser cache convention

Complete node service code

const http = require('http')
const fs = require('fs')

http.createServer((req, res) = > {
    const { url } = req
    if (url === '/') {
        const html = fs.readFileSync('./index.html')
        res.end(html)
    } else if (url === '/favicon.ico') {
        res.end(' ')}else if (url === '/catch') {
        / / cache
        const data = 'server.innerhtml =' Cache for 10 secondsThe ${new Date().toTimeString()}'`

        res.statusCode = 200
        // res.setHeader('Expires', new Date(Date.now() + 10 * 1000).toUTCString())
        // res.setHeader('Cache-Control', 'max-age=15')
        // res.setHeader('Cache-Control', 'public')
        // res.setHeader('Cache-Control', 'private')

        res.setHeader('Cache-Control'.'no-catch')
        // Write a dead hash here
        res.setHeader('Etag'.'9527')
        if (req.headers['if-none-match'= = ='9527') {
            console.log('Negotiate cache hits')
            res.statusCode = 304
            res.end(' ')}// res.setHeader('last-modified', new Date().toUTCString())
        // let ifres = new Date(req.headers['if-modified-since']).getTime()
        // // Simulate a cache hit negotiation
        // if (ifres + 1000 > Date.now()) {
        // console.log(' negotiate cache hit ')
        // res.statusCode = 304
        // res.end('')
        // return
        // }
        res.end(data)
    }
}).listen(5000.() = > console.log('Starting... '))
Copy the code

And have a great weekend ☕️