2

I’ve now spent countless hours trying to get the cache API to cache a simple request. I had it working once in between but forgot to add something to the cache key, and now its not working anymore. Needless to say, cache.put() not having a return value that specifies if the request was actually cached or not does not exactly help and I am left with trial and error. Can someone maybe give me a hint on what I’m doing wrong and what is actually required? I’ve read all the documentation more than 3 times now and I’m at a loss…

Noteworthy maybe is that this REST endpoint sets pragma: no-cache and everything else cache-related to no-cache, but i want to forcibly cache the response anyway which is why I tried to completely re-write the headers before caching, but it still isn’t working (not matching or not storing, no one knows…)

async function apiTest(token, url) {
    let apiCache = await caches.open("apiResponses");
    let request = new Request(
        new URL("https://api.mysite.com/api/"+url),
        {
            headers: {
                "Authorization": "Bearer "+token,
            }
        }
    )
    // Check if the response is already in the cloudflare cache
    let response = await apiCache.match(request);
    if (response) {
        console.log("Serving from cache");
    }
    if (!response) {
        // if not, ask the origin if the permission is granted
        response = await fetch(request);
        // cache response in cloudflare cache
        response = new Response(response.body, {
            status: response.status,
            statusText: response.statusText,
            headers: {
                "Cache-Control": "max-age=900",
                "Content-Type": response.headers.get("Content-Type"),
            }
        });
        await apiCache.put(request, response.clone());
    }
    return response;
}

Thanks in advance for any help, I've asked the same question on the Cloudflare community first and not received an answer in 2 weeks

1 Answers1

2

This might be related to your use of caches.default, instead of opening a private cache with caches.open("whatever"). When you use caches.default, you are sharing the same cache that fetch() itself uses. So when your worker runs, your worker checks the cache, then fetch() checks the cache, then fetch() later writes the cache, and then your worker also writes the same cache entry. Since the write operations in particular happen asynchronously (as the response streams through), it's quite possible that they are overlapping and the cache is getting confused and tossing them all out.

To avoid this, you should open a private cache namespace. So, replace this line:

let cache = caches.default;

with:

let cache = await caches.open("whatever");

(This await always completes immediately; it's only needed because the Cache API standard insists that this method is asynchronous.)

This way, you are reading and writing a completely separate cache entry from the one that fetch() itself reads/writes.

The use case for caches.default is when you intentionally want to operate on exactly the cache entry that fetch() would also use, but I don't think you need to do that here.


EDIT: Based on conversation below, I now suspect that the presence of the Authorization header was causing the cache to refuse to store the response. But, using a custom cache namespace (as described above) means that you can safely cache the value using a Request that doesn't have that header, because you know the cached response can only be accessed by the Worker via the cache API. It sounds like this approach worked in your case.

Kenton Varda
  • 41,353
  • 8
  • 121
  • 105
  • Unfortunately that did not solve the issue, the request is still never served from the cache. I updated the code in the question with the usage of a private cache like you suggested. – user13737110 Jun 13 '20 at 16:12
  • Maybe some more clarification: I'm basically trying to cache (mostly) the response code of the API using a specific authorization header which I derive from a cookie in an image request to check if the user is allowed to see the image, and cache everything along the way to reduce load on the backend and to improve site loading times. – user13737110 Jun 13 '20 at 16:14
  • Got it to work by using a string cache key again instead of the request object i was trying to use - unfortunately still don't know why, but I assume this wouldn't have worked without your answer so I am going to accept it either way. – user13737110 Jun 13 '20 at 16:29
  • @user13737110 Interesting, using a string key should be equivalent to using a `Request` object constructed from that string with no headers. So the only difference between that and the code you posted is the presence of the `Authorization` header. It might be the case that the presence of an `Authorization` header causes Cloudflare's cache to decide that the response is uncacheable. Such a rule makes sense for normal HTTP requests but maybe not when using the cache API. I will file an issue to look into this... but it sounds like you have a solution that works for your case. – Kenton Varda Jun 14 '20 at 15:48
  • Yes, that might be the case. I "solved" it by using the request URL as cache key, appending the token from the auth header as a fake query param. I hoped though that the Authorization header in a Request object will make for a unique cache key, as the docs only mention Set-Cookie headers making a response uncachable. – user13737110 Jun 15 '20 at 12:40
  • @user13737110 Unfortunately, the cache key is based only on the URL. The HTTP standard says that if some headers are relevant to the cache key, then the `Vary` header on the response specifies those headers. But it's very hard to implement this effectively, because obviously we don't know about the response `Vary` header at lookup time, so the cache would have to first look up the `Vary` for the URL and then do a second lookup for the actual response. Instead, Cloudflare doesn't implement `Vary` right now... But usually you can do better in a worker anyway. – Kenton Varda Jun 16 '20 at 14:22