Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Service Worker: cache.match(request) returns undefined

I have a simple service worker, that adds two resources (index.html, app.js) to the cache (on install), deletes old caches (on activate) and serves the resources from cache if present, else from the network (on fetch). To get the new service worker registered and to delete old caches I increase the version number in CACHE_NAME with each version of new resources:

var CACHE_NAME = 'test-cache-v4';
var urlsToCache = ['./','./app.js'];

self.addEventListener('install', function (event) {
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then(function (cache) {
        console.log('Opened cache');
        return cache.addAll(urlsToCache);
      })
  );
  return self.skipWaiting();
});

self.addEventListener('activate', function(event) {
  event.waitUntil(
    caches.keys().then(function(keyList) {
      return Promise.all(keyList.map(function(key) {
        if (key !== CACHE_NAME) {
          return caches.delete(key);
        }
      }));
    }) 
  );
});

self.addEventListener('fetch', function(event) {
  event.respondWith(
    caches.open(CACHE_NAME).then(function(cache){
      return cache.match(event.request)
        .then(function(response) {
          if (response) {
          console.log('SERVED FROM CACHE');
            return response;
          }
          return fetch(event.request).then(function(response){
              console.log('Response from network is:', response);
              return response;
          });
        }
      )
    })
  );
});

On my localhost the service worker works perfectly fine.

But on the server the response/promise returned by

return cache.match(event.request)

is always undefined. As a result the resources are never served from cache but always from network.

This happens even though in Chrome's dev tools "Application" tab I can see that registration of the new service worker works fine and "Cache Storage" gets filled with my new cache resources (request URLs to index.html and app.js). I also think that I have handled the web server config tweaks correctly with Cache-Control no-cache.

If I change

return cache.match(event.request)

to

return cache.match(event.request.url)

the service worker also works on the server.

However, I would like to understand why the syntax that seems right to me and is used in all the examples I can find does not work on the server.

One difference is that I access localhost directly and the server over https.

EDIT: Another difference is that resources from the server are compressed (Header => content-encoding:gzip).

Could it be linked to one of these differences?

Any ideas are greatly appreciated!

like image 423
tobik Avatar asked Mar 15 '17 15:03

tobik


People also ask

What is the function of the match method in the Cache?

The match() method of the CacheStorage interface checks if a given Request or URL string is a key for a stored Response . This method returns a Promise for a Response , or a Promise which resolves to undefined if no match is found. You can access CacheStorage through the global caches property.

What is service worker Cache?

At a high-level, a browser follows the caching order below when it requests a resource: Service worker cache: The service worker checks if the resource is in its cache and decides whether to return the resource itself based on its programmed caching strategies.

What is Cache in Javascript?

The Cache interface provides a persistent storage mechanism for Request / Response object pairs that are cached in long lived memory.


2 Answers

1. no-cache

Try to cancel cache mechanism in your server, i found these code in my remote Apache server (served over HTTPS):

<filesMatch "\.(html|htm|js|css)$">
    FileETag None
    <ifModule mod_headers.c>
        Header unset ETag
        Header append Vary User-Agent env=!dont-vary
        Header set Cache-Control "max-age=0, no-cache, no-store, must-revalidate"
        Header set Pragma "no-cache"
        Header set Expires "Wed, 11 Jan 1984 05:00:00 GMT"
    </ifModule>
</filesMatch>

My localhost is not served over HTTPS, and the example How to Setup a Basic Service Worker works fine on it.

2. Match Method

Cache.match() - Web APIs | MDN shows that parameter should be the Request you are attempting to find in the Cache. But cache.match(event.request.url) works as well in localhost and remote Apache Server. Caching has no problem.

3. Gzip

After test in localhost(no gzip) and remote Apache server(gzip), both caching files without any error. So gzip is not one of the reason.

like image 80
Zuo Syuan Wang Avatar answered Oct 06 '22 01:10

Zuo Syuan Wang


I got the exactly the same problem: gzipped, server using https, local at localhost.

TL;DR:

return cache.match(event.request, {ignoreVary: true})  // set option `ignoreVary` to `true`

Full Version:

At this page Cache.match() - Web APIs | MDN and I found an option ignoreVary:

ignoreVary: A Boolean that when set to true tells the matching operation not to perform VARY header matching — i.e. if the URL matches you will get a match regardless of the Response object having a VARY header or not. It defaults to false.

And I checked my request's response, it does have a Vary field with a value Cookie, Accept-Language. The cache wouldn't match although the value of this field does not change each time I requested.

So the solution is adding {ignoreVary: true} to your cache.match()

like image 29
Kyan Avatar answered Oct 06 '22 01:10

Kyan