RnMss
RnMss

Reputation: 3853

How can I make the browser cache a web resource that doesn't have a permanent URL?

The Problem

The web page needs to download several files, say ${some_id}.file. But

If the front-end simply does:

fetch(`https://some.cloud.com/${some_generated_token}`)

It will have to download every time the page get refreshed because the URL is changing so it cannot use those e-tag stuff for caching, and the users won't like it.


Approach 1

Write my own file server so the front-end downloads from https://my-server.com/download/${some_id}.file.

The server checks the user's permission before the download. And if the check passes, send redirect (302 moved temporarily) to the real downloading url https://some.cloud.com/${some_generated_token}, otherwise respond with error.

Maybe I should put an etag header in the response, copied from the real one. I'm not sure if this will work. Is it possible to make the browser cache files that are downloaded from redirected URLs?

Approach 2

Manually implement a caching system for the client side, which downloads the real file under the hood when necessary. So the file are actually loaded from cache rather than from the cloud. Files are identified by something like ${some_id}_${checksum}.

The downside might be that I have to manage the storage manually. (So either I have to bother the user with another setting to decide the capacity, or the browser decide this and I have no way to negotiate. I'm not sure about this.)


So what is the best / possible way to do that? And how to implement it?

Upvotes: 0

Views: 558

Answers (3)

Someone Special
Someone Special

Reputation: 13598

If you want security, then it's either the cloud service is "smart" (server side checking), or your frontend is "smart" (own caching own checks by query strings).

There is no easy way to wanting security, and yet, cheap "unsmart" service.

One of the solution is using fetch(url, { header: { Authorization: token}}) then use service like cloudfront.

your url remains unchanged so likely it will gets cached. Authorization header changes, however if you use jwt token, the token validity is very long, so likely it doesn't change unless you logout/login.

E.g. Cloudfront-like services

In the case of cloudfront for example, you can forward header authorization code to your origin to request for file, and you can choose to cache the header.

User request -> Cloudfront -> Forward authorization header to origin -> origin check and return file.

If you want savings on external bandwidth, you can use Lambda as origin to check authorization code + retrieve from s3 to be served from cloud front, aws internal bandwidth is mostly free.

lambda -> retrieve from s3 -> return to cloudfront.

Caching content based on request headers

Do note however, it's not 100% secure.

disclaimer: If you are putting files on the cloud, without a one time signedUrl, no matter what you do, it's not secure.

Upvotes: 1

hackape
hackape

Reputation: 19997

I recommend approach 2, client side caching, if the id of resource can be resolved/informed somehow on the client side.

Service Worker API can be helpful to your use case. And the specific section, quoted from MDN:

Your service worker can respond to requests using the FetchEvent event. You can modify the response to these requests in any way you want, using the FetchEvent.respondWith() method.

Example service worker code:

function deriveStableUrl(request) {
  // somehow decide a stable URL for the varying token URL for the same resource
  // note that, this URL is not for network fetching, but merely a key to index the underlying resource in the cache. 
  return stableUrl;
}

addEventListener('fetch', event => {
  event.respondWith(async function() {
    // Open 'default' cache object, or you can decide the name of your choice. 
    const cache = await caches.open('default');
    // Try to get the response from a cache.
    const url = deriveStableUrl(event.request);
    const cachedResponse = await cache.match(url);
    // Return it if we found one.
    if (cachedResponse) return cachedResponse;
    // If we didn't find a match in the cache, use the network.
    return fetch(event.request).then(resp => {
      // Cache it with the stable URL as key. 
      cache.put(url, resp);
      return resp;
    })
  }());
});

Upvotes: 1

EricSchaefer
EricSchaefer

Reputation: 26380

Approach 3 (smart proxying): Provide a stable URL on your own host (where the front-end is served from), passing the token in a header, and have that end-point pull the file from the cloud system (using the token) and forward it to the front-end. This would only make sense if A) the file size is not so big, that it inflicts significant cost to your own systems transfer volume and B) that you do actually have a back-end to pull this off from.

Upvotes: 1

Related Questions