Still in the hard process of learning Node, recently learned the HTTP related knowledge, learned something of course, the first time to share with you, today we will teach you to see the use of node HTTP module to achieve different cache strategy!!

As we all know, for our front-end developer, cache is a very important thing, that is hope the user can not download every request to come over to repeat our page content, hope to save the user traffic, and can improve our page browsing fluency, but at the same time when we modify a bug, and hopes to update online, At this time, we ask grandpa to tell Grandma to ask the operation and maintenance brother to refresh the cache for us. Then, are there some good cache policies that can modify the bug for us and not bother the operation and maintenance to update in time? Today, we will use Node to see how to set the cache policy in the back-end.

Mandatory cache

We usually for mandatory cache Settings are told the client server you have just had a request, we agreed good come back in ten minutes you request are directly read cache, meaning, that is, when the client requests for many times in ten minutes only will download page content for the first time, the request of the other cache are directly go, Whether or not our page changes in the meantime doesn’t affect the client read cache. So let’s look at the implementation of the code

let http = require('http');
let path = require('path');
let fs = require('fs');
let url = require('url');
// Create a service
let server = http.createServer();
// Listen for requests
server.on('request',(req,res)=>{
    // Get the requested path
    let {pathname,query} = url.parse(req.url,true);
    // Concatenate the path to the appropriate file path on the server
    let readPath = path.join(__dirname, 'public',pathname);
    console.log(readPath)
    try {
        // Get path status
        let statObj = fs.statSync(readPath);
        // The server sets the response header cache-control in seconds
        res.setHeader('Cache-Control'.'max-age=10');
        Gets the current time plus the number of cache seconds just set
        res.setHeader('Expires'.new Date(Date.now()+10*1000).toGMTString());
        // If the path is a folder, the default is to find the index.html file under that file
        if(statObj.isDirectory()){
            let p = path.join(readPath,'index.html');
            console.log(p);
            // If there is no index. HTML, return 404
            fs.statSync(p);
            // Create a file-readable stream and pipe into the response res writable stream
            fs.createReadStream(p).pipe(res)
        }else{
            // If the request is a file length, return it directly
            fs.createReadStream(readPath).pipe(res)
        }
    } catch (error) {
        // Return 404 if not read
        console.log(error)
        res.setHeader('Content-Type'.'text/html; charset=utf8')
        res.statusCode = 404;
        res.end('File not found')}})// Listen on port 3000
server.listen(3000)
Copy the code

In the above code test we can see that when we request the same file within 10 seconds, our browser will go straight out of the cache. As you can see in the figure above, when we repeat the request, we will see the CSS change to from Memory cache. We can also see that the response header we just set is also set

Negotiate the cache

The above mandatory cache we found is that we change the bug online at ordinary times to waiting for a reason, so there is no other good cache processing method, we imagine If we can know that our file if there is any change, if we changed the server returns the latest content if no change Has been the default cache, Doesn’t that sound great? Then we thought if we could know the last modification time of the file is not possible!

Cache by file last modification time

let http = require('http');
let path = require('path');
let fs = require('fs');
let url = require('url');
let server = http.createServer();
server.on('request',(req,res)=>{
    // Get the requested path
    let {pathname,query} = url.parse(req.url,true);
    // Concatenate the path to the appropriate file path on the server
    let readPath = path.join(__dirname, 'public',pathname);
    try {
        // Get path status
        let statObj = fs.statSync(readPath);
        // For testing purposes we tell the client not to force the cache
        res.setHeader('Cache-Control'.'no-cache');
        if(statObj.isDirectory()){
            let p = path.join(readPath,'index.html');
            let statObj = fs.statSync(p);
            // We get the Last Modified time of the file by getting the state of the file, which is called ctime. We tell the client this time through the last-modified header. The client will give this value to the server through the if-modified-since header the next time it requests the file. We just need to check if these two values are equal, and if they are equal then it means that the file has not been modified so we tell the client 304 you can read the cache directly
            res.setHeader('Last-Modified',statObj.ctime.toGMTString());
            if(req.headers['if-modified-since'] === statObj.ctime.toGMTString()){
                res.statusCode = 304;
                res.end();
                return
            }
            // We will return the new content directly
            fs.createReadStream(p).pipe(res)
        }else{
            res.setHeader('Last-Modified',statObj.ctime.toGMTString());
            if(req.headers['if-modified-since'] === statObj.ctime.toGMTString()){
                res.statusCode = 304;
                res.end();
                return
            }
            fs.createReadStream(readPath).pipe(res)
        }
    } catch (error) {
        console.log(error)
        res.setHeader('Content-Type'.'text/html; charset=utf8')
        res.statusCode = 404;
        res.end('File not found')
    }
})

