Get Cloudflare's HTTP_CF_IPCOUNTRY header with javascript?
Asked Answered
W

5

15

There are many SO questions how to get http headers with javascript, but for some reason they don't show up HTTP_CF_IPCOUNTRY header.

If I try to do with php echo $_SERVER["HTTP_CF_IPCOUNTRY"];, it works, so CF is working just fine.

Is it possible to get this header with javascript?

Weldon answered 8/9, 2015 at 10:20 Comment(3)
Which javascript do you mean - client or server-side (Node)?Grow
Added web-worker tag due to my answer. Hope you don't mind :-)Weirick
You can access using: cloudflare-quic.com/b/headers or cloudflare.com/cdn-cgi/trace Ref: cloudflare-quic.com/b and github.com/fawazahmed0/cloudflare-trace-apiAutochthonous
H
8

Assuming you are talking about client side JavaScript: no, it isn't possible.

  1. The browser makes an HTTP request to the server.
  2. The server notices what IP address the request came from
  3. The server looks up that IP address in a database and finds the matching country
  4. The server passes that country to PHP

The data never even goes near the browser.

For JavaScript to access it, you would need to read it with server side code and then put it in a response back to the browser.

Hild answered 8/9, 2015 at 10:22 Comment(2)
Sorry but in the context of the question this misleading. The Cloudflare network already does the work for you (it knows what country people are in) and passes it in the CF_IPCOUNTRY header. So while your methodology is correct steps 2 and 3 should just say Read the HTTP_CF_IPCOUNTRY header. If you try to find the country from IP alone it's a big PITA and subject to change all the time. Cloudflare's header should be much more accurate.Weirick
@Weirick — "The server" in this case is "The cloudflare server" (because that is the one that the browser is communicating with). The fact step 4 involves an extra HTTP request from the cloudflare server to the server running PHP isn't relevent to the problem (which is about getting the information client-side).Hild
F
12

@Quentin's answer stands correct and holds true for any javascript client trying to access server header's.

However, since this question is specific to Cloudlfare and specific to getting the 2 letter country ISO normally in the HTTP_CF_IPCOUNTRY header, I believe I have a work-around that best befits the question asked.

Below is a code excerpt that I use on my frontend Ember App, sitting behind Cloudflare... and varnish... and fastboot...

function parseTrace(url){
    let trace = [];
    $.ajax(url,
        {
            success: function(response){
                let lines = response.split('\n');
                let keyValue;

                lines.forEach(function(line){
                    keyValue = line.split('=');
                    trace[keyValue[0]] = decodeURIComponent(keyValue[1] || '');

                    if(keyValue[0] === 'loc' && trace['loc'] !== 'XX'){
                        alert(trace['loc']);
                    }

                    if(keyValue[0] === 'ip'){
                        alert(trace['ip']);
                    }

                });

                return trace;
            },
            error: function(){
                return trace;
            }
        }
    );
};

let cfTrace = parseTrace('/cdn-cgi/trace');

The performance is really really great, don't be afraid to call this function even before you call other APIs or functions. I have found it to be as quick or sometimes even quicker than retrieving static resources from Cloudflare's cache. You can run a profile on Pingdom to confirm this.

Futurism answered 18/1, 2017 at 3:23 Comment(1)
Can you explain how to use it? Where should i state the country code i'm targeting?Mantling
A
9

You can get country code etc from cloudflares /cdn-cgi/trace endpoint

const cloudflareFallbackURLs = ['https://one.one.one.one/cdn-cgi/trace',
'https://1.0.0.1/cdn-cgi/trace',
'https://cloudflare-dns.com/cdn-cgi/trace',
'https://cloudflare-eth.com/cdn-cgi/trace',
'https://cloudflare-ipfs.com/cdn-cgi/trace',
'https://workers.dev/cdn-cgi/trace',
'https://pages.dev/cdn-cgi/trace',
'https://cloudflare.tv/cdn-cgi/trace']

async function getCloudflareJSON(){
let data = await fetchWithFallback(cloudflareFallbackURLs).then(res=>res.text())
let arr = data.trim().split('\n').map(e=>e.split('='))
return Object.fromEntries(arr)
}

async function fetchWithFallback(links, obj) {
let response;
for (let link of links) {
try {
    response = await fetch(link, obj)
    if (response.ok)
        return response
} catch (e) { }
}
return response
}

getCloudflareJSON().then(console.log)
Autochthonous answered 8/9, 2015 at 10:20 Comment(0)
H
8

