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
-
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 environmentnpm 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, thendoes 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]
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。