First look at a classic flow chart, combined with understanding

Eat it ~

1. Cache function

  • Reduced redundancyThe data transfer, saving the network fee.
  • Reduce the burden of the server, greatly improve the siteperformance
  • Speed up the client loading of web pagesspeed

2. Cache classification

2.1 the DNS cache

The main thing is to associate the corresponding IP and domain name locally in the browser, so that the DNS resolution is fast.

2.2 MemoryCache

This refers to the cache that exists in memory. In terms of priority, it is the first cache that the browser tries to hit. In terms of efficiency, it is one of the most responsive caches. Memory caches are fast and short-lived. It lives and dies with the rendering process, and when the process ends, that is, when the TAB is closed, the data in memory will no longer exist.

2.3 Browser Cache

Browser caches, also known as Http caches, are divided into strong caches and negotiated caches. The negotiation cache is used only when the strong cache fails to be hit

2.3.1 strong cache

Strong caching is controlled using the Expires and cache-control fields in the HTTP header. In a strong cache, when the request is made again, the browser will determine whether the target resource “hits” the strong cache based on the Expires and cache-control Settings. If so, the browser will obtain the resource directly from the cache without communicating with the server.

Expires

To implement a strong cache, we’ve always used Expires in the past. When the server returns a Response, the expiration time is written to the Expires field in Response Headers. Like this,

expires: Wed, 12 Sep 2019 06:12:18 GMT

As you can see, Expires is a timestamp, and then if we try to request a resource from the server again, the browser will first compare the local time to the Expires timestamp, and if the local time is less than the expires set time, it will simply fetch the resource from the cache.

From this description, it’s not hard to guess that Expires is problematic, and its biggest problem is its dependence on local time. If the server and client time may be set differently, or if I manually change the client time, expires will not meet our expectations.

Cache-Control

Considering expires’s limitations, HTTP1.1 adds a cache-control field to the Expires task. Cache-control can do everything expires can do; Cache-control can do what Expires cannot. Therefore, cache-control can be considered a complete expires alternative. In current front-end practice, the only reason we continue to use Expires is for backward compatibility.

In cache-control, we use max-age to Control the validity of the resource. Max-age is not a timestamp, but a time length. In this case, max-age is 31,536,000 seconds, which means the resource is valid for up to 31,536,000 seconds, perfectly avoiding potential problems with timestamps.

Cache-control is more accurate and has a higher priority than Expires. When cache-control and Expires are present at the same time, cache-control prevails.

Please refer to the following two pictures:

2.3.2 Negotiation Cache (Comparison Cache)

The negotiated cache relies on the communication between the server and the browser. Under the negotiated cache mechanism, the browser needs to ask the server for cache information, and then determine whether to resend the request, download a complete response, or obtain cached resources from the local server. If the server prompts that the cached resource is Not Modified, the resource is redirected to the browser cache, in which case the network request corresponds to a status code of 304.

The implementation of the negotiation cache, from last-Modified to Etag, last-Modified is a timestamp, if we enable the negotiation cache, it will return the Response Headers on the first request:

Last-Modified

Last-Modified: Fri, 27 Oct 2017 06:35:57 GMT
Copy the code

Each time we make a request, the browser’s headers will carry a timestamp field called if-modified-since, which is the last-modified value that the response returned to it Last time:

If-Modified-Since: Fri, 27 Oct 2017 06:35:57 GMT
Copy the code

When the server receives the timestamp, it compares it with the last time the resource was modified on the server to determine whether the resource has changed. If so, a complete Response is returned and the new last-modified value is added to the Response Headers. Otherwise, a 304 Response is returned, and the Response Headers does not add the Last-Modified field.

The diagram below:

The last modification time is used to determine whether the cache is available

  • Last-Modified: Tells the client when the resource was last modified
  • If-Modified-Since: When the resource expires (useCache-ControlIdentification of themax-age), found that the resource hasLast-ModifiedDeclare, then request the server again with the aboveIf-Modified-Since.
  • The server found a header after receiving the requestIf-Modified-SinceIs compared to the last modification time of the requested resource. If the last modification time is newer, it indicates that the resource has been modified again. The system responds to the latest resource content and returns200A status code.
  • If the last modification time andIf-Modified-SinceIf yes, the resource is not modified304Indicates no update and tells the browser to continue using the saved cache file.

