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.
Related
I want to load some template files dynamically with the help of ajax. I have added the ajax $.get method for loading the html files and it's working fine with all browsers except safari browser.
In safari it gives me "Failed to load resource: cancelled" error when first time I open the url. However after I refresh my page again, it loads all the files.
When I open my url with http request instead of https, it can load the template file in first time on safari browser.
This issue only happens when I open the url with https. I have successfully installed the certificate and its working fine with other browser. Even there is no certificate issue in safari as well.
Here is my code
var decorator = {
init: function(book, cd) {
this.loadTPL(cd);
},
tpl: {
btnStart: "tpl/startBtn.html",
interfaceTpl: "tpl/interfaceTpl.html",
topMenu: "tpl/topMenu.html",
topMenuItem: "tpl/topMenuItem.html",
},
loadTPL: function(cbTpl) {
var self = this;
var objTpl = {};
async.forEachOf(this.tpl, function(value, key, callback) {
$.get(value, {}, function(data) {
//alert("Load was performed.");
//console.log(value, data);
objTpl[key] = data;
callback();
});
}, function(err, results) {
if (err) {
console.log(err);
}
self.tpl = objTpl;
cbTpl(err);
});
}
}
Any Idea?
While your approach "should" work, it goes into the weird unknown areas of JS, specially using the async lib. So, my solution basically involves refactoring all of it. Instead async you can use jQuery promises to fire all the gets you need, and then handle the responses/errors in each one of them with the promises handlers.
As an example:
$(templatesToLoad).each(function (element, index) {
$.ajax({element.url, cache: false })
.done(function (result) {
objTpl[key] = result;
element.allback(); // callback for each template
})
.fail(function () {
alert( "error" );
})
.always(function () {
alert( "completed" );
});
});
Note:$.get its just a sugar code for $.ajax. By default $.ajax performs a get, unless another method is specified.
The browser, whichever it is, will handle the calls and it will trigger each one of them as soon as permitted, based on each browser capabilities and limitations, so no need to worry about specific implementations.
As general rule, always remember to check the encoding of the calls and responses and their formats, json, text or whatever you use as a response format.
This is likely a cache/timeout issue. Try setting the ajax timeout to something huge. If that works, back it off until you find the sweet spot.
Phantom.js documentation shows how to monitor HTTP communication: http://phantomjs.org/network-monitoring.html
However, it does not work for WebSockets. How can I monitor WebSocket communication in Phantom.js?
PhantomJS 1.x does not support WebSockets1, so you cannot monitor them. If the page uses some fallback for WebSockets, then the page.onResourceRequested and page.onResourceReceived can be used to log the meta data of the messages. PhantomJS does not expose the actual data sent in any way.
WebSockets work correctly in PhantomJS 2. Since no fallback is necessary, it is not possible to observe the traffic with those events. The event handlers mentioned above don't show anything. The only way to see the messages would be to proxy the WebSocket object as early as possible:
page.onInitialized = function(){
page.evaluate(function(){
(function(w){
var oldWS = w.WebSocket;
w.WebSocket = function(uri){
this.ws = new oldWS(uri);
...
};
w.WebSocket.prototype.send = function(msg){
w.callPhantom({type: "ws", sent: "msg"});
this.ws.send(msg);
};
...
})(window);
});
};
page.onCallback = function(data){
console.log(JSON.stringify(data, undefined, 4));
};
1 My tests actually show that the websocket echo page works with v1.9.6 and up, but not v1.9.0.
If the page uses some fallback for WebSockets, then the
page.onResourceRequested and page.onResourceReceived can be used to
log the meta data of the messages. PhantomJS does not expose the
actual data sent in any way.
Slimer JS helped solve this problem.
Slimer JS will let you capture the response body. Which is not possible with phantom JS 1.x and this feature is removed from latest 2.x.
https://github.com/ariya/phantomjs/issues/10158
Using slimerjs, you can capture content for XHR requests, but you cannot do it for websocket requests.
So we will be explicitly disabling websockets for the webpage when the page is initialized, ( page.onInitialized ), so that the webpage will be using the fallback, which is XHR, and then you can capture the content.
page.captureContent = [ /application\/javascript/ ];
page.onInitialized = function() {
page.evaluate(function() {
(function(w) {
window.WebSocket = undefined; // we are explicitly disabling websockets for the page. so that the website will be using the fallback
})(window);
});
};
page.open(url, function(status) {
})
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.
I'm putting together an app using WP-API with an Angular frontend. I'm developing locally, with the data I'm trying to use loaded in from a remote server. Doing a $resource request for all posts works great.
But, I'm trying now to get the result of the X-WP-TotalPages header and can't figure out how to do it. Here's the code as it stands:
var request = function() {
var fullRoute = 'http://dylangattey.com/wp-json/posts/';
var defaultGet = {
method: 'GET',
transformResponse: function(data, headers){
response = {};
response.posts = JSON.parse(data);
response.headers = headers();
console.log(headers['X-WP-TotalPages']);
return response;
}
};
return $resource(fullRoute, {}, {
get: defaultGet,
query: defaultGet
});
};
// This gives me back the first 10 posts
request().query();
Chrome and curl both show that X-WP-TotalPages should be equal to 2 as a header. However, it just logs undefined.
Am I missing something? No matter whether I use $http or $resource I get the same result. I also have the same issue whether I use a remote site or a local WP installation on localhost. Really, I just want to know the total number of pages or even just the total number of posts for a given request, so if there's a better way to do it, I'd love to know.
You probably need to control the access to the specific headers you want on the server side.
See this thread for a brief discussion, or MDN on Access-Control-Expose-Headers.
I am trying to access an API using AngularJS. I have checked the API functionality with the following node code. This rules out that the fault lies with
var http = require("http");
url = 'http://www.asterank.com/api/kepler?query={"PER":{"$lt":1.02595675,"$gt":0.67125}}&limit=10';
var request = http.get(url, function (response) {
var buffer = ""
response.on("data", function (chunk) {
buffer += chunk;
});
response.on("end", function (err) {
console.log(buffer);
console.log("\n");
});
});
I run my angular app with node http-server, with the following arguments
"start": "http-server --cors -a localhost -p 8000 -c-1"
And my angular controller looks as follows
app.controller('Request', function($scope, $http){
// functional URL = http://www.w3schools.com/website/Customers_JSON.php
$scope.test = "functional";
$scope.get = function(){
$http.get('http://www.asterank.com/api/kepler?query={"PER":{"$lt":1.02595675,"$gt":0.67125}}&limit=10',{
params: {
headers: {
//'Access-Control-Allow-Origin': '*'
'Access-Control-Request-Headers' : 'access-control-allow-origin'
}
}
})
.success(function(result) {
console.log("Success", result);
$scope.result = result;
}).error(function() {
console.log("error");
});
// the above is sending a GET request rather than an OPTIONS request
};
});
The controller can parse the w3schools URL, but it consistently returns the CORS error when passed the asterank URL.
My app avails of other remedies suggested for CORS on this site (below).
Inspecting the GET requests through Firefox shows that the headers are not being added to the GET request. But beyond that I do not know how to remedy this. Help appreciated for someone learning their way through Angular.
I have tried using $http.jsonp(). The GET request executes successfully (over the network) but the angular method returns the .error() function.
var app = angular.module('sliderDemoApp', ['ngSlider', 'ngResource']);
.config(function($httpProvider) {
//Enable cross domain calls
$httpProvider.defaults.useXDomain = true;
delete $httpProvider.defaults.headers.common['X-Requested-With'];
});
You should understand one simple thing: even though those http modules look somewhat similar, they are totally different beasts in regards to CORS.
Actually, the node.js http.get() has nothing to do with CORS. It's your server that makes a request - in the same way as your browser does when you type this URL in its location bar and command to open it. The user agents are different, yes, but the process in general is the same: a client accesses a page lying on an external server.
Now note the difference with angular's $http.get(): a client opens a page that runs a script, and this script attempts to access a page lying on an external server. In other words, this request runs in the context of another page - lying within its own domain. And unless this domain is allowed by the external server to access it in the client code, it's just not possible - that's the point of CORS, after all.
There are different workarounds: JSONP - which basically means wrapping the response into a function call - is one possible way. But it has the same key point as, well, the other workarounds - it's the external server that should allow this form of communication. Otherwise your request for JSONP is just ignored: server sends back a regular JSON, which causes an error when trying to process it as a function call.
The bottom line: unless the external server's willing to cooperate on that matter, you won't be able to use its data in your client-side application - unless you pass this data via your server (which will act like a proxy).
Asterank now allows cross origin requests to their API. You don't need to worry about these workarounds posted above any more. A simple $http.get(http://www.asterank.com/api/kepler?query={"PER":{"$lt":1.02595675,"$gt":0.67125}}&limit=10')
will work now. No headers required.I emailed them about this issue last week and they responded and configured their server to allow all origin requests.
Exact email response from Asterank : "I just enabled CORS for Asterank (ie Access-Control-Allow-Origin *). Hope this helps!"
I was having a similar issue with CORS yesterday, I worked around it using a form, hopefully this helps.
.config(function($httpProvider){
delete $httpProvider.defaults.headers.common['X-Requested-With'];
$httpProvider.defaults.headers.common = {};
$httpProvider.defaults.headers.post = {};
$httpProvider.defaults.headers.put = {};
$httpProvider.defaults.headers.patch = {};
})
.controller('FormCtrl', function ($scope, $http) {
$scope.data = {
q: "test"//,
// z: "xxx"
};
$scope.submitForm = function () {
var filters = $scope.data;
var queryString ='';
for (i in filters){
queryString=queryString + i+"=" + filters[i] + "&";
}
$http.defaults.useXDomain = true;
var getData = {
method: 'GET',
url: 'https://YOUSEARCHDOMAIN/2013-01-01/search?' + queryString,
headers: {
'Content-Type': 'application/json; charset=utf-8'
}
};
console.log("posting data....");
$http(getData).success(function(data, status, headers, config) {
console.log(data);
}).error(function(data, status, headers, config) {
});
}
})
<div ng-controller="FormCtrl">
<form ng-submit="submitForm()">
First names: <input type="text" name="form.firstname">
Email Address: <input type="text" ng-model="form.emailaddress">
<button>bmyutton</button>
</form>
</div>
Seems to work with the url you posted above as well..
ObjectA: 0.017DEC: 50.2413KMAG: 10.961KOI: 72.01MSTAR: 1.03PER: 0.8374903RA: 19.04529ROW: 31RPLANET: 1.38RSTAR: 1T0: 64.57439TPLANET: 1903TSTAR: 5627UPER: 0.0000015UT0: 0.00026
I should also add that in chrome you need the CORS plugin. I didn't dig into the issue quite as indepth as I should for angular. I found a base html can get around these CORS restrictions, this is just a work around until I have more time to understand the issue.
After lots of looking around. The best local solution I found for this is the npm module CORS-anywhere. Used it to create AngularJS AWS Cloudsearch Demo.