Backbone.js won't post to the specified URL - javascript

I have a Backbone.js application running with this generator https://github.com/yeoman/generator-backbone. I need to make a POST request to a service running on a different server and domain. On my model I have the following:
urlRoot: 'https://custom.com/',
saveData: function(params) {
this.set(params);
if(this.isValid()) {
this.save(null, {
url: 'api/v1/data',
success: function(model, response, options) {
// success
},
error: function(model, response, options) {
// error
}
});
}
}
When I try posting the data, it posts the data to http://localhost:9000/api/v1/data. Any idea why Backbone chooses to POST to the server it is runnning on, instead of the settings I supplied (https://custom.com/api/v1/data)?
On my server, I'm running Restify on top of NodeJS (I'm allowing origin from any client for testing purposes). Here's how I setup CORS:
// Setup CORS
api.use(function(req, res, next) {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE, OPTIONS');
res.header('Access-Control-Allow-Headers', 'X-Requested-With');
return next();
});
I doubt there is a problem with the server, since it isn't even being reached by the client because somehow the specified URL automatically maps to http://localhost:9000/api/v1/data instead of https://custom.com/api/v1/data.
UPDATE: If i do it "manually" with jQuery (without using Backbone's API), it works:
saveData: function() {
$.ajax({
type: 'POST',
data: this.toJSON(),
url: 'https://custom.com/api/v1/data'
})
.success(function() {
// success
})
.fail(function() {
// fail
});
}
Is there anyway to do it using Backbone's API?

Passing a url as an option to Backbone.Model.prototype.save will circumvent the url generation logic (i.e. urlRoot) and assume the url option is the full path (including the domain if not POSTing to the current domain).
You should instead set the urlRoot to the path of the entity (or whatever your backend platform may call it), so in your case it should be:
var MyModel = Backbone.Model.extend({
urlRoot: 'https://custom.com/api/v1/data'
});
var myModel = new MyModel();
myModel.save(); // POST https://custom.com/api/v1/data
// Assuming that returns an id of '123';
myModel.save(); // PUT https://custom.com/api/v1/data/123
myModel.destroy(); // DELETE https://custom.com/api/v1/data/123
If your server isn't set up to handle Backbone style RESTful endpoints then you probably just want to override Backbone.Model.prototype.url instead of urlRoot.

If you only wanna test calls to the server, you can download a chrome extension. It will allow you to make calls to a different domain. Here : https://chrome.google.com/webstore/detail/allow-control-allow-origi/nlfbmbojpeacfghkpbjhddihlkkiljbi

Post request to a different domain will not work from browser. Better way to handle this is you can set some redirect rules in server.

Related

Node.js: POST request sent twice by system

I send JSON POST data via a form in a MEAN environment to my server. On the server side, I process the data inside of a waterfall function, using the async library, including various operations such as:
[...]
- create a database entry for a new author
- create a database entry for a new book
- associate the new book to an author (reference to book ID)
[...]
This is the method called by my route, which handles the associated POST-request:
exports.createAuthor = function(req, res) {
console.log(req.url+' !!!POST REQUEST INCOMING!!! '+req.body);
async.waterfall([
function(callback){
//create Author db entry
},
function(parameter, callback){
//add author to additional directory (db action)
},
function(parameter, callback){
//create book db entry
},
function(parameter, callback){
//associate book to author (db action)
}
], function (err, result) {
console.log('DONE!!!');
res.send('200');
});
}
This is the client-side AngularJS controller code:
searchApp = angular.module("searchApp",[]);
searchApp.controller('authorCreator', function ($scope,$http) {
$scope.tags = [];
$scope.sendAuthor = function(){
alert('I was called!');
$http({
method: 'POST',
url: '/newauthor/',
data: { 'authorname' : $scope.authorName,
'authordescription' : $scope.authorDescr,
'bookname' : $scope.bookName,
'tags' : $scope.tags }
})
.success(function(data){
//no actions yet
})
.error(function(){
//no actions yet
});
};
});
This is the AngularJS form:
<div ng-controller="authorCreator">
<form>
<p>Author name: <input ng-model="authorName"></p>
<p>Author description: <input ng-model="authorDescr"></p>
<p>Book name: <input ng-model="bookName"></p>
<p>Tags:<input ng-model="tags"></p>
<p><button ng-click="sendAuthor()">Send</button></p>
</form>
</div>
I noticed that, if the waterfall-process is "stuck" somewhere, meaning the client does not get an answer to it's request whatsoever, the POST request seems to be sent a second time automatically (as soon as the browser is giving a timeout according to firebug). According to firebug, a second POST request does not seem to be sent by the browser, so the call must be initiated from somewhere else.
I found out by checking the database (multiple documents with identical values, except the ObjectID of course) and monitoring the node.js console window where I output incoming POST data. Again: as soon as the entire waterfall-process completes, hence the client browser does not abort the post request after a while, and res.send('200') executes, the error does not occur (= no multiple db entries).
Can anyone please tell me, who does initiate this second POST request and how may I deactivate it?
Cheers
Igor
Try adding this:
exports.createAuthor = function(req, res) {
if(req.method == 'POST' && req.url = 'REQUESTEDURL'){
console.log('POST REQUEST INCOMING!!! '+req.body);
async.waterfall([
//TODO...
]);
}
Maybe the problem is that the favicon or some other resource is doing a request to
After spending some time on that issue I found out, that this error seems to be based on missing answers to the client (be it via res.json, res.sendfile, ...). Therefore the client seems to re-send the request after some time, thus executing server-side code a second time. Responding to the client in reasonable time solves this issue. Sorry for the confusion.
i "fixed" this by adding
.get('/favicon.ico:1', (req, res) =>{
//do nothing because i dont care
})

Saving the user session with a single page app using CORS?

I have read a few articles around the web and even implemented a few, but for the most part following along is quite tedious and gets a bit off track from my base code causing confusion and wasted time.
That said I know I am close with how I have things implemented, I just need access to the req.user object which I am pretty sure is stored in a session variable. I want to provide what I have done and figure out how to make that extra push in getting a user login session to stick onto my single page abstracted app (cordova/phonegap).
On the server side I am using (node.js, express.js, and passport.js). I generally will allow express to render the views, but since I am using a CORS abstracted app I don't want to send the template to the client over an AJAX call, so I built the views on the client side, basically at this point regardless of what HTML is rendered I realized I just need to have one AJAX POST call to login the user, to invoke the POST route on my server which authenticates the user.
$(document).ready(function() {
$('body').on('submit', '#logIn', function(e){
e.preventDefault();
var formData = $(this).serialize();
$.ajax({
url: "http://mysite.io:3300/login",
data: formData,
type: "POST",
crossDomain: true,
dataType: "json",
success: function(data, textStatus, jqXHR) {
alert('succeeded!');
console.log('success')
},
error: function(jqXHR, textStatus, errorThrown) {
console.log(textStatus, errorThrown);
console.log('error')
}
});
});
});
My question at this point has to do with the passport req.user object. In the client side of the CORS app, the session isn't intact it does not seem because the client relies on an api call that only a logged in user can access, I have this route.
me: function(req, res) {
if(req.user) {
var admin;
if(req.user.admin === true) {
admin = true;
}
var user = {
admin : admin,
user_id : req.user.id,
name : req.user.local.name,
email : req.user.local.email,
}
res.json(user);
} else {
res.redirect('/login');
}
}
I also have other routing methods that rely on the req.user object. So the initial POST method returns the user object, like the name etc, because I have a routing method that looks like this.
//cordova post method
app.post('/login', passport.authenticate('local-login'), function(req, res) {
if (req.user) {
console.log('is user');
res.send({ user: req.user });
}
else
console.log('is not user');
});
Within this method the req.user returns true giving me access to the req.user object and the template, so when making the ajax POST call I am able to render the user profile. After this or on different routing calls that object is false.
So again my question is how can I save the req.user object so I can access it in other methods so that the app knows YES the user is logged in.. obviously that is stored in the session variable, but I am confused upon implementing it?
I don't know how you're handing CORS server-side, but you may want to look into using the cors middleware to make things easier. Either way, you need to make sure you have the Access-Control-Allow-Credentials header set to the value of true.
For the cors middleware, all you have to do is set credentials: true in the cors middleware config. Otherwise you can set the header via res.header('Access-Control-Allow-Credentials', 'true');
You also will need to set withCredentials to true wherever you do any ajax calls so that the cookies will be sent. For $.ajax you would do:
xhrFields: {
withCredentials: true
}
Angular has a similar setting for it's $http service.
After that, you should see your req.user get auto-populated for authenticated sessions.

Backbone PUT request to cross domain is not appending id to URL

I have the following backbone model.
var aModel = Backbone.Model.extend({
url: 'http://api.site1.com/list
});
modelObj = new aModel();
Domain of my application is product.site1.com, so all the request made to server by my application are cross domain requests.
All my backbone requests to the API needs common header, so I have written following backbone sync method
var sync = Backbone.sync;
Backbone.sync = function(method, model, options) {
options.beforeSend = function (xhr) {
xhr.setRequestHeader('key1', 'ABCD');
xhr.setRequestHeader('key2', 'EFGH');
};
sync(method, model, options);
};
When I execute fetch method like below
modelObj.fetch();
My application makes a OPTIONS request to the api.site1.com/list
On success of that request it makes a GET request to api.site1.com/list
This is the same case for POST as well.
When I perform
modelObj.save({name: "abc"}) before fetch, my app sends OPTIONS request to api.site1.com/list, on success it performs POST request.
Whereas when I do it for update i.e PUT,
I expect backbone appends id to the url, like api.site1.com/list/1 and performs OPTIONS request
But when I check my dev tools, the OPTIONS request is made to api.site1.com/list. On success, it makes PUT request to api.site1.com/list instead of api.site1.com/list/1
Why is this happening and how to handle this case?
By setting aModel.url, you force the url used in the requests. Set aModel.urlRoot instead :
urlRoot model.urlRoot or model.urlRoot()
Specify a urlRoot if you're
using a model outside of a collection, to enable the default url
function to generate URLs based on the model id.
Try
var aModel = Backbone.Model.extend({
urlRoot: 'http://api.site1.com/list'
});

Socket.io IE7-9 JSONP Polling error

We've spent a better part of yesterday trying to get this resolved. So as a last ditch effort i'm posting here.
Our setup is a Node.js / Express backend. With Socket.io on the same port as express.
app.io = io.listen(server);
app.io.set('origin', '*');
app.io.set('log level', '2');
app.io.enable('browser client minification');
app.io.set('transports', [
'websocket',
'flashsocket',
'htmlfile',
'xhr-polling',
'jsonp-polling'
]);
I've explicitly enabled all of the transports. We were hoping either jsonp or flash sockets would play nice on our least favorite browsers...
But, I guess there is a reason that they're our least favorite. ;-)
Client side we've got an Angularjs application with socket.io. Very loosely based on this tutorial
window.WEB_SOCKET_SWF_LOCATION = 'https://api.ourdomain.com/socket.io/static/flashsocket/WebSocketMain.swf';
var socket = io.connect('https://api.ourdomain.com', {
'reconnect' : true,
'reconnection delay' : 1500
});
We've tried adding the SWF location as seen here to get flashsockets working. It is serving up the policy and getting the files.. So no luck on that.
Anyways on ie7-9 when it attempts to connect via jsonp polling we get this error:
Object doesn't support this property or method
Contents of the jsonp message will be something like this:
io.j[1]("1::");
Occasionally with more content in it.
io.j seems to be being set as an array in the main socket.io.js file.
When I put this in the developer tools console, it throws the same error.
I've tried moving all the meta tags before the scripts like suggested here. That didn't change anything.
XHR-polling doesn't seem to work either. We've seen some posts about changing settings in ie.. But obviously we can't require our clients to go and request their IT department to change their browser settings just to visit our site. So we've ditched that method.
Also tried creating a blank page, and connecting.. That works. So what would be interfering?
Hopefully you guys have something?
We were unable to resolve this issue by simply making Socket.io work. I don't know if this is a Socket.io issue or if this is a combo of Angularjs and Socket.io.
We did however resolve this issue by adding our own fallback. In our Socket.io service we check for the existence of a feature present in ie9+ as well as webkit and firefox browsers.
var use_socketIO = true;
/*
I'd like to detect websocket, or jsonp.
But those transport methods themselves work.
Just not reliably enough to actually use
*/
if (typeof(window.atob) === 'undefined') {
console.log('browser appears to be < ie10.');
use_socketIO = false;
}
if (use_socketIO) {
var socket = io.connect('https://api.ourdomain.com', {
'reconnect' : true,
'reconnection delay' : 1500
});
}
// Fall back http method.
function httpReq(method, url, data) {
var defer = $q.defer();
$http({
method: method,
url: '//www.domain.com/api'+url,
data: data
}).success(function (data, status, headers, config) {
defer.resolve(data)
});
return defer.promise;
}
// Socket.io method.
function socketReq(method, url, data) {
var defer = $q.defer();
socket.emit(method, {url: url, data: data}, function (response) {
try {
var data = JSON.parse(response);
defer.resolve(data);
} catch (e) {
$log.error('Failed to parse JSON');
$log.info(response);
defer.reject(e);
}
});
return defer.promise;
}
// Main Request method
function request(method, url, data) {
if (use_socketIO) {
return socketReq(method, url, data || {});
} else {
return httpReq(method, url, data || {});
}
}
Then we simply just call request('get', '/url');
Server side we do some magic to build a req and res object if its Socket.io and then forward it to express to handle, otherwise express just handles it like normal.

How to use Azure mobile service REST api?

The new custom API script allows a lot of customization through any type of connection.
I found that this website Custom API in Azure Mobile Services – Client SDKs describes the custom API.
var client = new WindowsAzure.MobileServiceClient('http://myservice.azure-mobile.net/', 'mykey');
client.invokeApi('query', {
method: 'POST'
});
But I couldn't run this code. It is supposed to show a message "Hello post World!".
I put the code inside tags in an HTML file and ran it but nothing happened.
Any help?
The call you have is making a call to your service, but it's ignoring its response. Assuming you have a custom API called 'query' (since it's what you're passing to invokeApi) with the following body:
exports.post = function(request, response) {
response.send(200, { message: 'Hello world' });
};
Your client code is calling it and (if everything goes fine) receiving the response, but it's not doing anything with it. There are a couple of ways to find out whether the call is being made. For example, you can add a log entry in the API and check the logs in your service:
exports.post = function(request, response) {
console.log('The API was called');
response.send(200, { message: 'Hello world' });
};
Or you can use a networking tool (the browser developer tools or Fiddler, for example) to see if the request is being made. Or you can actually do something with the result in the client side:
var client = new WindowsAzure.MobileServiceClient('http://myservice.azure-mobile.net/', 'mykey');
client.invokeApi('query', {
method: 'POST'
}).done(
function(result) { alert('Result: ' + JSON.stringify(result)); },
function(error) { alert('Error: ' + error); }
);
One thing which you need to look at if you're calling the API from a browser is whether the domain from where the page is being loaded is in the 'allow requests from host names' list, under the 'configure' tab, 'cross-origin resource sharing (cors)' section. If it's not, then you may get an error instead of the response you want.

Categories