Assuming you are talking about client side JavaScript: no, it isn't possible.

  1. The browser makes an HTTP request to the server.
  2. The server notices what IP address the request came from
  3. The server looks up that IP address in a database and finds the matching country
  4. The server passes that country to PHP

The data never even goes near the browser.

For JavaScript to access it, you would need to read it with server side code and then put it in a response back to the browser.

Hild answered 8/9, 2015 at 10:22 Comment(2)
Sorry but in the context of the question this misleading. The Cloudflare network already does the work for you (it knows what country people are in) and passes it in the CF_IPCOUNTRY header. So while your methodology is correct steps 2 and 3 should just say Read the HTTP_CF_IPCOUNTRY header. If you try to find the country from IP alone it's a big PITA and subject to change all the time. Cloudflare's header should be much more accurate.Weirick
@Weirick — "The server" in this case is "The cloudflare server" (because that is the one that the browser is communicating with). The fact step 4 involves an extra HTTP request from the cloudflare server to the server running PHP isn't relevent to the problem (which is about getting the information client-side).Hild
W
7

Yes you have to hit the server - but it doesn't have to be YOUR server.

I have a shopping cart where pretty much everything is cached by Cloudflare - so I felt it would be stupid to go to MY server to get just the countrycode.

Instead I am using a webworker on Cloudflare (additional charges):

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

async function handleRequest(request) {
  
  var countryCode = request.headers.get('CF-IPCountry');
  
  return new Response(
    
    JSON.stringify({ countryCode }),

    { headers: {
      "Content-Type": "application/json"
    }});
}

You can map this script to a route such as /api/countrycode and then when your client makes an HTTP request it will return essentially instantly (for me it's about 10ms).

 /api/countrycode
 {
    "countryCode": "US"
 }

Couple additional things:

  • You can't use webworkers on all service levels
  • It would be best to deploy an actual webservice on the same URL as a backup (if webworkers aren't enabled or supported or for during development)
  • There are charges but they should be neglibible
  • It seems like there's a new feature where you can map a single path to a single script. That's what I am doing here. I think this used to be an enterprise only feature but it's now available to me so that's great.
  • Don't forget that it may be T1 for TOR network

Since I wrote this they've exposed more properties on Request.cf - even on lower priced plans:

https://developers.cloudflare.com/workers/runtime-apis/request#incomingrequestcfproperties

You can now get city, region and even longitude and latitude, without having to use a geo lookup database.

Weirick answered 12/6, 2019 at 20:47 Comment(4)
Important note: It looks like when you have multiple web workers they don't run in any kind of order of precedence. So you may need to combine this logic into your main webworker script if you are already using this feature. And also if you're not already using webworkers you may not want to pay $5 a month just for this!Weirick
Thanks for this, helped us do redirects over at wiki.polkadot.network 👍Waldemar
The edit queue is full so I'll just post my comments here. Cloudflare gives you 100,000 free web worker requests per day and you can configure it to fail gracefully by passing the request through to your server once it goes beyond 100,000. So if your site rarely goes beyond that number then this is an excellent free option. @Weirick also failed to mention that using an XMLHttpRequest object you can use browser javascript to query the country by querying /api/countrycode or whatever route you set up.Diligence
This is a lovely solution, thank you @WeirickKurt
W
3

I've taken Don Omondi's answer, and converted it to a promise function for ease of use.

function get_country_code() {
    return new Promise((resolve, reject) => {
        var trace = []; 
        jQuery.ajax('/cdn-cgi/trace', {
            success: function(response) {
                var lines = response.split('\n');
                var keyValue;
                for (var index = 0; index < lines.length; index++) {
                    const line = lines[index];
                    keyValue = line.split('=');
                    trace[keyValue[0]] = decodeURIComponent(keyValue[1] || '');
                    if (keyValue[0] === 'loc' && trace['loc'] !== 'XX') {
                        return resolve(trace['loc']);
                    }
                }
            },
            error: function() {
                return reject(trace);
            }
        });

    });
}

usage example

get_country_code().then((country_code) => {
    // do something with the variable country_code
}).catch((err) => {
    // caught the error, now do something with it
});
Whelk answered 9/11, 2018 at 12:40 Comment(1)
Can you explain how to use it? Where should i state the country code i'm targeting? I tried document.write(country_code); nothing comes up.Mantling

© 2022 - 2024 — McMap. All rights reserved.