Trouble with XDomain Request to node server in IE8 - javascript

I've been having trouble trying to issue a CORS request via the XDomainRequest object in IE8. For my code, I need to send a POST request with some data to a server which then passes that data along through several other services. I've gotten the server to respond to and process requests and data coming from all other browsers, both by using jQuery's ajax method and by using vanilla javascript and the XMLHttpRequest object. However, after reading Mozilla's CORS documentation, Microsoft's XDomainRequest documentation, and quite a few blog posts and stack overflow questions about the latter, I can't seem to get the XDomainRequests to work. Here is the code for the XDomainRequest I'm trying to make:
Creating the request:
if (typeof XDomainRequest != "undefined") {
// XDomainRequest for IE8 & 9.
xhr = new XDomainRequest();
console.log('Generated XDomainRequest');
xhr.onprogress = function() {console.log('Progress');};
xhr.ontimeout = function() {console.log('ontimeout');};
xhr.onerror = function() {console.log('Error');};
xhr.onload = function() {console.log('Success');};
xhr.open(method, url);
console.log('Open XDomainRequest');
}
And then sending the request (which is done in another function):
if (typeof XDomainRequest != 'undefined') {
console.log('XDomainRequest');
setTimeout(function () {
console.log('Sending request');
data = 'foo=bar&baz=bat';
xhr.send(data);
}, 0);
}
I'm aware that the request can not be sent across different protocols, and I can confirm that the request is being made from HTTPS to HTTPs. However, when running the code, I receive an error generated by the XDomainRequest's error handler. When testing a GET request from a Windows XP IE8 virtual machine on virtual box, I also get an error generated by the request's error handler, but unfortunately, no indication of what failed. I know that XDomainRequest is only able to send data if the content type is of 'text/plain' and that is the type of data I have been testing it with. The relevant server code is here:
For an OPTIONS request:
var http = require('http');
var url = require('url');
var request = require('request');
var AWS = require('aws-sdk');
var path = require('path');
var fs = require('fs');
function checkOrigin(request) {
/* Function to determine if origin is greenlit for CORS
* #param request is the http request being made to the server.
* #return returns whether origin matches parent domain.
*/
var acceptableDomain = new RegExp("some_url.com");
if (acceptableDomain.test(request.headers.origin)) {
return request.headers.origin;
} else {
return null;
}
}
.
. // Unrelated code between these functions //
.
if (request.method === 'OPTIONS') {
console.log('!OPTIONS');
var headers = {};
headers["Access-Control-Allow-Origin"] = checkOrigin(request);
headers["Access-Control-Allow-Methods"] = "POST, OPTIONS";
headers["Access-Control-Allow-Credentials"] = true;
headers["Access-Control-Max-Age"] = '86400'; // 24 hours
headers["Vary"] = 'Origin';
headers["Access-Control-Allow-Headers"] = "X-Requested-With, X-HTTP-Method-Override, Content-Type, Accept";
response.writeHead(200, headers);
response.end();
}
For a GET request:
if (request.method === 'GET') {
console.log("Request received!");
var fileType = {
"html": "text/html",
"jpeg": "image/jpeg",
"jpg": "image/jpeg",
"png": "image/png",
"js": "application/javascript",
"css": "text/css"};
var fileName = "some_script.js";
var filePath = path.join(process.cwd(), fileName);
var ext = fileType[fileName.split(".")[1]];
var fileStream = fs.createReadStream(filePath);
console.log(ext);
response.writeHead(200, {'content-type':ext});
fileStream.pipe(response);
//Maybe need return here?
}
For a POST request:
if (request.method == 'POST'
&& (contenttype != undefined)
&& ((contenttype.indexOf('application/json') != -1)
|| (contenttype.indexOf('application/x-www-form-urlencoded') != -1)
|| (contenttype.indexOf('text/plain')!= -1))) {
var message = '';
var body = "";
console.log("Post received!");
if((contenttype.indexOf('application/json') != -1)
|| contenttype.indexOf('application/x-www-form-urlencoded') != -1) {
// Once the request posts data, we begin parsing that data and add it to 'body.'
request.on('data', function (chunk) {
var parsedChunk = JSON.parse(chunk);
body += parsedChunk;
});
request.on('end', function () {
console.log('Data:' + body.replace(/,/g, '\n'));
});
} else {
message = 'POST Received';
response.write(message);
}
response.writeHead(200, {'content-length': message.length,
'Access-Control-Allow-Origin': checkOrigin(request),
'Access-Control-Allow-Headers': "X-Requested-With, X-HTTP-Method-Override, Content-Type, Accept",
'Access-Control-Allow-Methods': "POST, OPTIONS",
'Access-Control-Allow-Credentials': 'true',
'Access-Control-Max-Age': '86400',
'Vary':'Origin',
'content-type': 'text/plain'});
//response.write('POST Received');
response.end();
Does anyone have any ideas as to what might be going wrong when making the XDomainRequest? Let me know if there's any other information I can include that might help!

Related

How to properly do a server side api request?

I am very new with node js and decided to learn how to secure api keys, i've looked everywhere but can't find a example. But i found some that suggest the only way is to do a server side api request.
I am using openweathermap api for this code, i get the expected data back as a response in chrome network tab but i have questions regarding it.
How do i use the response data (e.g getting the current weather, temp) ?
Is this the proper way on doing a server side api request in node.js?
http.createServer(function(req, res) {
if (req.url === '/') {
res.writeHead(200, {"Content-Type": "text/html"});
fs.createReadStream(__dirname + '/index.html').pipe(res);
} else if (req.url === '/getweather') {
var weatherApiURL = 'https://api.openweathermap.org/data/2.5/weather?q=London,uk&appid=<API KEY>';
request(weatherApiURL, function (error, response, body) {
if (error) {
res.writeHead(500, 'Weather API request failed', {'content-type': 'text/plain'});
res.end();
} else {
res.writeHead(200, {'content-type': 'application/json'});
res.end(body);
}
});
} else {
res.end('not found')
}
}).listen(8080);
Front:
function requestWeatherData() {
var xhr = new XMLHttpRequest();
xhr.open('GET', '/getweather', true);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.onload = function () {
console.log(this.responseText);
};
xhr.send();
};
Thank you in advanced!!
Part 1: How to use the data.
The first thing you'll want to do is to check whether the request succeeded
if (this.status !== 200) {
// The request did not work.
throw new Error('Cannot use data.');
}
Once the requset status has been verified you need to 'parse' the response.
const weatherData = JSON.parse(this.responseText);
// Lets see the js object:
console.log(weatherData);
Now you can do whatever you need with the data.
The full example:
function requestWeatherData() {
var xhr = new XMLHttpRequest();
xhr.open('GET', '/getweather', true);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.onload = function () {
if (this.status !== 200) {
// The request did not work.
throw new Error('Cannot use data.');
}
const weatherData = JSON.parse(this.responseText);
// Lets see the js object:
console.log(weatherData);
};
xhr.send();
};
Part 2: Is there a proper way of doing this?
Now, I don't not know enough about this to say definitely, however, here are some concerns that you may want to think about.
Most APIs have rate limits, meaning you probably want to try to 'cache' the requests somewhere to reduce the need to 'poll' the APIs
Other people could use your exposed url in their application.
Writing all of the routes as you are currently will become a real headache for larger applications, I can recommend express from experience for small to medium applications.

Get response from PhanomJS server using the Requests library

I am testing ways to use a PhantomJS server with Python's Requests library.
The GET and POST requests work as expected and I can get the PhantomJS server to request any page I want and put the results to the console, but I can not figure out how to send the page content back using response.write(page.content). The request object has no text, content or usable raw content. The only way to get response.write() to work as expected is to hardcode the response content. If I add keep-alive to true the request functions hangs.
Here is my server.js
var webserver = require('webserver').create();
page = require('webpage').create();
var service = webserver.listen(8080, function(request, response) {
if (request.method == 'POST') {
console.log(request.post);
var content = '';
page.open(request.post, function (status) {
if (status !== 'success') {
console.log('FAIL to load the address');
response.statusCode = 200;
response.write('Page not responding.');
} else {
content = page.content;
response.statusCode = 200;
response.write(content);
}
})
} else {
response.statusCode = 200;
console.log(request.method == 'GET' );
response.write('No URL provided');
}
response.closeGracefully();
});
The Python code is straightforward:
import requests
response = requests.post('http://127.0.0.1:8080, data='http://python.org')
The connection needs to be closed after sending data back: response.close();
Also I'd suggest using a variable in POST request, cause response.post is actually an object.
var webserver = require('webserver').create();
page = require('webpage').create();
var service = webserver.listen(8080, function(request, response) {
if (request.method == 'POST') {
var url = request.post.url;
console.log(url);
var content = '';
page.open(url, function (status) {
if (status !== 'success') {
console.log('FAIL to load the address');
response.statusCode = 200;
response.write('Page not responding.');
response.close();
} else {
content = page.content;
response.statusCode = 200;
response.write(content);
response.close();
}
})
} else {
response.statusCode = 200;
console.log(request.method == 'GET' );
response.write('No URL provided');
respone.close();
}
});
Then POST with url variable:
import requests
response = requests.post('http://127.0.0.1:8080, data = {'url':'http://python.org'})

Uploading a file with FormData and multer

I have successfully managed to upload a file to a Node server using the multer module by selecting the file using the input file dialog and then by submitting the form, but now I would need, instead of submitting the form, to create a FormData object, and send the file using XMLHttpRequest, but it isn't working, the file is always undefined at the server-side (router).
The function that does the AJAX request is:
function uploadFile(fileToUpload, url) {
var form_data = new FormData();
form_data.append('track', fileToUpload, fileToUpload.name);
// This function simply creates an XMLHttpRequest object
// Opens the connection and sends form_data
doJSONRequest("POST", "/tracks/upload", null, form_data, function(d) {
console.log(d);
})
}
Note that fileToUpload is defined and the url is correct, since the correct router method is called. fileToUpload is a File object obtained by dropping a file from the filesystem to a dropzone, and then by accessing the dataTransfer property of the drop event.
doJSONRequest is a function that creates a XMLHttpRequest object and sends the file, etc (as explained in the comments).
function doJSONRequest(method, url, headers, data, callback){
//all the arguments are mandatory
if(arguments.length != 5) {
throw new Error('Illegal argument count');
}
doRequestChecks(method, true, data);
//create an ajax request
var r = new XMLHttpRequest();
//open a connection to the server using method on the url API
r.open(method, url, true);
//set the headers
doRequestSetHeaders(r, method, headers);
//wait for the response from the server
r.onreadystatechange = function () {
//correctly handle the errors based on the HTTP status returned by the called API
if (r.readyState != 4 || (r.status != 200 && r.status != 201 && r.status != 204)){
return;
} else {
if(isJSON(r.responseText))
callback(JSON.parse(r.responseText));
else if (callback !== null)
callback();
}
};
//set the data
var dataToSend = null;
if (!("undefined" == typeof data)
&& !(data === null))
dataToSend = JSON.stringify(data);
//console.log(dataToSend)
//send the request to the server
r.send(dataToSend);
}
And here's doRequestSetHeaders:
function doRequestSetHeaders(r, method, headers){
//set the default JSON header according to the method parameter
r.setRequestHeader("Accept", "application/json");
if(method === "POST" || method === "PUT"){
r.setRequestHeader("Content-Type", "application/json");
}
//set the additional headers
if (!("undefined" == typeof headers)
&& !(headers === null)){
for(header in headers){
//console.log("Set: " + header + ': '+ headers[header]);
r.setRequestHeader(header, headers[header]);
}
}
}
and my router to upload files is the as follows
// Code to manage upload of tracks
var multer = require('multer');
var uploadFolder = path.resolve(__dirname, "../../public/tracks_folder");
function validTrackFormat(trackMimeType) {
// we could possibly accept other mimetypes...
var mimetypes = ["audio/mp3"];
return mimetypes.indexOf(trackMimeType) > -1;
}
function trackFileFilter(req, file, cb) {
cb(null, validTrackFormat(file.mimetype));
}
var trackStorage = multer.diskStorage({
// used to determine within which folder the uploaded files should be stored.
destination: function(req, file, callback) {
callback(null, uploadFolder);
},
filename: function(req, file, callback) {
// req.body.name should contain the name of track
callback(null, file.originalname);
}
});
var upload = multer({
storage: trackStorage,
fileFilter: trackFileFilter
});
router.post('/upload', upload.single("track"), function(req, res) {
console.log("Uploaded file: ", req.file); // Now it gives me undefined using Ajax!
res.redirect("/"); // or /#trackuploader
});
My guess is that multer is not understanding that fileToUpload is a file with name track (isn't it?), i.e. the middleware upload.single("track") is not working/parsing properly or nothing, or maybe it simply does not work with FormData, in that case it would be a mess. What would be the alternatives by keeping using multer?
How can I upload a file using AJAX and multer?
Don't hesitate to ask if you need more details.
multer uses multipart/form-data content-type requests for uploading files. Removing this bit from your doRequestSetHeaders function should fix your problem:
if(method === "POST" || method === "PUT"){
r.setRequestHeader("Content-Type", "application/json");
}
You don't need to specify the content-type since FormData objects already use the right encoding type. From the docs:
The transmitted data is in the same format that the form's submit()
method would use to send the data if the form's encoding type were set
to multipart/form-data.
Here's a working example. It assumes there's a dropzone with the id drop-zone and an upload button with an id of upload-button:
var dropArea = document.getElementById("drop-zone");
var uploadBtn = document.getElementById("upload-button");
var files = [];
uploadBtn.disabled = true;
uploadBtn.addEventListener("click", onUploadClick, false);
dropArea.addEventListener("dragenter", prevent, false);
dropArea.addEventListener("dragover", prevent, false);
dropArea.addEventListener("drop", onFilesDropped, false);
//----------------------------------------------------
function prevent(e){
e.stopPropagation();
e.preventDefault();
}
//----------------------------------------------------
function onFilesDropped(e){
prevent(e);
files = e.dataTransfer.files;
if (files.length){
uploadBtn.disabled = false;
}
}
//----------------------------------------------------
function onUploadClick(e){
if (files.length){
sendFile(files[0]);
}
}
//----------------------------------------------------
function sendFile(file){
var formData = new FormData();
var xhr = new XMLHttpRequest();
formData.append("track", file, file.name);
xhr.open("POST", "http://localhost:3000/tracks/upload", true);
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
console.log(xhr.responseText);
} else {
console.error(xhr.statusText);
}
}
};
xhr.send(formData);
}
The server side code is a simple express app with the exact router code you provided.
to post a FormData object accepted by multer the upload function should be like this:
function uploadFile(fileToUpload, url) {
var formData = new FormData();
//append file here
formData.append('file', fileToUpload, fileToUpload.name);
//and append the other fields as an object here
/* var user = {name: 'name from the form',
email: 'email from the form'
etc...
}*/
formData.append('user', user);
// This function simply creates an XMLHttpRequest object
// Opens the connection and sends form_data
doJSONRequest("POST", "/tracks/upload", null, formData, function(d) {
console.log(d);
})
}

AJAX post call to node.js server route - providing Cross origin request error

I am new to node.js and trying to use my basic API and call the correct router.get function in my route file.
I have a basic clientside javascript for the ajax post:
console.log("main.js loaded");
var form = document.getElementById('addUserForm');
form.addEventListener("submit", function(event){
event.preventDefault();
var username = document.getElementById('username').value;
var fd = new FormData();
fd.append('username', username);
window.ajaxCall.call("POST", "localhost:3000/api/users/add", fd, function(responseText){
// var response = JSON.parse(responseText);
console.log(response);
});
});
In this client side javascript I am using a custom xmlhttprequest library - I will provide the code below the whole question ( maybe the error for node js is there )
** UPDATE ** When I change the ajax call url to : /api/users/add or http://localhost:3000/api/users/add I get the following error : POST http://localhost:3000/api/users/add 404 (Not Found)
Here is my route file :
var express = require('express');
var router = express.Router();
/* GET home page. */
router.get('/users', function(req, res) {
var Users = require('../models/users'); // inkludiert das Users model
var data; // undefined data variable
Users.getData(function(err, result) { // ruft die getData Funktion vom Users Model auf
// TODO: Error handling.
data = result;
render(data); // ruft die render Funktion auf und übergibt das Resultobjekt
});
function render(data){
res.send(data);
}
});
router.get('/api/users/add', function(req, res){
console.log(req,res);
});
module.exports = router;
All I am trying to do is to call the router.get('api/users/add'... function in order to continue working with my api.
Now when I try to do that with my clientside javascript ajax call I get following error:
XMLHttpRequest cannot load localhost:3000/api/users/add. Cross origin requests are only supported for protocol schemes: http, data, chrome, chrome-extension, https, chrome-extension-resource.
When I inspect the error it shows that the error occurs on my window.ajax call in the js file as well as in the callback function in my library.
Here is the necessary library code:
window.ajaxCall = {
call: function(RequestType, pathToFile, data, cb){
var ajax = null;
if(window.XMLHttpRequest){ //Google Chrome, Mozilla Firefox, Opera, Safari,...
ajax = new XMLHttpRequest();
}else if(window.ActiveXObject){ // Internet Explorer
try{
ajax = new ActiveXObject("Msxml2.XMLHTTP.6.0");
} catch(e){
try{
ajax = new ActiveXObject("Msxml2.XMLHTTP.3.0");
}
catch(e){}
}
}
if(ajax!=null){
ajax.open(RequestType, pathToFile, true);
typeof data == "string" || typeof data == "array" ? ajax.setRequestHeader("Content-type", "application/x-www-form-urlencoded") : "" ;
ajax.onreadystatechange = function() {
if (ajax.readyState == 4) {
if(this.status == 200){
cb(ajax.responseText);
}
}
}
ajax.send(data);
}
},
abort: function(){
ajax.abort();
}
}
Add the protocol to your ajax call.
window.ajaxCall.call("POST", "http://localhost:3000/api/users/add", fd, function(responseText){
// var response = JSON.parse(responseText);
console.log(response);
});
Now, for the 404 part, your routes is expecting a get, but you are sending a post, change the router as follows:
router.post('/api/users/add', function(req, res){
console.log(req,res);
});

turn caching off in javascript

Hi all I am trying to turn caching off by
Adding a random value to the query string component of the URL sent with the request message.
I have a server that sends the etag as a string to my client and I want to make sure no caching is going on I already setRequestHeaders but i'm also supposed to add an http request similar to POST /message?x=0.123456789 HTTP/1.1
this is my client code
<html>
<header><title>This is title</title></header>
<body>
<span id="ajaxButton" style="cursor: pointer; text-decoration: underline">
Make a request
</span>
<script type="text/javascript">
(function() {
var httpRequest;
var x= Math.random();
document.getElementById("ajaxButton").onclick = function() { makeRequest('http://localhost:5000/'); };
function makeRequest(url) {
if (window.XMLHttpRequest) { // Mozilla, Safari, ...
httpRequest = new XMLHttpRequest();
} else if (window.ActiveXObject) { // IE
try {
httpRequest = new ActiveXObject("Msxml2.XMLHTTP");
}
catch (e) {
try {
httpRequest = new ActiveXObject("Microsoft.XMLHTTP");
}
catch (e) {}
}
}
if (!httpRequest) {
alert('Giving up :( Cannot create an XMLHTTP instance');
return false;
}
httpRequest.onreadystatechange = alertContents;
httpRequest.open('GET', url, true);
//httpRequest.setRequestHeader("pragma", "no-cache");
//httpRequest.setRequestHeader("Cache-Control", "no-cache", "no-store");
httpRequest.send();
}
function alertContents() {
if (httpRequest.readyState === 4) {
if (httpRequest.status === 200) {
var etagString = httpRequest.responseText;
alert(etagString);
} else {
alert('There was a problem with the request.');
}
}
}
})();
</script>
</body>
</html>
edit for adding errors
XMLHttpRequest cannot load http://localhost:5000/?_0.1909303846769035. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'null' is therefore not allowed access.
using node.js I run the server using main.js which is
var http = require('http');
var domain = require('domain');
var root = require('./root'); // do I have to replace root w/ message
var image = require('./image'); // for better readability?
function replyError(res) {
try {
res.writeHead(500);
res.end('Server error.');
} catch (err) {
console.error('Error sending response with code 500.');
}
};
function replyNotFound(res) {
res.writeHead(404);
res.end('not found');
}
function handleRequest(req, res) {
console.log('Handling request for ' + req.url);
if (req.url === '/') {
root.handle(req, res);
}
if (req.url === '/image.png'){
image.handle(req, res);
}
else {
replyNotFound(res);
}
}
var server = http.createServer();
server.on('request', function(req, res) {
var d = domain.create();
d.on('error', function(err) {
console.error(req.url, err.message);
replyError(res);
});
d.run(function() { handleRequest(req, res)});
});
function listen(){
server.listen(5000);
}
root.init(listen);
and inside root.js is
var http = require('http');
var response = require('./response');
var body;
var etag;
exports.handle = function(req, res) {
if (req.headers['if-none-match'] === etag) {
console.log('returning 304');
return response.replyNotModified(res);
}
res.writeHead(200, {'Content-Type': 'text/plain',
'Content-Length': body.length,
"Access-Control-Allow-Origin":"*",
"Access-Control-Allow-Headers":"X-Requested-With",
'ETag' : etag
});
res.end(body);
}
exports.init = function(cb) {
require('fs').readFile('app.html', function(err, data) {
if (err) throw err;
etag = response.generateETag(data); //
body = etag;
console.log("init");
cb();
});
}
/*function generateETag(buffer) {
var shasum = require('crypto').createHash('sha1');
shasum.update(buffer, 'binary');
return shasum.digest('hex');
console.log(shasum.digest('hex'));
}
var replyNotModified = function(res) {
res.writeHead(304);
res.end();
};*/
the errors are in
So, the error that you're getting is to do with cross-origin resource sharing, which has nothing to do with caching or query strings. It looks like you're trying to make AJAX calls from a file:// url, which you can't do.
If you serve the page in question from your Node.js app, that message should go away.
If you can't do that, set up that app to send CORS headers. You can read about CORS in detail at MDN, but the short version is that you need to send a header that looks like this (where otherdomain.com is where the Web page is hosted):
Access-Control-Allow-Origin: http://otherdomain.com
Note that you'll still have to serve the page over HTTP; to my knowledge you can't do AJAX at all from a page loaded via a file:// URL.
You could add '_=' + new Date().getTime(); to the query string of the url. Since it isn't clear whether the url already has a query string attached to it, it's hard to give a more complete answer. It'd be either url += '?_=' + new Date().getTime(); or url += '&_=' + new Date().getTime();.
I'll leave this answer here because it seems to answer the question that the OP was asking. But the solution to the problem the OP was experiencing is Adam Brenecki's answer below.

Categories

Resources