HTTP cache
Browser compatibility
There really isn’t an API called HTTP caching. It is a generic name for a collection of Web platform apis. All browsers support these apis:
- Cache-Control
- ETag
- Last-Modified
How does HTTP caching work
The behavior of the HTTP cache is controlled by a combination of request and response headers. Ideally, you can control both the code of the Web application (which determines the request header) and the configuration of the Web server (which determines the response header). Common HTTP caches store only GET responses, not other types of responses. The key to caching is the Request Method and the target URI (usually only GET requests are cached).
Because the HTTP protocol is C/S mode, the server to update a resource, can’t be directly to notify the client cache updates, so both parties must agree to the resource an expiration time, before the expiration date, the resource (cached copy) is fresh, when after the expiration date, the resources (cached copy) have become obsolete.
Negotiation algorithm is used to replace old resources (cached copy) for fresh, note that an old resource (cached copy) is not directly be cleared or ignored, when the client initiates a request, caching retrieved for a corresponding old resources (cached copy), the cache will attach the request an If – None – Match head, If the server returns 304 (Not Modified), the resource copy is fresh. This saves some bandwidth. If the server determines that the resource has expired by if-none-match or if-modified-since, the entity content of the resource is returned.
Request headers: Stick to the default values
Browsers always default to caching headers when sending requests. Request headers affect whether the check is fresh, such as if-none-match and if-modified-since
Response headers
- The cache-control. The server can return a cache-control directive that specifies how the browser and other intermediate caches should Cache the individual response and for how long.
- ETag. When the browser finds an expired cached response, it can send a small token (usually a hash of the file’s contents) to the server to check if the file has changed. If the server returns the same token, the file is the same, so there is no need to re-download it.
- The last-modified. This header is used for the same ETag, but uses a time-based policy to determine whether the resource has changed, rather than a content-based policy ETag.
Note: Omitting the cache-control response header does not disable HTTP caching! Instead, browsers can effectively guess which type of caching behavior makes the most sense for a given type of content.
Versioned URL long-term caching
When responding to a request for a URL that contains “hash” or version information and whose content is never expected to change, add cache-control: max-age=31536000 to the response. Setting this value will tell the browser that in the coming year (31,536,000 seconds; When the same URL is loaded in maximum supported value, it can immediately use the value in the HTTP cache without sending a request. Build tools like WebPack can automatically assign hash fingerprints to the process of asset urls.
A URL that is not versioned, requesting revalidation from the server
The HTML file of every Web application, for example, will never contain version information.
Cache-control can take the following values:
- No-cache: indicates that the browser must revalidate with the server each time it uses the cached version of a URL.
- No-store: Indicates that browsers and other intermediate caches (such as CDN) never store any version of the file.
- Private: The browser can cache files, but the intermediate cache cannot.
- Public: The response can be stored by any cache.
Cache-control can accept a comma-separated list of instructions. For example, public, max-age=31536000
The ETag and last-modified
ETag or last-modified matches request headers if-none-match and if-modified-since by setting ETag or last-Modified. Determine whether the version of the resource already in the browser’s HTTP cache matches the latest version on the Web server, and if so, the server can respond with 304 Not Modified.
Last-modified and if-Modified-since processes are the same as shown above.
The cache-control flow chart
Example of cache-control Settings
The cache-control values | instructions |
---|---|
max-age=86400 | Responses can be cached by the browser and the intermediate cache for up to 1 day (60 seconds x 60 minutes x 24 hours) |
private, max-age=600 | Responses can be cached by the browser cache (but not by the intermediate cache) for up to 10 minutes (60 seconds x 10 minutes). |
public, max-age=31536000 | Responses can be stored by any cache for 1 year. |
no-store | Caching of the response is not allowed and must be obtained in its entirety on each request. |
Offline cache (ServiceWorker)
The ServiceWorker controls how requests are cached and handled. The ServiceWorker handles requests independently from the cache, so let’s take a look at them individually. First, when should you cache?
When the cache
Install – in the form of dependencies
Suitable for: CSS, images, fonts, JS, templates, etc., basically any object that includes the static content of a site. Failure to obtain these objects will result in the site not working at all.
self.addEventListener('install'.function(event) {
event.waitUntil(
caches.open('mysite-static-v3').then(function(cache) {
return cache.addAll([
'/css/whatever-v3.css'.'/css/imgs/sprites-v6.png'.'/css/fonts/whatever-v8.woff'.'/js/all-min-v4.js'// etc ]); })); });Copy the code
The event.waitUntil parameter is a promise that defines the duration and success of the installation. If the promise refuses, the installation is considered a failure and the ServiceWorker is discarded (it will remain unchanged if an older version is running). Caches. Open and cache.addAll return promises. If any of these resources fail to fetch, the cache.addAll call is rejected.
Install – not as a dependency
Suitable for: large resources that are not immediately needed, such as resources for a higher level of the game.
self.addEventListener('install'.function(event) {
event.waitUntil(
caches.open('mygame-core-v1').then(function(cache) {cache.addall (// low priority);returnCache.addall (// Core resources are given higher priority); })); });Copy the code
We do not pass the promise of low-priority cache.addall back to event.waitUntil, so even if it fails, the application is still available offline. The possibility of missing these levels must be considered, and if they are missing, try caching them again.
When activated
Good for: cleanup and migration.
If the new ServiceWorker is installed and not using the previous version, the new ServiceWorker will activate, activating the Activate event. This is a good time to deal with schema migration and removal of unused caches in IndexedDB due to the retirement of the older version.
self.addEventListener('activate'.function(event) {
event.waitUntil(
caches.keys().then(function(cacheNames) {
return Promise.all(
cacheNames.filter(function(cacheName) {// Filter the cache to be deleted}).map(function(cacheName) {
returncaches.delete(cacheName); })); })); });Copy the code
During activation, other events such as fetch are placed in a queue, so prolonged activation may prevent the page from loading.
User interaction
When the button is clicked, the desired content is fetched from the network and placed in the cache.
document.querySelector('.cache-button').addEventListener('click'.function(event) {
event.preventDefault();
var id = this.dataset.articleId;
caches.open('mysite-article-' + id).then(function(cache) {
fetch('/get-article-urls? id=' + id).then(function(response) {// Get all dependent urls and cache themreturn response.json();
}).then(function(urls) {
cache.addAll(urls);
});
});
});
Copy the code
Network response time
Good for: frequent updates to resources such as user inboxes or article content. This also applies to non-important resources, such as profile pictures, but should be handled with caution.
If the requested resource does not match any of the resources in the cache, it is fetched from the network, sent to the page and added to the cache.
self.addEventListener('fetch'.function(event) {
event.respondWith(
caches.open('mysite-dynamic').then(function(cache) {
return cache.match(event.request).then(function (response) {
return response || fetch(event.request).then(function(response) {
cache.put(event.request, response.clone());
returnresponse; }); }); })); });Copy the code
To allow sufficient memory usage, only one response/request body can be read at a time. In the code above,.clone() is used to create additional copies that can be read separately.
Revalidation
Suitable for: Resources that are not required to update the latest version frequently. Avatars fall into this category.
If a cached version is available, that version is used, but updates are retrieved next time.
self.addEventListener('fetch'.function(event) {
event.respondWith(
caches.open('mysite-dynamic').then(function(cache) {
return cache.match(event.request).then(function(response) {
var fetchPromise = fetch(event.request).then(function(networkResponse) {
cache.put(event.request, networkResponse.clone());
return networkResponse;
})
returnresponse || fetchPromise; })})); });Copy the code
The caching pattern
No matter how much content you cache, ServiceWorker will not use caching unless you instruct it when and how to use it. Here are a few patterns for handling requests:
Only the cache
Good for: any resource that gets static content from a web site. These resources should be cached in the Install event so that they can be used.
self.addEventListener('fetch'.function(event) {// If not found in the cache, the response looks like a connection error. });Copy the code
Only the network
self.addEventListener('fetch'.function(event) {
event.respondWith(fetch(event.request));
});
Copy the code
Cache and rollback to the network
Good for: Build in an offline first fashion, which is how most requests are handled. Depending on the incoming request, there are exceptions to other patterns.
self.addEventListener('fetch'.function(event) {
event.respondWith(
caches.match(event.request).then(function(response) {
returnresponse || fetch(event.request); })); });Copy the code
Caching and network races
In a combination of older hard drives, virus scanners, and fast Internet connections, getting resources from the network is faster than accessing disks.
// Don't use promise. race, as there is a Reject before fulfillment. whole is reject.function promiseAny(promises) {
returnNew Promise((resolve, reject) => {// Promise => Promise(p)); ForEach (p => p.tenn (resolve)); Reduce ((a, b) => a.catch(() => reject(Error)). Catch (() => reject(Error)"All failed")));
});
};
self.addEventListener('fetch'.function(event) {
event.respondWith(
promiseAny([
caches.match(event.request),
fetch(event.request)
])
);
});
Copy the code
The network falls back to cache
Suitable for: Quick repair of frequently updated resources. For example, articles, avatars, social media timelines, game leaderboards.
This means providing the latest content for online users, but older cached versions for offline users. If the network request is successful, you may need to update the cache.
self.addEventListener('fetch'.function(event) {
event.respondWith(
fetch(event.request).catch(function() {
returncaches.match(event.request); })); });Copy the code
Note: This method has drawbacks. This can take a long time if the user’s network is patchy or slow.
Cache first and then access the network
Good for: frequently updated content. For example, articles, social media timelines, game leaderboards.
The page is required to make two requests, one for caching and one for network access. The operation is to first display the cached data and then update the page when the network data arrives.
var networkDataReceived = false; Var networkUpdate = fetch('/data.json').then(function(response) {
return response.json();
}).then(function(data) {
networkDataReceived = true; updatePage(); }); // Request cache data caches. Match ('/data.json').then(function(response) {
if(! response) throw Error("No data");
return response.json();
}).then(function(data) {// Prevent overwriting cached dataif(! networkDataReceived) { updatePage(data); } }).catch(function() {// To get cached data, the network is requestedreturn networkUpdate;
})
Copy the code
Code in ServiceWorker: Always access the network and update the cache
self.addEventListener('fetch'.function(event) {
event.respondWith(
caches.open('mysite-dynamic').then(function(cache) {
return fetch(event.request).then(function(response) {
cache.put(event.request, response.clone());
returnresponse; }); })); });Copy the code
Conventional back
Suitable for: secondary images such as avatars, failed POST requests, “Unavailable while offline” pages.
self.addEventListener('fetch'.function(event) {
event.respondWith(
caches.match(event.request).then(function(response) {
return response || fetch(event.request);
}).catch(function() {
return caches.match('/offline.html'); })); });Copy the code
Render the ServiceWorker template
Suitable for: pages whose server response cannot be cached (SSR). For server-side rendering, if caching fails, you can choose instead to request JSON data and a template and render.
importScripts('templating-engine.js');
self.addEventListener('fetch'.function(event) {
var requestURL = new URL(event.request);
event.respondWith(
Promise.all([
caches.match('/article-template.html').then(function(response) {
return response.text();
}),
caches.match(requestURL.path + '.json').then(function(response) {
return response.json();
})
]).then(function(responses) {
var template = responses[0];
var data = responses[1];
return new Response(renderTemplate(template, data), {
headers: {
'Content-Type': 'text/html'}}); })); });Copy the code
A mixture of
You may use more than one of these methods depending on the requested url.
self.addEventListener('fetch'.function(event) { var requestURL = new URL(event.request.url); // use a specific mode to determine hostif (requestURL.hostname == 'api.example.com') {
event.respondWith(/* some combination of patterns */);
return;
}
if(requesturl.origin == location.origin) {// Textif (/^\/article\//.test(requestURL.pathname)) {
event.respondWith(/* some other combination of patterns */);
return; } / / pictureif (/\.webp$/.test(requestURL.pathname)) {
event.respondWith(/* some other combination of patterns */);
return;
}
if (request.method == 'POST') {
event.respondWith(/* some other combination of patterns */);
return;
}
if (/cheese/.test(requestURL.pathname)) {
event.respondWith(
new Response("Flagrant cheese error", {
status: 512
})
);
return; Event.respondwith (caches. Match (event.request).then()function(response) {
returnresponse || fetch(event.request); })); });Copy the code