question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

maxAge of 0 results in no cache-control header

See original GitHub issue

Problem

We’re facing issues with persisted queries using GET requests and IE11. This lovely browser is serving responses without a cache-control header from its cache. While this is ok for most of our current application’s queries, some queries should not be cached.

We consider leaving out cache-control headers for a maxAge setting of 0 a bug. Also the scope is ignored while Cache-Control: private would be a totally reasonable header.

Workaround

A workaround for us is to annotate specific types with a maxAge of 1 second, which is ugly but works for these kind of requests (we don’t expect the user to be faster, but in case he is, it results in glitches).

Furthermore, this workaround for types containing other types is not that easy: as soon as another type without a cache hint is added to a type, the cache-control header is no longer sent to the browser because the default maxAge is 0 and therefore lower than 1. We need to annotate all types used in a type as well, which results in a mess: some types by itself might benefit from a different cacheControl settings in certain situations.

This code is responsible for leaving out the headers for situations when the lowest maxAge is 0: https://github.com/apollographql/apollo-server/blob/821578775d3ca62ac33a76a8d3d8f51f412cdd80/packages/apollo-cache-control/src/index.ts#L171-L178

Possible solution

A possible solution for our case would be to still send headers in case the lowest maxAge is 0.

I’d be happy to submit a PR for a proper solution that enables consumers of apollo-server to specify a maxAge of 0 or scope only if this is a reasonable approach.

However, it seems more cache requirements are out there (eg. the old and long untouched #1295 as well as #1424, which might also work for our situation) so this solution might not fit to the overall roadmap. Unfortunately for us #2360 doesn’t mention any plans for cacheControl.

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Reactions:10
  • Comments:15 (6 by maintainers)

github_iconTop GitHub Comments

9reactions
molombycommented, Feb 13, 2020

This is a pretty serious bug, no?

Specifying Cache-Control: private, max-age: 0 is completely valid – it tells shared caches (like CDNs) to exclude the resource, allows private caches (like the browser) to store the resource but instructs them to validate its freshness before using it. This is very different than not returning a cache control header at all which, I believe, is the current behaviour.

At best this incorrectly conflates these two directives, causing sub-optimal caching, at worst it allows information (that has been explicitly marked as private) to be leaked/shared between requests from different users.

If i’m understanding correctly, due to the way cache hints from multiple queries are combine (by min’ing the max ages), it’s possible that adding a field to a query can cause the cache control header to be dropped, even if all the existing fields in the query are private.

Eg. with this type:

type User {
  id: ID
  name: String
  email: String @cacheControl(scope: PRIVATE)
  notes: String @cacheControl(scope: PRIVATE, maxAge: 0)
}

This query will return with a cache control header of Cache-Control: private:

query {
  User (id: "123") { id name email }
}

But if you also hit the notes field, the max age of 0 takes precedence and no cache control header is added:

query {
  User (id: "123") { id name email notes }
}

This implicitly makes the entire result cacheable, even though it’s pretty clear the developer intended the opposite.

2reactions
liamchildscommented, Jul 1, 2021

I came here with the same problem and while there is still no fix I thought I would share my “workaround” that can be adapted. The following works as an apollo plugin which sets the cache control header if it hasn’t already been set.

export class CacheControlFallbackPlugin<TContext> implements ApolloServerPlugin<TContext> {
  public requestDidStart(): GraphQLRequestListener<TContext> {
    return {
      willSendResponse(requestContext: GraphQLRequestContextWillSendResponse<TContext>): ValueOrPromise<void> {
        const existingCacheHeader = requestContext.response.http?.headers.get('Cache-Control');
        if (!existingCacheHeader) {
          // This can be tailored....
          requestContext.response.http?.headers.set('Cache-Control', 'no-store, max-age=0');
        }
      },
    };
  }
}
Read more comments on GitHub >

github_iconTop Results From Across the Web

What's the difference between Cache-Control: max-age=0 and ...
Squid Cache, by default, seems to never store anything with a no-cache header, just like Firefox. My advice would be to set public,max-age=0...
Read more >
Why set max-age=0 if no-store is already set?
A result of a recent pentest suggested that the HTTP Cache-Control Header max-age=0 should be set when no-store is set.
Read more >
Cache-Control - HTTP - MDN Web Docs
The Cache-Control HTTP header field holds directives (instructions) ... max-age=0 is a workaround for no-cache , because many old (HTTP/1.0) ...
Read more >
Server-side caching - Apollo GraphQL Docs
The response's maxAge equals the lowest maxAge among all fields. If that value is 0 , the entire result is not cached. The...
Read more >
Setting Cache control headers for common content types ...
When you revisit that Web page, there is no need to re-download such components. This results in a faster Web page load. Browser...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found