kwhitley / apicache

Simple API-caching middleware for Express/Node.
MIT License
1.24k stars 193 forks source link

Browser ignoring cleared cache group #218

Open ezeikel opened 4 years ago

ezeikel commented 4 years ago

I have a similar issue to https://github.com/kwhitley/apicache/issues/93 but setting {headers: { 'cache-control': 'no-cache' } } doesn't seem to solve it.

I have a GET request /sparks that gets all items in a collection and sets the apicachGroup:

req.apicacheGroup = `sparks-user-${req.user.id}`;

a GET request /sparks/:id that gets a specific item in that collection and sets the apicacheGroup:

req.apicacheGroup = `spark-${req.params.id}-user-${req.user.id}`;

and a PUT request /sparks/:id to update an item which clears both of the apiCacheGroup:

  apicache.clear(`spark-${req.params.id}-user-${req.user.id}`);
  apicache.clear(`sparks-user-${req.user.id}`);

However, when reloading the page the cache seems to be correctly invalidated for the GET to /sparks/:id and I get the fresh data but the GET to /sparks still has stale data.

Checking the cache index shows the the groups did exist and are removed correctly but while the group for a specific item is created again the group for all of the items isn't and the browser is still returning a cached version in the Network tab.

So even those the api has cleared the index the browser still has a cached version somehow? Not sure why it works for one request and not for another when they are pretty similar

My cache options are set like this:

const CACHED = apicache
  .options({
    headers: {
      "cache-control": "no-cache",
    },
    appendKey: (req) => (req.user ? req.user.id : null),
    debug: true,
    trackPerformance: true,
  })
  .middleware(CACHED_SERVER_TIME_S * 1000);
ezeikel commented 4 years ago

Have added a temporary fix by commenting out this in apicache.js's sendCachedResponse function:

    Object.assign(headers, filterBlacklistedHeaders(cacheObject.headers || {}), {
      // set properly-decremented max-age header.  This ensures that max-age is in sync with the cache expiration.
      'cache-control':
        'max-age=' +
        Math.max(
          0,
          (duration / 1000 - (new Date().getTime() / 1000 - cacheObject.timestamp)).toFixed(0)
        ),
    })

Now the browser asks the server every time and if the server cache has it then it returns it, so not as quick but better than having no caching at all.

Having this does make sense and I want to add it back but it seems I'm getting inconsistent behaviour as explained above and I don't know why.

The only thing I can think of is that when the PUT request to a single resource happens the server knows that a resource has been updated and the browser cache gets ignored when a GET request for that resource is made. But a GET request for all of those resources doesn't know that a single resource has been updated so returns the browser cached version?

ezeikel commented 4 years ago

Have done some more digging around I believe it's because the Etag doesn't change for GET request to /sparks even though a resource has changed. Now wondering how you get the Etag to change for a collection where one of its resources has been updated

kganczarek commented 9 months ago

@ezeikel Sorry for digging this up, have you found the solutions for this?