Look at this example code:

let http = require('http');
let fs = require('fs');
let path = require('path');
let mime = require('mime');
http.createServer(function (req, res) {
    let file = path.join(__dirname, req.url);
    fs.stat(file, (err, stat) => {
        if (err) {
            sendError(err, req, res, file, stat);
        } else {
            let ifModifiedSince = req.headers['if-modified-since'];
            if (ifModifiedSince) {
                if (ifModifiedSince == stat.ctime.toGMTString()) {
                    res.writeHead(304);
                    res.end();
                } else{ send(req, res, file, stat); }}else{ send(req, res, file, stat); }}}); }).listen(8080);
function send(req, res, file, stat) {
    res.setHeader('Last-Modified', stat.ctime.toGMTString());
    res.writeHead(200, { 'Content-Type': mime.getType(file) });
    fs.createReadStream(file).pipe(res);
}
function sendError(err, req, res, file, stat) {
    res.writeHead(400, { "Content-Type": 'text/html' });
    res.end(err ? err.toString() : "Not Found");
Copy the code

There are some downsides to using last-modified, the most common of which are the following scenarios. Some servers do not have the exact last modification time of a file, which makes it impossible to determine whether a file has been updated by the last modification time. We have edited the file, but the content of the file has not changed. The server doesn’t know if we’ve actually changed the file, it’s still judging by the last edit time. This resource will therefore be treated as a new resource when it is requested again, triggering a full response — even when it should not be requested again. 3. When we modify a file too quickly (for example, it takes 100ms to complete the change), if-modified-since is not aware of the change because it only checks for the smallest time difference in seconds — when it’s time to request it again, it doesn’t. 4. If the same file resides on multiple CDN servers, the content of the file is the same, but the modification time is different.

The second and third scenarios point to the same bug – the server is not properly aware of file changes. To solve this problem, Etag came along as a supplement to Last-Modified.

Etag

This is another one in the negotiation cache

An Etag is a unique identity string (fingerprint) generated by the server for each resource. This identity string can be encoded based on the contents of the file. As long as the contents of the file are different, the Etag corresponding to them will be different, and vice versa. Therefore, Etag can accurately perceive file changes.

The Etag is generated by the Web server and sent to the browser client. The downside is that the generation process requires extra overhead on the server and affects the performance of the server. Therefore, enabling Etag requires a sense of perspective. As we just mentioned — Etag is not a replacement for Last-Modified, it can only supplement and reinforce last-Modified.

The execution process is as follows: 1. To determine whether the cache is available, the client can obtain the ETag of the document in the cache first, and then send a request to the Web server through if-none-match to ask whether the cache is available. 2. The server receives the request and compares the ETag of the file in the server with the if-None-match value in the request header. If the value is the same, the cache is up to date. 3. If no, the Web server sends the latest version of the document to the browser client

Look at the following example code:

let http = require("http");
let fs = require("fs");
let path = require("path");
let mime = require("mime");
let crypto = require("crypto");
http
  .createServer(function(req, res) {
    let file = path.join(__dirname, req.url);
    fs.stat(file, (err, stat) => {
      if (err) {
        sendError(err, req, res, file, stat);
      } else {
        let ifNoneMatch = req.headers["if-none-match"];
        let etag = crypto
          .createHash("sha1")
          .update(stat.ctime.toGMTString() + stat.size)
          .digest("hex");
        if (ifNoneMatch) {
          if (ifNoneMatch == etag) {
            res.writeHead(304);
            res.end();
          } else{ send(req, res, file, etag); }}else{ send(req, res, file, etag); }}}); }) .listen(8080);
function send(req, res, file, etag) {
  res.setHeader("ETag", etag);
  res.writeHead(200, { "Content-Type": mime.lookup(file) });
  fs.createReadStream(file).pipe(res);
}
function sendError(err, req, res, file, etag) {
  res.writeHead(400, { "Content-Type": "text/html" });
  res.end(err ? err.toString() : "Not Found");
}
Copy the code

Strong cache and negotiated cache comparison

Priority:

Etag is more accurate than last-Modified in sensing file changes and has a higher priority. If both Etag and last-Modified exist, the Etag prevails.

Contrast:

  • The mandatory cache does not need to interact with the server, while the comparison cache does not need to interact with the server
  • Both types of cache rules can exist at the same time, and the mandatory cache takes precedence over the comparison cache. That is, when the mandatory cache rule is executed, if the cache is in effect, the cache is used directly, and the comparison cache rule is not executed

2.4 the Service Worker Cache

A Service Worker is a Javascript thread that is separate from the main thread. It is detached from the browser form, so you cannot access the DOM directly. This independent personality prevents the “personal behavior” of the Service Worker from interfering with the performance of the page. This behind-the-scenes Worker can help us implement functions such as offline caching, message push, and network proxy. The offline Cache we implement with Service workers is called Service worker Cache.

The life cycle of a Service Worker includes three phases: install, Activited, and working. Once the Service Worker is installed, it will always exist and only switch between active and working unless we actively terminate it. This is an important prerequisite for it to be used for offline storage.

It’s in the Browser Development Tools (F12) Application TAB

2.5 Push the Cache

Push Cache refers to the Cache that exists in HTTP2 server Push phase. This area of knowledge is relatively new and the application is still in its infancy, and the limited application does not mean that it is unimportant – HTTP2 is the trend and the future. At this point in time, we still want you to understand the key features of Push Cache:

  • Push CacheIs the last line of defense for the cache. The browser is only availableMemory Cache,HTTP CacheService Worker CacheIf both of them missedPush Cache.
  • Push CacheA cache that exists during the session phase and is released when the session terminates.
  • As long as different pages share the sameHTTP2Connect, so they can share the samePush Cache.

3. Request process

3.1 First request

3.2 Second request

Up to the caching mechanism

4. How to not send a request at all

  • The browser caches the file toCacheDirectory, which the browser checks first on the second requestCacheDoes the directory contain the file? If yes, it has not arrivedExpiresWhen the file has not expired, the browser will read the file directly from the Cache directory without sending any more requests
  • ExpiresIs the server response header field that tells the browser in response to an HTTP request that the browser can cache the data directly from the browser until the expiration date without having to request it again. This isHTTP1.0The content of the browser is now used by defaultHTTP1.1, so you can basically ignore it
  • Cache-ControlwithExpiresSpecifies the validity period of the current resource and controls whether the browser buffers the data directly from the browser or resends the request to the server. If set at the same time, the priority is higherExpires

5.1 use the cache-control

  • Private clients can cache
  • Both public clients and proxy servers can cache
  • Max-age =60 The cached content will expire after 60 seconds
  • No-cache Uses the comparison cache to verify data and forcibly verifies the data to the source server
  • No-store All content is not cached, and neither force caching nor comparison caching is triggered
  • Cache-Control:private, max-age=60, no-cache
let http = require("http");
let fs = require("fs");
let path = require("path");
let mime = require("mime");
let crypto = require("crypto");
http
  .createServer(function(req, res) {
    let file = path.join(__dirname, req.url);
    console.log(file);

    fs.stat(file, (err, stat) => {
      if (err) {
        sendError(err, req, res, file, stat);
      } else{ send(req, res, file); }}); }) .listen(8080);
function send(req, res, file) {
  let expires = new Date(Date.now() + 60 * 1000);
  res.setHeader("Expires", expires.toUTCString());
  res.setHeader("Cache-Control"."max-age=60");
  res.writeHead(200, { "Content-Type": mime.lookup(file) });
  fs.createReadStream(file).pipe(res);
}
function sendError(err, req, res, file, etag) {
  res.writeHead(400, { "Content-Type": "text/html" });
  res.end(err ? err.toString() : "Not Found");
}
Copy the code

The resources

Browser Caching

The last

Give this article a like if it helped you

Here at Github, let’s study together