server.listen(3000)
Copy the code

Cache by file content

Again again again again again if we delete the characters in the file and then a reduction, so at this time to save our file modification time is also changed, but the real content of the file does not change, so this time also can actually went on the client cache, let’s take a look at how such caching policy implementation.

let http = require('http');
let path = require('path');
let fs = require('fs');
let url = require('url');
let crypto = require('crypto');
let server = http.createServer();
server.on('request',(req,res)=>{
    // Get the requested path
    let {pathname,query} = url.parse(req.url,true);
    // Concatenate the path to the appropriate file path on the server
    let readPath = path.join(__dirname, 'public',pathname);
    try {
        // Get path status
        let statObj = fs.statSync(readPath);
        // For testing purposes we tell the client not to force the cache
        res.setHeader('Cache-Control'.'no-cache');
        if(statObj.isDirectory()){
            let p = path.join(readPath,'index.html');
            let statObj = fs.statSync(p);
            // We read the file through the stream and then use MD5 encryption to get a Base64 encrypted hash value
            let rs = fs.createReadStream(p);
            let md5 = crypto.createHash('md5');
            let arr = [];
            rs.on('data',(data)=>{
                arr.push(data);
                md5.update(data);
            })
            rs.on('end',(data)=>{
                let r = md5.digest('base64');
                // We then pass the hash value to the client via the response header Etag. The client will bring the previous Etag value to the request header if-none-match on the next request. Then we can continue to check whether the hash value generated by the file is the same as the hash value generated last time. If the hash value is the same, we can tell the client 304 to read the cache
                res.setHeader('Etag',r);
                if(req.headers['if-none-match']===r){
                    res.statusCode=304;
                    res.end();
                    return;
                }
                res.end(Buffer.concat(arr))
            })
        }else{
            let rs = fs.createReadStream(readPath);
            let md5 = crypto.createHash('md5');
            let arr = [];
            rs.on('data',(data)=>{
                arr.push(data);
                md5.update(data);
            })
            rs.on('end',(data)=>{
                let r = md5.digest('base64');
                res.setHeader('Etag',r);
                if(req.headers['if-none-match']===r){
                    res.statusCode=304;
                    res.end();
                    return;
                }
                res.end(Buffer.concat(arr))
            })
        }
    } catch (error) {
        console.log(error)
        res.setHeader('Content-Type'.'text/html; charset=utf8')
        res.statusCode = 404;
        res.end('File not found')
    }
})

server.listen(3000)
Copy the code

We can see through the console Request and reply headers are we said above the corresponding values, but we can also see from the code, we every time when the request comes to encrypt all read out the document and production hash and then compare, this is very consumption performance, so this way of cache also has his shortcomings

conclusion

We implemented three caching methods ourselves through Node, and we can summarize the corresponding implementation of each caching method:

  • Force the Cache server to set cache-control :max-age= XXX and set the Expires time of the response header. The client determines whether to read the Cache
  • Negotiation cache Indicates that the client should remove the cache through status code 304
    • Modification time: The Last modification time of the file is used to determine whether the cache should be read. The server sets the response header last-modified, and the client passes the last-modified value of the Last server response header to the server through if-modified-since. The server compares the modification time of the current file with the last modification time (the value transmitted to the client last time). If the value is the same, the modification time of the file has not changed
    • Contents of the document: The server reads the contents of the file to determine whether the cache should be read or not. The server obtains the hash value by base64 encryption based on MD5 and sets this value to the response header Etag. The next request from the client is brought by if-none-match. The server then checks whether the hash value obtained by encrypting the current file content is the same as that obtained by the last one. If the hash value is the same, the file content has not changed. This method is the most accurate but also the most cost-effective