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