Attach query string to URL


#1

I am writing a custom cloudfllare worker to attach Country to a query string of url and redirect to it. I have written below code and it seems like it redirects 2 times after refreshing same URL

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

/**
 * Fetch and log a request
 * @param {Request} request
 */
async function handleRequest(request) {
  const countryCode = request.headers.get("cf-ipcountry")
  let url = new URL(request.url);

  if(url.toString().indexOf("?") == -1)
    url += "?country=" + countryCode;
  else
    url += "&country=" + countryCode;

  return fetch(url);
}

Can you guys tell me what i am doing wrong here?


#2

@Matteo is pretty good at this. Maybe he can spot the problem.


#3

I sincerely don’t know why it redirects you twice since it’s not even done in the Worker, I would assume something origin related. When you send the request with the search parameter of the country code, what are you returning? Could it be it redirects to HTTP and/or the wrong subdomain (say www instead of root or viceversa).

I would also change the let to var, it shouldn’t matter much since it actually works (don’t have any way to try), but it doesn’t usually let you change the variable with let.


I will point out a few things things though.

  1. As it says here (https://developers.cloudflare.com/workers/reference/request-attributes/) you have a direct variable with the same value as CF-IPCountry, I would use that directly.
  2. In a URL you already have the array of searchParams, use that removing the need for the if statement that can cause issues with specific URL (you may never know what people add in the URLs). More info: https://developer.mozilla.org/en-US/docs/Web/API/URL/searchParams
  3. Does it make sense to go to the origin to do the redirect instead of directly in the worker?

#4

I am still not very familiar with workers, but could there be a chance this script enters a loop? It basically appears to send requests to itself with that fetch on the very same URL where it was invoked, does it not?

You are referring to const, let is just fine in this context.


#5

No, not really the worker adds the search parameter and then goes to the origin.

Yeas, I know, but technically the `url += […]’ part is replacing the variable which isn’t supported by let. You can change the original variable, but not redeclare it.


#6

So basically any reference in a worker to a proxied host goes straight to the origin, skipping the worker?!

True, you cannot redeclare it, but that is not what += does. Just run the following snippet

let x = 1;
x += 1;

#7

I don’t exactly know that, I suppose that a request from a Worker running on domain example.com will skip the Worker, if it goes to example-2.com which has Workers enabled will not. Don’t know if it’s limited to the domain or additionally to the host (so www.example.com would pass through the Worker for example.com).

Yeah, it’s doing x = x + 1…


#8

Try like this:

addEventListener('fetch', event => {
    event.respondWith(handleRequest(event.request))
  })
  
  /**
   * Fetch and log a given request object
   * @param {Request} request
   */
  async function handleRequest(request) {
    let url = new URL(request.url);
    let params = new URLSearchParams(url.search.slice(1));
    params.append('country', request.headers.get("cf-ipcountry"));
    url.search = params; 
    return fetch(url)
  }

#9

@adaptive I tried it but now it works fine in CF debugger but when i deploy it and open URL in my browser it does not have any country flag appended to it

Any ideas about this?


#10

It will append between worker and origin, you shouldn’t see anything in the browser.
Don’t you see that on your server logs?


#11

Slightly hijacking the thread but one thing I am surprised by is the fact the geolocation header is present in the request object, as that header is usually a response header.

My understanding is the request object is what the client sent, so it should not contain that header. Or is the request object, in the case of a worker, enriched with further header of this type?


#12

#13

Going further off-topic, Geo should be in the request header so the server can get some use out of it. There’s not much point in telling the user where they’re coming from. :wink:


#14

My bad, you are absolutely right. I believe I confused it with some the response header (possibly my beloved TLS status :smile:)

Apologies :slight_smile:


#15

@adaptive The reason for appending it in the query string is that i need to switch view on front end
My app is JavaScript app no backend so i need that Country code to switch view between different countries
Is it possible that my front end JavaScript app can query this query string from URL and get country data?


#16

Instead, run a worker that will generate a JSON with your user country.

Your Javascript app fetches that JSON data, to a variable.

Another way will be to append the via a worker to .js file
conts user_country = 'XX'


#17

@adaptive Not sure but i think this will overwrite the actual API response with JSON data
I don’t want to effect existing API calls and JSON responses.
Will it work in paralell with existing logic?

Just wanted to let you know about my current infra
i am using S3 to host a static website and domain is coming through CF. What i see now is when i run the script in debug mode it appends country fine with URL but when i access the URL in my browser it does not append anything to it

Here is the updated script i have written

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

/**
 * Fetch and log a request
 * @param {Request} request
 */
async function handleRequest(request) {
  const countryCode = request.headers.get("cf-ipcountry")
  let url = new URL(request.url);
  let suffix = '';
  
  const init = {
      method: request.method,
      headers: request.headers
  }

  let subf = url.pathname.split("/")[1];
  const filteredPaths = ["js", "css", "img", "media", "fonts"];

  if(filteredPaths.indexOf(subf) != -1)
    return fetch(request);

  if(request.url.toString().indexOf("?") == -1)
    suffix = "?country=" + countryCode;
  else
    suffix = "&country=" + countryCode;

  url += suffix;
  const modifiedRequest = new Request(url, init)
  console.log(url);
  return fetch(modifiedRequest);
}

Can you pinpoint the issue in the script?


#18

@matteo let doesn’t prevent reassignment, only redeclaration.

let x = 1;
x = 'hello';

is fine. You might be thinking of const, which would TypeError.

@hassnain-alvi In order to tell the browser that the response served was at a different URL (you want to modify the URL after the request was made) you have to redirect. If you just want to see the country code in the response for debugging, you can add it as a header.

Example of adding header to the response:

    url += suffix;
    const modifiedRequest = new Request(url, init);
    console.log(url);

    // Grab response
    let response = await fetch(modifiedRequest);

    // Copy the response so you can modify headers
    response = new Response(response.body, response);

    // Add your header
    response.headers.set('My-Special-Header', suffix);

    return response;