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?
#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.
Assuming you are talking about client side JavaScript: no, it isn't possible.
The browser makes an HTTP request to the server.
The server notices what IP address the request came from
The server looks up that IP address in a database and finds the matching country
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.
fetch('https://cloudflare-quic.com/b/headers').then(res=>res.json()).then(data=>{console.log(data.headers['Cf-Ipcountry'])})
Reference:
https://cloudflare-quic.com/b
https://cloudflare-quic.com/b/headers
Useful Links:
https://www.cloudflare.com/cdn-cgi/trace
https://github.com/fawazahmed0/cloudflare-trace-api
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.
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
});
Related
I am getting an error due to CORS policy. It seems a common issue. I have read other questions and tried some of their solutions, but it still does not work.
My app does the following:
1- GET a list of values from a google sheet and show them in a drop down list in a web app (still using script.google.com)
2- POST the value that the user select in the drop down list from the web app to the sheet.
1 (GET) was working fine with no issues. When I added 2 (POST), it gave me the CORS error for the POST call. The GET seems fine.
Here is the code for POST:
function doPost(e) {
var value = JSON.parse(e.postData.contents).value;
var ss = SpreadsheetApp.openById('1Zbvy1x_DsBlcwK4FdjoY4m0MFvS_tYZlGtKvD36fDyk');
var sh = ss.getSheetByName('Dashboard');
sh.getRange(92, 2).setValue(value);
return ContentService.createTextOutput(JSON.stringify({message: "ok"})).setMimeType(ContentService.MimeType.JSON);
}
with HTML file:
<script>
function listQ() {
const index = this.selectedIndex;
if (index > 0) {
const e = document.getElementById("sel1");
const value = e.options[index].value;
const url = "https://script.google.com/a/google.com/macros/s/AKfycbxHX7cthji076OrqfY9ZpGa7jNDxKHUMf_ib7Ekmoo0Ir5DQF1Y/exec";
fetch(url, {
method: "POST",
body: JSON.stringify({index: index, value: value}),
})
.then(function (response) {
return response.json();
})
.then(function (data) {
console.log(data);
})
}
}
document.getElementById("sel1").addEventListener("change",listQ);
</script>
The GET part is fine, so no need to add the code here.
I have tried to change POST with PUT as suggested here: Change Google Sheet data based on user selection in Google Site (Web App) but it gave the same error.
I have also read this link, but mine is a POST request, not GET and I am not sure how to apply it to my case: CORS authorization on google sheets API requests
FYI The "origin" in the CORS error message is ...googleusercontent.com
UPDATE: I have also tried this: Google Apps Script cross-domain requests stopped working
I think I should implement that solution, but when I tried to add those mods in my code and it did not work. Probably I am adding the mods incorrectly since I am not 100% sure what I am doing. I get the error that the call back function is not defined. Here is what I did:
function doPost(e) {
var callback = e.parameter.callback;
var value = JSON.parse(e.postData.contents).value;
var ss = SpreadsheetApp.openById('1ROvDcIQ8JCGvvLvCvTKIqSor530Uj9ZJv-n6hQ761XA');
var sh = ss.getSheetByName('Dashboard');
sh.getRange(92, 2).setValue(value);
return ContentService.createTextOutput(callback+'('+JSON.stringify({message: "ok"})+')').setMimeType(ContentService.MimeType.JSON);
}
and in HTML side, I just modified the URL:
const url = "https://script.google.com/a/google.com/macros/s/AKfycbxHX7cthji076OrqfY9ZpGa7jNDxKHUMf_ib7Ekmoo0Ir5DQF1Y/exec?offset="+offset+"&baseDate="+baseDate+"&callback=?";
In Apps Script Web Apps, in order to access server-side functions from your current project, you don't need to make a GET or POST request to the Web App URL. You just need to use google.script.run.
In your exact case, you could do the following:
function listQ() {
const index = this.selectedIndex;
if (index > 0) {
const e = document.getElementById("sel1");
const value = e.options[index].value;
const body = { index: index, value: value };
google.script.run.withSuccessHandler(yourCallback).yourServerSideFunc(body);
}
}
function yourCallBack(response) {
// Whatever you want to do with the data retrieved from server-side
}
In the code above, the client-side function calls a server side function called yourServerSideFunc via google.script.run. If the server-side function returns successfully, a callback function is called (function yourCallback), whose purpose is to handle the data returned from the server (a callback is needed since, on its own, the function called by google.script.run returns void). This callback function receives the content returned by the server-side function as an argument.
And in the server-side, no need to use doPost, since you would not be making a POST request:
function yourServerSideFunc(body) {
var value = body["value"];
var ss = SpreadsheetApp.openById('1Zbvy1x_DsBlcwK4FdjoY4m0MFvS_tYZlGtKvD36fDyk');
var sh = ss.getSheetByName('Dashboard');
sh.getRange(92, 2).setValue(value);
return ContentService.createTextOutput(JSON.stringify({message: "ok"})).setMimeType(ContentService.MimeType.JSON);
}
Reference:
HTML Service: Communicate with Server Functions
Class google.script.run (Client-side API)
The first thing is that I'm a noob and I know it... Maybe you think this is a repeated question but I read a lot of posts about the same question but no one helped.
I'm trying to develop a web application about working orders, clients, providers, etc... I have a Rest API with the proper routes connected to a database (mysql), and the logic with the CRUM methods. I'm still working on the server part and until some days ago everything went fine, since I tested this first with Postman and then with some simple tests and everything is working well.
The thing is I'm trying to develop the logic (the real one) of my application to access to some individual elements of the json object (the API returns an array, but that's not the problem). I need to check and generate the serial number of the working orders with this format 'number/year'. I tried with both fetch() and XMLHttpRequest to access the data and nothing works.... I can't access to the elements of the array because I always have something wrong.
If I try this inside if my tests using fetch() it works but if I try this inside my numeroOT() method I can't, I don't know what else to do so I need help please... I'm going crazy with this thing!!
This is the code that works IN MY TEST:
describe('TEST logica.js', function () {
it('Genero el Nº de Orden', async () => {
var numeroDeOT = laLogica.numeroOT(); //this is the method of my logic I'm testing and it's suposed to do the thing
//This is the the only code which I could acceed to the json. But this has to go inside the numeroOT() method but only works in the test
//-------------------------------------------
var response = await fetch('http://localhost:3000/ots');
var orden = await response.json();
var laOrden = orden[orden.length-1]; //the last element/json object of the array
var elNumero = laOrden.numero_ot; //the element I want to check and generate
console.log(elNumero);
//The code to check this element and generate the new one goes down here but that's clear
//---------------------------------------------
console.log(numeroDeOT);
expect(numeroDeOT).to.be.a('String'); //this is to check what my method numeroOT() returns. Usually 'undefined'
}) //it
}); //describe
I realized that the two ways I was trying in my code there weren't possible to use them with node (since I'm using node), so I tried this code into my method and it works perfectly and I finally can access to my array of JSON objects!
var options = {
host : 'localhost',
port : 3000,
path : '/ots', // the rest of the url with parameters if needed
method : 'GET' // do GET
};
var request = http.request(options, function (res) {
console.log("request received");
var data = '';
res.on('data', function (chunk) {
data += chunk;
});
res.on('end', function () {
console.log(data);
});
});
request.on('error', function (e) {
console.log(e.message);
});
request.end();
We are using zapier.com to connect many programs, but one function that I need is to autofill the city and state from a zip code. This is available in zapier.com as setup Code by Zapier Run Javascript. I can't seem to figure this out and any help is much appreciated.
<script type="text/javascript">//<![CDATA[
$(function() {
// IMPORTANT: Fill in your client key
var clientKey; // Deleted for Stack Overflow
var cache = {};
var container = $("#example1");
var errorDiv = container.find("div.text-error");
/** Handle successful response */
function handleResp(data)
{
// Check for error
if (data.error_msg)
errorDiv.text(data.error_msg);
else if ("city" in data)
{
// Set city and state
container.find("input[name='city']").val(data.city);
container.find("input[name='state']").val(data.state);
}
}
// Set up event handlers
container.find("input[name='zipcode']").on("keyup change", function() {
// Get zip code
var zipcode = $(this).val().substring(0, 5);
if (zipcode.length == 5 && /^[0-9]+$/.test(zipcode))
{
// Clear error
errorDiv.empty();
// Check cache
if (zipcode in cache)
{
handleResp(cache[zipcode]);
}
else
{
// Build url
var url = "https://www.zipcodeapi.com/rest/"+clientKey+"/info.json/" + zipcode + "/radians";
// Make AJAX request
$.ajax({
"url": url,
"dataType": "json"
}).done(function(data) {
handleResp(data);
// Store in cache
cache[zipcode] = data;
}).fail(function(data) {
if (data.responseText && (json = $.parseJSON(data.responseText)))
{
// Store in cache
cache[zipcode] = json;
// Check for error
if (json.error_msg)
errorDiv.text(json.error_msg);
}
else
errorDiv.text('Request failed.');
});
}
}
}).trigger("change");
});
//]]></script>
It looks like you're trying to use client-side JavaScript here. This won't work in a Zapier code step because it's meant to be used in a browser (on a webpage). To make an HTTP request in a Zapier code step, you'll want to use fetch (here's some documentation on that).
Alternatively, the simplest way to get the data you need from that API is with a Webhook step:
Add a step to your Zap
Choose Webhooks by Zapier and select the GET action
Set up that step like this. The step will return city/state data which you can use in subsequent steps
There are a couple points of confusion here, the main one being:
Code by Zapier is not run "in the browser" (there is no <script> tags or Jquery) - it is run in something called Node.js.
You'll need to approach the problem completely differently as a result - definitely take a look at the samples found in https://zapier.com/help/code/ and at the Node docs https://nodejs.org/docs/v4.3.2/api/.
First of all - I am aware of this answer to a kind of similar problem.
Problem
I have a third party protocol, that uses TCP\IP. This protocol defines that the server replies to every message received. On the client side (which I try to implement) I have to wait for the answer from the server.
The problem occurs, when I try to send messages. I need to wait for the answer from the first message before I send the second one (like ping-pong).
I tried to do multiple writes on my NodeJS tcp-client like this, which understandably fails due to async:
client.connect(connectOptions, function () {
client.write(message1);
client.write(message2);
});
Like I said before, I have a third party component, which responses to both messages with a numeric value. So when
client.on('data',function (data) {});
fires an event, I can't distinguish which message, was responsible for the answer. Unlike the linked answer I don't have the ability, to tag the answer on the server side.
I am new to node.js, so I try to figure out the best way to solve this kind of problem, as it´s of the nature: do synchronous things in the async environment.
One way would be to use a common list of handlers to keep track of requests and responses:
var handlers = [];
client.connect(connectOptions, function () {
client.write(message1);
handlers.push(function msg1_handler(data) {});
client.writewrite(message2);
handlers.push(function msg2_handler(data) {});
});
client.on('data',function(data) {
var handler = handlers.shift();
handler(data);
});
All of this should obviously be wrapped in a separate class containing both handlers an client objects. It's just an example of how to do it. The drawback is that if the server fails to respond to some request then you have a complete mess, hard to make it right.
Another idea is to buffer requests:
function BufferedClient(cli) {
this.cli = cli;
this.buffer = [];
this.waiting_for_response = false;
var that = this;
cli.on('data', function(data) {
that.waiting_for_response = false;
var pair = that.buffer.shift();
var handler = pair[0];
process.nextTick(function() {
// we use .nextTick to avoid potential
// exception in handler which would break
// BufferedClient
handler(data);
});
that.flush();
});
};
BufferedClient.prototype = {
request: function(msg, handler) {
this.buffer.push([handler, msg]);
this.flush();
},
flush: function() {
var pair = this.buffer[0];
if (pair && !this.waiting_for_response) {
this.cli.write(pair[1]);
this.waiting_for_response = true;
}
}
};
This time you send requests sequentially (so like synchronous) due to how .request() and .on('data') handler work together with .flush() function. Usage:
client.connect(connectOptions, function () {
var buff_cli = new BufferedClient(client);
buff_cli.request(message1, function(data) { });
buff_cli.request(message2, function(data) { });
});
Now even if the server fails to respond you don't have a mess. However if you issue buff_cli.request parallely and one of them fails then you will have a memory leak (since this.buffer is getting bigger while nothing is draining it because the BufferedClient is waiting for a response). This can be fixed by adding some timeouts on the socket.
Note that both solutions assume that the server never pushes anything to the client without a request.
If I were you I would go with second solution. Note that I haven't tested the code so it might be buggy but the general idea should be ok.
Side note: When you implement a server (and I know that you don't in this case) you should always have a protocol that matches each request with a response in a unique way. One way would be to send a unique ID with each request so that the server would be respond with the same ID. In such scenario matching request with response is very easy and you avoid all that mess.
I have a node application handling some ZeroMQ events coming from another application utilizing the Node-ZMQ bindings found here: https://github.com/JustinTulloss/zeromq.node
The issue I am running into is one of the operations from an event takes a long time to process and this appears to be blocking any other event from being processed during this time. Although the application is not currently clustered, doing so would only afford a few more threads and doesn't really solve the issue. I am wondering if there is a way of allowing for these async calls to not block other incoming requests while they process, and how I might go about implementing them.
Here is a highly condensed/contrived code example of what I am doing currently:
var zmq = require('zmq');
var zmqResponder = zmq.socket('rep');
var Client = require('node-rest-client').Client;
var client = new Client();
zmqResponder.on('message', function (msg, data) {
var parsed = JSON.parse(msg);
logging.info('ZMQ Request received: ' + parsed.event);
switch (parsed.event) {
case 'create':
//Typically short running process, not an issue
case 'update':
//Long running process this is the issue
serverRequest().then(function(response){
zmqResponder.send(JSON.stringify(response));
});
}
});
function serverRequest(){
var deferred = Q.defer();
client.get(function (data, response) {
if (response.statusCode !== 200) {
deferred.reject(data.data);
} else {
deferred.resolve(data.data);
}
});
return deferred.promise;
}
EDIT** Here's a gist of the code: https://gist.github.com/battlecow/cd0c2233e9f197ec0049
I think, through the comment thread, I've identified your issue. REQ/REP has a strict synchronous message order guarantee... You must receive-send-receive-send-etc. REQ must start with send and REP must start with receive. So, you're only processing one message at a time because the socket types you've chosen enforce that.
If you were using a different, non-event-driven language, you'd likely get an error telling you what you'd done wrong when you tried to send or receive twice in a row, but node lets you do it and just queues the subsequent messages until it's their turn in the message order.
You want to change REQ/REP to DEALER/ROUTER and it'll work the way you expect. You'll have to change your logic slightly for the ROUTER socket to get it to send appropriately, but everything else should work the same.
Rough example code, using the relevant portions of the posted gist:
var zmqResponder = zmq.socket('router');
zmqResponder.on('message', function (msg, data) {
var peer_id = msg[0];
var parsed = JSON.parse(msg[1]);
switch (parsed.event) {
case 'create':
// build parsedResponse, then...
zmqResponder.send([peer_id, JSON.stringify(parsedResponse)]);
break;
}
});
zmqResponder.bind('tcp://*:5668', function (err) {
if (err) {
logging.error(err);
} else {
logging.info("ZMQ awaiting orders on port 5668");
}
});
... you need to grab the peer_id (or whatever you want to call it, in ZMQ nomenclature it's the socket ID of the socket you're sending from, think of it as an "address" of sorts) from the first frame of the message you receive, and then use send it as the first frame of the message you send back.
By the way, I just noticed in your gist you are both connect()-ing and bind()-ing on the same socket (zmq.js lines 52 & 143, respectively). Don't do that. Inferring from other clues, you just want to bind() on this side of the process.