Workers to support caching of JSONP requests

Hi,

JSONP requests can’t be easily cached by Cloudflare, as each request contains a unique callback function name.

I was wondering if we could use Workers, to rewrite a JSONP request and replace the random callback name with a predefined one.

Then workers would make the request with that predefined callback name, taking advantage of Cloudflare caching (since the url with predefined callback name will be repeated multiple times) and then rewrite the response, by replacing the the callback name back to the original callback name, in the response body.

Does it sound doable with workers? Has anyone done anything similar?

So basically you want to replace a string in an HTTP response body?! That should be possible.

However I am not quite sure what you meant by taking advantange of Cloudflare caching as you most likely wouldnt want these responses to be cached, would you? Also, considering you control the domain I’d assume you control the content as well, so wouldnt it be easier to change the JSONP output in the first place?

Thanks for your reply Sandro.

I’m thinking about the following scenario:

  1. Browser sends JSONP request to Cliudflare with random callback=xyz1234
  2. Cloudflare worker replaces callback=xyz12345 with callback=always_the_same
  3. Cloudflare worker then passes the modified request down to the end API,
    but via Cloudflare caching. There is a cache MISS, but the response is cached.
  4. Cloudflare worker replaces ‘always_the_same’ with ‘asdf567’ in the response body.
  5. Browser sends another JSONP request to Cliudflare with random callback=asdf567
  6. Cloudflare worker replaces callback=xyz12345 with callback=always_the_same
  7. Cloudflare worker then passes the modified request down to the end API,
    but via Cloudflare caching. There is a cache HIT, and the previous response is returned.
  8. Cloudflare worker replaces ‘always_the_same’ with ‘asdf567’ in the response body and returns the response to the browser.

The second request never touches the end API, and the response is retrieved from Cloudflare cache, despite the callback param was different than in the first JSONP request.

Hang on a second :slight_smile: Was this a typo or did you actually mean “browser”? JSONP is sent from the server to the client, not the other way round. If you control the server you should also be able to control the function name, right?

I’m sorry if I wasn’t clear :slight_smile:

The browser sends JSONP request to the server. The request is sent to the server through Cloudflare. Server responds with JSONP response to the client.

I can control the function name on the server, yes.

The thing is that the client expects the same function name, as the one defined by “callback” parameter in the URL. It also needs to be unique.
Having unique callback parameter makes the URL difficult to cache on Cloudflare, since each URL is different.
I’d like to use workers to unify the callback parameter to some standard value. Then use the url with normalized value to take advantage of Cloudflare cache.
Cloudflare cache will return a response body with a callback name different than the one that client expects, therefore the worker will have to replace it before returning it to the client.

I’ll try to write some diagram if it’s still not clear.

So the problem is the clients request different function names which changes the URL, precisely the query string, and that tampers with your caching?

In this case changing the setting of https://support.cloudflare.com/hc/en-us/articles/200168256-What-are-Cloudflare-s-caching-levels- might be the better solution. Have you already looked into that?

Yes, that is exactly the problem. I’m sorry for not being clear earlier.

I have used https://support.cloudflare.com/hc/en-us/articles/200168256-What-are-Cloudflare-s-caching-levels-

There are two problems:

  1. I want the cache to take into account the query params, apart from the callback param, not just the path.
  2. The previously cached response body is going to have the wrong callback function name, than the one passed via callback parameter.

Alright, in that case you might really need a worker.

What you could probably do is have the server return a JSONP response with a static function name, which you then dynamically replace in the worker with the name passed in the query string.

Alternatively, and assuming that response rarely changes, you could generate the entire JSONP response from within the worker.

The steps you outlined in your comment are fine. Using a Worker script to intercept multiple different requests (with varying querystring params) and redirect them to the same cached response is a good use case.

Thank you.

I can return a static function name but I’d like Cloudflare to cache that response.

If the worker rewrites the url and replaces the callback parameter value to a static value, then the server will simply return that static function name.

Where could I find some examples of workers doing something similar?

Thank you @manigandham. Do you know where could I find an example or a worker that does something similar?

If you generate the entire response in the worker, without contacting your origin, you wouldnt need to worry about caching in the first place. Would that be an option?

Unfortunately only the server knows what to return.

@user137

https://Cloudflareworkers.com has a sample that shows how to find/replace the contents of a request. Here’s the code updated for your jsonp situation below.

The first check is important to make sure you don’t end up in an infinite loop, or you can use a different route for your Worker public endpoint and your origin. I assumed it’s the same here to keep it simple.

You can also go further by sorting the query params alphabetically to increase cache hits since Cloudflare uses the entire URL as the cache key. I’ll leave that to you.

addEventListener('fetch', event => { event.respondWith(handle(event.request)); });

async function handle(request) {
    const url = new URL(request.url);

    // check for the path and querystring you need
    if (url.pathname.startsWith('/someapi') && url.searchParams.get('callback') !== 'standard_jsonp_name') {
        return standardizeJsonp(request);
    }

    return fetch(request);
}

async function standardizeJsonp(request) {

    const url = new URL(request.url);
    const originalName = url.searchParams.get('callback');

    url.searchParams.set('callback', 'standard_jsonp_name');
    const response = await fetch(request);
    const text = await response.text();
    const modified = text.replace('standard_jsonp_name', originalName);

    return new Response(modified, {
        status: response.status,
        statusText: response.statusText,
        headers: response.headers
    });
}

That would have been my assumption too, but considering you really want it cached I understood it rarely/never changes. If that is the case you can easily generate it directly with the worker and simply adapt that code, should there be any changes necessary.

@sandro

I’m not sure why you’re so insistent on generating it in the worker. @user137 has said that it needs to be generated on the server because it probably has access to data and other resources that the Worker script doesn’t. They also state that the origin uses other query params to generate the response so it’s clearly not completely static but cached per unique querystring.

Cloudflare as a CDN is already very good at caching, but since it uses the entire URL as the key, this is a perfect use case for Workers to avoid a cache miss on every request when the only difference is the callback parameter.

A similar feature is the Querystring Sort feature on the Enterprise plans which increases cache hit rate by making the alphabetically sorting the querystring, something which can now be done with Workers as well.

Excuse me?

Apart from having it mentioned twice I am not “insistent”, I mentioned it as viable alternative. Whether the OP choose that path is up to him. I am not quite sure what your problem here is.

That is your interpretation.

Not unique query string, as you have to exclude the mentioned parameter and also need to take into account the order (what you mentioned).

If that is the case I’d reconsider the approach to be honest. Either something is reasonably static or it is not. Mixing these two can be error prone and a nice source of issues.

I don’t see how something being easily cached means it can also be easily generated elsewhere. Either way it’s about normalizing a single variable to avoid cache misses. It’s not error prone but a standard scenario. Normalizing accept-encoding and user-agent headers is also commonly done for the same reason.

It seems the poster stated it wasn’t viable to generate in the worker so I wanted to reduce confusion. No need to argue.

That depends on the use-case and - as I mentioned - my understanding was it is a static response.

Caching itself is error prone (this forum is living proof of that :wink: ), if you add to this mixture string comparisons and values in a seemingly random order you can easily be asking for trouble.

That could have been based on a misunderstanding and that was what I wanted to clarify. If his use-case requires it to be generated on the server, he will need to generate it on the server. My goal was to show an alternative approach that might simplify his implementation and minimise the point of failures, if applicable.

Yes.

To clarify, the response can’t be generated by the worker as it depends on the data available on the server. It can change over time and responses will be cached only for a few minutes.