Progressier handles opaque responses automatically so that you don't have worry about them. But if you're interested in learning more about opaque responses, why they're a problem and how you should deal with them in your service worker, please continue reading.
What's an opaque response?
When a website requests an asset, e.g. a JPG image or a JavaScript file, it sends a request to a server. This server then responds with the requested image or JavaScript contents.
Problems start when the asset is hosted on a different domain than the requesting site. Browsers follow a mechanism called Cross-Origin Resource Sharing (or CORS). In a nutshell, it greatly limits what you can do with a resource located on a different domain.
One way to prevent issues is for the owner of the resource to add a access-control-allow-origin: *
header to the response (also works with your specific domain instead of *
). It essentially tells the browser hey Chrome, just let anyone freely use that resource on their site.
Here is where it gets interesting — even without this header, <script>
, <style>
or <img>
tags will still be able to request and use these resources. But your Javascript code or service worker won't be allowed to read or otherwise modify them. Responses that the browser can use but that you as a developer cannot are called opaque responses.
So... what's the problem with opaque responses, exactly?
When a server sends a response to a browser, it also sends a HTTP response status code, which tells the browser whether the request was successful or not.
Anything starting with 2xx
usually means success. 5xx
means there was an error with the server. And 4xx
an error with the request.
For opaque responses, the response status code is always 0
.
Your service worker has no way to know whether a request was successful or whether it resulted in an error. And because the request is made completely opaque, it does not contain any other clues that can tell you which way it went.
Editor's Note: I understand why opaque responses are necessary, but I honestly have no idea why 'opaque' also has to mean hiding the request status code. If anyone knows, I'm genuinely interested in learning more about the reasoning behind this choice.
The problem is that most apps and websites do receive quite a few opaque responses. And if you choose not to cache them, these resources won't be accessible offline at all when the network fails to send a successful response.
Approach 1: The Last Resort Approach
The approach we use at Progressier is to cache resources that have opaque responses but only serve them as a last resort — when there is no other response available in cache and the network couldn't provide a response (because that server is down or the user is offline for example).
In that case, the choice is between showing an error (100% probability of error) versus showing an opaque response that may or not have been a successful response (unknown probability of error but by definition less than 100%).
Approach 2: The No-Cache Approach
The default behavior in Workbox is to not cache opaque responses at all. This is another valid approach. It eliminates the uncertainty described above and it also ensures that the resource will NOT be available offline.
In cases where your front-end actually need to know what the error is (not just know that there is an error), this approach provides consistency and may be a better alternative.
Recognizing and caching opaque responses
An opaque response can be identified by the status code in the Response object. If it contains response.status === 0
, then you're dealing with an opaque response.
Note that what you can do with opaque responses caching-wise is limited. You'll necessarily have to call response.clone() before putting it in cache. If you don't, the body of the response will be consumed when you put in cache and it will fail when you also return it as a response to the fetch event.
Response.clone() sometimes isn't enough, i.e. if you need to alter the response before putting in cache. That's why you can usually make a copy of the headers and body of the request, and recreate a response from scratch using the Response constructor.
Well, with opaque responses... you can't do that. The constructor simply doesn't allow to create opaque responses.
Conclusion
When building Progressier's caching strategy builder, I had quite a bit of fun figuring out the inner workings of service workers. And opaque responses were definitely one of the highlights! Their quirks can be hard to grasp at first, but once you understand how they behave, dealing with them becomes a lot easier.
If that article helped you understand anything about opaque responses, leave a comment. And if you have any questions, feel free to reach out to me 💪🎉😜
Top comments (2)
It doesn't solve the problem completely, but depending on what sort of resource you're interested in, you could use HTML error events - developer.mozilla.org/en-US/docs/W.... So you intercept the fetch event, you cache an opaque response, serve it as a result of the fetch event and then in the element that uses this resource as its "src" would trigger an onError event if that resource is dodgy. Then you can finish the loop and the onError listener could remove the request-response from the cache. Not very straight forward; what do y'all think?
Amazing eye-opener article on opaque responses, thanks a lot. 👍