Unable to make ajax requests in node.js - javascript

I am running a nodejs server to run my website, and I want the backend server to make a call to an api on an external server. I tried the following, basic and straightforward method:
router.post('/calculate', function (req, res) {
var data = /*some json object*/
var xmlhttp = new XMLHttpRequest();
xmlhttp.open("POST", "some.server/pricing");
xmlhttp.setRequestHeader("Content-Type", "application/json");
xmlhttp.send(JSON.stringify(data));
xmlhttp.onreadystatechange = function() {
if(xmlhttp.status == 200)
{
var str = xmlhttp.responseText.toString().trim()
dd = JSON.parse(str);
res.send(dd);
//res.end();
}
};
});
When I run this I get:
_http_outgoing.js:346
throw new Error('Can\'t set headers after they are sent.');
^
Error: Can't set headers after they are sent.
The issue seems to be in res.send(dd);
EDIT:
Upon further investigation, it seems like xmlhttp.onreadystatechange happens twice with status 200, and res.send is called twice. I created a temporary hack to fix this using a boolean flag, what is the rpoper nodejs way to fix this?
What is the most straightforward way of making such a call in nodejs? I want this done on the server side. I am not using any libraries like express. Thanks

Easy do it with request package
var request = require('request');
request({
url: 'some.server/pricing',
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
form: data
}, function (err, res, body) {
if (err) res.send(err)
else res.send(body)
});

After a lot more investigation, I found out that res.send was being called twice. The reason this was happening was because the xmlhttp object changes its state several times:
http://www.w3schools.com/ajax/ajax_xmlhttprequest_onreadystatechange.asp
I fixed the code to:
if (xhttp.readyState == 4 && xhttp.status == 200)
Now everything works properly.

Related

Received data through a POST Request always empty

I have server and a client the server uses node js the client send requests to the sever and the server should act accordingly.
However I came across a little bit of a confusing behavior and i want to know why its behaving like that!
The thing is when i send a json array or Object the received data by the server is always empty for some reason.
Here is the code of the request that raises the problem:
function Save()
{ // saves the whole global data by sending it the server in a save request
if( global_data.length > 0)
{
var url = "http://localhost:3000/save";
var request = new XMLHttpRequest();
request.open("POST", url, true);
request.setRequestHeader("Content-Type", "application/json");
request.onreadystatechange = function () {
if (request.readyState === 4 && request.status === 200) {
console.log(this.responseText);
}
};
let object={ id: "101.jpg", RelativePath: "images/101.jpg", size: 61103 }; // this just an exemple of data
let data_json = JSON.stringify(object);
request.send(data_json);
}
else
{
console.log("Nothing to save");
}
}
And Here is the server code related to this request:
const server=http.createServer(onRequest)
server.listen(3000, function(){
console.log('server listening at http://localhost:3000');
})
function onRequest (request, response) {
/*function that handles the requests received by the server and
sends back the appropriate response*/
/*allowing Access-Control-Allow-Origin since the server is run on local host */
response.setHeader('Access-Control-Allow-Origin', '*');
response.setHeader('Access-Control-Request-Method', '*');
response.setHeader('Access-Control-Allow-Methods', 'OPTIONS, GET');
response.setHeader('Access-Control-Allow-Headers', '*');
console.log("a request received :" ,request.url);
let parsed_url = url.parse(request.url);
if(parsed_url.pathname == '/save')
{
console.log("Proceeding to save state : ...");
let received_data = '';
request.on('data', function(chunck) {
received_data += chunck;
console.log("another line of data received");
});
request.on('end', function() {
console.log(received_data); // why is this empty (my main problem)?
let jsondata = JSON.parse(received_data); // here raises the error since the received data is empty
console.log(jsondata);
response.writeHeader(200,{"content-Type":'text/plain'});
response.write("SAVED!");
response.end()
});
}
}
Just if anyone got the same problem: for me I couldn't solve it directly so I was forced to use query-string in order to parse the data instead of json.parse it seems the data received emptiness was related to the failure of the JSON parser somehow. so I installed it with npm install querystring and used const qs = require('querystring'); in order to invoque the parser by calling qs.parse(received_data.toString());.
Hope this helps anyone who got stuck in the same situation.

How to load a json file with javascript locally without Jquery?

