- From: Danielle Church <notifications@github.com>
- Date: Sat, 23 Dec 2023 16:52:30 -0800
- To: w3c/ServiceWorker <ServiceWorker@noreply.github.com>
- Cc: Subscribed <subscribed@noreply.github.com>
- Message-ID: <w3c/ServiceWorker/issues/1699@github.com>
I'm currently developing a browser game that operates as a single-page web application, distributed as a GitHub pages site. There is no build step, as everything is written in pure HTML/SVG, CSS, or JS, and so the GH pages source can simply be the HEAD of the repository. However, since this is a build-free repo, this means that all the subresources are fetched individually, and thus individual script files can end up being out-of-sync, if some of them are retrieved from cache and others from the network. I've lost count of the number of bug reports I've gotten for weird one-off failures caused by out-of-sync resources or the number of times I've had to instruct my players on how to do a full ctrl-shift-R reload, so I decided that in order to fix the issue once and for all, I'd write a ServiceWorker that could monitor the requests and ensure that the player is using a _consistent_ snapshot of the codebase, and at the same time I could cache the game's resources so that players could continue to play even while offline. I've run into trouble with the caching functionality, however. I'm using [RFC9111 4. Constructing Responses From Caches](https://httpwg.org/specs/rfc9111.html#constructing.responses.from.caches) as a behavior guide, but I'm running into unexpected trouble implementing [4.3.1. Sending a Validation Request](https://httpwg.org/specs/rfc9111.html#validation.sent), which begins: > When generating a conditional request for validation, a cache either starts with a request it is attempting to satisfy or — if it is initiating the request independently — synthesizes a request using a stored response by copying the method, target URI, and request header fields identified by the Vary header field ([Section 4.1](https://httpwg.org/specs/rfc9111.html#caching.negotiated.responses)). Unfortunately, I'm completely unable to do that. When my service worker receives a `FetchEvent`, the request headers are immutable, even if I've created a new Request object using `new Request(evt.request)`, and so I can't add `If-None-Match` or `If-Modified-Since`. On the other hand, if I try to create an empty Request object, I can't copy whatever headers might have been specified in the Vary of the cached response, because the incoming Request's headers are shielded and can't be retrieved for security/privacy reasons. Even worse, the failure was entirely silent. The following _should_ have been a conservative but functional cache-verification function: ```js async function verifyCacheResult(req, cacheResult) { const origReq = req; req = new Request(req); if (cacheResult.headers.has("etag")) { req.headers.append("If-None-Match", cacheResult.headers.get("etag")); } if (cacheResult.headers.has("last-modified")) { req.headers.append("If-Modified-Since", cacheResult.headers.get("last-modified")); } const response = await fetch(req.clone()); if (response.status === 200) { // New response, update cache const cache = await caches.open("direct"); cache.put(origReq, response); } return response; } ``` And for a time, it seemed like it was working, until I happened to notice while I was tracking down a different issue that I was getting _way_ too many cache updates. It took me two days of research to discover that the problem was that `req.headers` has an entirely invisible attribute called "guard", which cannot be interrogated by JavaScript, and which causes the call to `req.headers.append()` to fail _silently_. I'll note that this is mentioned nowhere on the [MDN page for Headers.append](https://developer.mozilla.org/en-US/docs/Web/API/Headers/append), not even in passing, but that's obviously an issue the MDN team ought to be solving. As a result, not only does my service worker implementation not improve on the browser's own HTTP cache, it makes things _significantly_ worse, as _every single subresource_ is subjected to an unconditional HTTP fetch on every single load of the page. If I hadn't noticed the issue - which, again, generated no errors, which is apparently allowable by spec (???) - I would have been responsible for a very small-scale DDoS of Github, as every player's browser would attempt to revalidate every subresource, and the server would report that every single subresource had been modified, which would trigger a transparent reload in the player's browser, at which point the service worker would attempt to revalidate the newly-cached responses (which were the same as the old cached responses in the first place)... I've put the service worker implementation on hold for now, as there are other features I can work on that now seem more likely to bear fruit, but given that Service Workers are explicitly intended and touted as a programmable browser-cache system, why can't I set conditional-request headers on outgoing requests? How am I supposed to do this and still provide proper semantics for cache validation? -- Reply to this email directly or view it on GitHub: https://github.com/w3c/ServiceWorker/issues/1699 You are receiving this because you are subscribed to this thread. Message ID: <w3c/ServiceWorker/issues/1699@github.com>
Received on Sunday, 24 December 2023 00:52:37 UTC