I'm creating a website to progress in javascript and I have a little problem, every ways I try, my browser doesn't want to load my json file.
I tried many codes i found on internet but none of them work (or I don't know how to make them work). Finally i fond this one which is quite easy to understand but yhis one too doesn't work and always return an error message.
function loadJSON(path,success, error)
{
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function()
{
if (xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status === 1) {
if (success)
success(JSON.parse(xhr.responseText));
} else {
if (error)
error(xhr);
}
}
};
xhr.open("GET", path , true);
xhr.send();
}
function test()
{
loadJSON('test.json', function(data) { console.log(data); }, function(xhr) { console.error(xhr); });
}
I run the test function but everytimes, the console return me an error. Someone have an idea to solve my problem ?
status is the HTTP response code.
200 means the request has been successful. The status will most likely never be 1.
Here is a list of HTTP codes
As a solution, I suggest using the fetch API, which is the modern way to query files.
Here are some examples on how to use it
If you really want to use AJAX, use this :
var request = new XMLHttpRequest();
request.open('GET', '/my/url', true);
request.onload = function() {
if (this.status >= 200 && this.status < 400) {
// Success!
var resp = this.response;
} else {
// We reached our target server, but it returned an error
}
};
request.onerror = function() {
// There was a connection error of some sort
};
request.send();
Source : You Might Not Need jQuery

Any way to make AJAX calls to Gmail API without going through JS library?

A simple guide to making a GET request to get a user's messages through Gmail API can be found here.
But the way we are instructed to do the request is in the following manner:
function getMessage(userId, messageId, callback) {
var request = gapi.client.gmail.users.messages.get({
'userId': userId,
'id': messageId
});
request.execute(callback);
}
Is it possible to make the request using the good ol' XMLHttpRequest object on the client side? If so what parameters should be passed into the call?
I have tried this:
var getMessages = function() {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && xhr.status == 200)
console.log(xhr.responseText);
}
xhr.open( "GET", "https://www.googleapis.com/gmail/v1/users/me/messages", true );
xhr.send();
}
But I get a 401, even after authenticating.
As it states in this answer, you should pass the access token as a query parameter with the name access_token, or prefix the authorization header value with "Bearer", like so:
xhr.setRequestHeader("authorization", "Bearer " + userToken.access_token);

Basic Authentication Using JavaScript

I am building an application that consumes the Caspio API. I am having some trouble authenticating against their API. I have spent 2-3 days trying to figure this out but it may be due to some understanding on my end. I have read countless articles on stackoverflow post and otherwise but have not solved the issue. Below is a code example of my solution based on what i have looked at and i am getting a 400 Status code message; What am i doing wrong here? (Please provide well commented code example and i would prefer to NOT have links posted here referencing other material as i have looked at these extensively. Thanks!):
Some references i have looked at:
1) Pure JavaScript code for HTTP Basic Authentication?
2) How to make http authentication in REST API call from javascript
I would like to use this authentication method as described by caspio below:
As an alternative to including credentials in the request body, a client can use the HTTP Basic authentication scheme. In this case, authentication request will be setup in the following way:
Method: POST
URL: Your token endpoint
Body: grant_type=client_credentials
Header parameter:
Authorization: Basic Basic authentication realm
Below are my Javascript and HTML code.
JavaScript:
var userName = "clientID";
var passWord = "secretKey";
function authenticateUser(user, password)
{
var token = user + ":" + password;
// Should i be encoding this value????? does it matter???
// Base64 Encoding -> btoa
var hash = btoa(token);
return "Basic " + hash;
}
function CallWebAPI() {
// New XMLHTTPRequest
var request = new XMLHttpRequest();
request.open("POST", "https://xxx123.caspio.com/oauth/token", false);
request.setRequestHeader("Authorization", authenticateUser(userName, passWord));
request.send();
// view request status
alert(request.status);
response.innerHTML = request.responseText;
}
HTML:
<div>
<div id="response">
</div>
<input type="button" class="btn btn-primary" value="Call Web API" onclick="javascript:CallWebAPI();" />
After Spending quite a bit of time looking into this, i came up with the solution for this; In this solution i am not using the Basic authentication but instead went with the oAuth authentication protocol. But to use Basic authentication you should be able to specify this in the "setHeaderRequest" with minimal changes to the rest of the code example. I hope this will be able to help someone else in the future:
var token_ // variable will store the token
var userName = "clientID"; // app clientID
var passWord = "secretKey"; // app clientSecret
var caspioTokenUrl = "https://xxx123.caspio.com/oauth/token"; // Your application token endpoint
var request = new XMLHttpRequest();
function getToken(url, clientID, clientSecret) {
var key;
request.open("POST", url, true);
request.setRequestHeader("Content-type", "application/json");
request.send("grant_type=client_credentials&client_id="+clientID+"&"+"client_secret="+clientSecret); // specify the credentials to receive the token on request
request.onreadystatechange = function () {
if (request.readyState == request.DONE) {
var response = request.responseText;
var obj = JSON.parse(response);
key = obj.access_token; //store the value of the accesstoken
token_ = key; // store token in your global variable "token_" or you could simply return the value of the access token from the function
}
}
}
// Get the token
getToken(caspioTokenUrl, userName, passWord);
If you are using the Caspio REST API on some request it may be imperative that you to encode the paramaters for certain request to your endpoint; see the Caspio documentation on this issue;
NOTE: encodedParams is NOT used in this example but was used in my solution.
Now that you have the token stored from the token endpoint you should be able to successfully authenticate for subsequent request from the caspio resource endpoint for your application
function CallWebAPI() {
var request_ = new XMLHttpRequest();
var encodedParams = encodeURIComponent(params);
request_.open("GET", "https://xxx123.caspio.com/rest/v1/tables/", true);
request_.setRequestHeader("Authorization", "Bearer "+ token_);
request_.send();
request_.onreadystatechange = function () {
if (request_.readyState == 4 && request_.status == 200) {
var response = request_.responseText;
var obj = JSON.parse(response);
// handle data as needed...
}
}
}
This solution does only considers how to successfully make the authenticated request using the Caspio API in pure javascript. There are still many flaws i am sure...
Today we use Bearer token more often that Basic Authentication but if you want to have Basic Authentication first to get Bearer token then there is a couple ways:
const request = new XMLHttpRequest();
request.open('GET', url, false, username,password)
request.onreadystatechange = function() {
// D some business logics here if you receive return
if(request.readyState === 4 && request.status === 200) {
console.log(request.responseText);
}
}
request.send()
Full syntax is here
Second Approach using Ajax:
$.ajax
({
type: "GET",
url: "abc.xyz",
dataType: 'json',
async: false,
username: "username",
password: "password",
data: '{ "key":"sample" }',
success: function (){
alert('Thanks for your up vote!');
}
});
Hopefully, this provides you a hint where to start API calls with JS. In Frameworks like Angular, React, etc there are more powerful ways to make API call with Basic Authentication or Oauth Authentication. Just explore it.
To bring this question up to date, a node.js solution (using node-fetch) would be as follows:
const auth = Buffer.from(`${clientId}:${clientSecret}`).toString("base64");
fetch("https://some-oauth2.server.com/connect/token", {
method: "POST",
body: "grant_type=client_credentials",
headers: {
"Content-type": "application/x-www-form-urlencoded",
Authorization: `Basic ${auth}`,
},
})
.then((response) => response.json())
.then((response) => {
console.log(response); //response.access_token is bearer token, response.expires_in is lifetime of token
});
Sensitive requests like this should be server-to-server, and keeping the credential details in the Header rather than QueryString means it's less likely to be visible in web server logs
EncodedParams variable is redefined as params variable will not work. You need to have same predefined call to variable, otherwise it looks possible with a little more work. Cheers! json is not used to its full capabilities in php there are better ways to call json which I don't recall at the moment.
change var to const for the username, password, token_, and key variables.

MooTools CORS request vs native Javascript

I have this MooTools code:
new Request.JSON({
method: 'POST',
url: URL, /*URL TO ANOTHER DOMAIN*/
onSuccess: function(r){
callback(r);
}
}).post(data);
And this code doesn't send POST requests (OPTIONS only)...
Look at the code below (it works great):
var http = null,
params = Object.toQueryString(data);
try {
http = new XMLHttpRequest();
} catch (e) {
try {
http = new ActiveXObject("Msxml2.XMLHTTP");
} catch (e) {
try {
http = new ActiveXObject("Microsoft.XMLHTTP");
} catch (e) {
http = null;
alert("Your browser does not support AJAX!");
}
}
}
var url = URL;
http.onreadystatechange = function () {
if (http.readyState == 4 && http.status == 200) {
var jsonData = JSON.parse(http.responseText); /*OR EVAL*/
callback(jsonData);
}
};
http.open("POST", url);
http.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
http.send(params);
EDIT:
Tried: .setHeader('Content-Type','application/x-www-form-urlencoded');
Still nothing... Where can there be a problem?
Thanks!
This is because MooTools bundles some extra stuff with the request headers.
eg. if your htaccess says:
Header set Access-Control-Allow-Origin: *
you need to craft your request like that:
var foo = new Request({
url: 'http://fragged.org/Epitome/example/data/',
method: 'get',
onComplete: function (data) {
// returns an object with name and surname
new Element('div[html="{name} {surname}"]'.substitute(JSON.decode(data))).inject(document.body);
}
});
// need to remove that or CORS will need to match it specifically
delete foo.headers['X-Requested-With'];
foo.send();
This is why you are only seeing the OPTIONS pre-flight. It does not like you :)
You could change the .htaccess to also match X-Requested-With, which is probably some extra "security".
See http://jsfiddle.net/7zUSu/1/ for a working example - I did that a while ago when I wanted to get this change to Request https://github.com/mootools/mootools-core/issues/2381 fixed.
What do you mean by (OPTIONS only)? Both examples sends POST request, only difference is in Accept request headers.
MooTools sends Accept: application/json, while native sends Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8.
This may affect how the server responds.

Categories