Reputation: 2610
According to my searches this shouldn't be that hard, and I have tried many things with no luck so far. I have a Node.js server running on Heroku that creates a socket.io server (I also have tried hosting it on Nodejitsu, which didn't help). The socket.io socket connection works with no problems if I serve the page from the actual Node.js server that I want to create a websocket connection to, but I can't make any of my cross-domain socket connection attempts work.
I have tried to do a cross-domain connect to this socket.io server several ways:
(1) A simple local HTML file with some script that fetches client side socket.io library successfully from node server (using $.getScript), and then tries to create a socket connection (var socket = io.connect('nodejs_server_hostname:port_num');), which fails.
(2) Running rails app server locally (development mode) that serves up a response to clients containing javascript that tries to form a websocket connection in the same fashion as method (1), and fails
(3) Hosting rails app in Heroku, and doing the same thing as method (2). This fails as well. I made sure to enable websockets as specified in the Heroku documentation (https://devcenter.heroku.com/articles/heroku-labs-websockets). Once again, everything works if the request is served from the actual Node.js server that the client attempts to form a socket.io connection with.
The socket.socket.on('error') event is trigged in all three attempts.
I have tried modifying the http response headers in the rails app using the following code:
response.headers['Access-Control-Allow-Origin'] = '*'
This sets the headers correctly (I checked after requesting page from the rails server), but it doesn't fix the socket connection problem.
I created an socket-io authorization callback on the Node.js server that performs logging, and it seems this callback is never even hit at all for the cross-domain socket connection attempts. So it seems like the Node.js server never even sees the websocket connection attempt, which is quite confusing to me.
Below I will paste all the relevant code files.
Node.js server file:
var http = require('http'),
express = require('express'),
whiskers = require('whiskers'),
redis = require('redis'),
port = process.env.PORT || 5000,
app = express(),
server = http.createServer(app).listen(port),
io = require('socket.io').listen(server);
console.log('---- ' + port);
console.log(port);
console.log('---- ' + port);
io.configure(function () {
io.set('authorization', function (handshakeData, callback) {
console.log("-- inside io.set('authorization, cb)")
console.log('-- handshakeData:');
console.log(handshakeData);
console.log('-- end of handshakeData logging --');
callback(null, true); // error first callback style
});
});
app.configure(function() {
app.use(express.static(__dirname + '/public'));
app.set('views', __dirname + '/views');
app.set('view options', { layout: false });
app.engine('.html', whiskers.__express);
});
app.get('/', function(req, res) {
res.render('index.html');
});
io.sockets.on('connection', function(socket) {
console.log('---- new client connected ----');
console.log(socket);
console.log('---- end of socket details logging for new client connection ----');
socket.emit('news', { hello: 'world' });
socket.on('my other event', function(data) {
console.log("-- server -- inside socket.on 'my other event' callback");
console.log(data);
});
socket.on('button clicked', function(data) {
console.log("-- server -- inside socket.on 'button clicked' callback");
console.log(data);
io.sockets.emit('news', { info: 'the button was clicked' });
});
});
Here is the client-side javascript file from Rails app:
$(document).ready(function() {
console.log('-- inside chat.js -- document is ready');
$('#join-chat-container').hide();
$('#new-message-container').hide();
//$.getScript('http://node-server-for-rails-chat.herokuapp.com/socket.io/socket.io.js')
$.getScript('http://node-server-for-rails-chat.jit.su/socket.io/socket.io.js')
.done(function( script, textStatus ) {
console.log("-- socket.io.js loaded and executed -- textStatus: " + textStatus);
setup_socket();
})
.fail(function( jqxhr, settings, exception ) {
console.log("-- socket.io.js load error -- exception:");
console.log(exception);
console.log("-- jqxhr: " + jqxhr);
});
});
function setup_socket() {
//var socket = io.connect('node-server-for-rails-chat.herokuapp.com', { port: 19364 });
//var socket = io.connect('node-server-for-rails-chat.jit.su', { port: 5000 });
var socket = io.connect('node-server-for-rails-chat.herokuapp.com:27305');
socket.socket.on('error', function (reason) {
console.log('--- cant connect');
console.log(reason);
console.error('Unable to connect Socket.IO', reason);
});
socket.on('connect_failed', function() {
console.log('connect failed');
});
socket.on('connect', function() {
console.log('successfully connected!');
});
socket.on('message', function (message_html) {
console.log("-- client -- inside socket.on('message', cb) callback");
console.log("-- client -- message_html: " + message_html);
add_message(message_html);
});
if (!socket.socket.connected) {
console.log(socket);
console.log("-- Error connecting with socket");
}
socket_loaded(socket);
};
function socket_loaded(socket) {
// this part contains irrelevant (not socket related) code
}
//more irrelevant code excluded for brevity (add_message function, plus other stuff)
This is the stand-alone HTML file I opened locally in browser:
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="http://code.jquery.com/jquery-git.js"></script>
<script>
$(document).ready(function() {
if (typeof jQuery !== 'undefined') {
console.log('-- client -- jQuery library loaded successfully');
$('#myButton').on('click', function() {
console.log("-- client -- inside jquery '#myButton' click callback");
socket.emit('button clicked', { button: '#myButton' });
});
}
$.getScript('http://node-server-for-rails-chat.herokuapp.com/socket.io/socket.io.js')
.done(function( script, textStatus ) {
console.log("-- socket.io.js loaded and executed -- textStatus: " + textStatus);
socket_is_loaded();
})
.fail(function( jqxhr, settings, exception ) {
console.log("-- socket.io.js load error -- exception:");
console.log(exception);
console.log("-- jqxhr: " + jqxhr);
});
});
function socket_is_loaded() {
//var socket = io.connect('node-server-for-rails-chat.jit.su:5000');
var socket = io.connect('node-server-for-rails-chat.herokuapp.com:27305');
socket.socket.on('error', function (reason) {
console.log('--- cant connect');
console.log(reason);
console.error('Unable to connect Socket.IO', reason);
});
socket.on('connect_failed', function() {
console.log('connect failed');
});
socket.on('connect', function() {
console.log('successfully connected!');
});
socket.on('news', function(data) {
console.log("-- client -- inside socket.on 'news' callback");
console.log(data);
//socket.emit('my other event', { my: 'data' });
});
}
</script>
</head>
<body>
<h1>Socket.io How-to-use Example Script</h1>
<input type='button' id='myButton' value='click me!'/>
</body>
</html>
Does anyone have ideas on how I can solve this problem? Thanks a bunch.
Upvotes: 5
Views: 14131
Reputation: 3707
Socket.IO version --> 1.3.7 Express version --> 4.13.3
Option 1: Force use of Websockets only
By default, websockets are cross domain. If you force Socket.io to only use that as means to connect client and server, you are good to go.
Server side
//HTTP Server
var server = require('http').createServer(app).listen(8888);
var io = require('socket.io').listen(server);
//Allow Cross Domain Requests
io.set('transports', [ 'websocket' ]);
Client side
var connectionOptions = {
"force new connection" : true,
"reconnectionAttempts": "Infinity", //avoid having user reconnect manually in order to prevent dead clients after a server restart
"timeout" : 10000, //before connect_error and connect_timeout are emitted.
"transports" : ["websocket"]
};
var socket = io("ur-node-server-domain", connectionOptions);
That's it. Problem? Won't work on browsers (for clients) who don't support websockets. With this you pretty much kill the magic that is Socket.io, since it gradually starts off with long polling to later upgrade to websockets (if client supports it.)
If you are 100% sure all your clients will access with HTML5 compliant browsers, then you are good to go.
Option 2: Allow CORS on server side, let Socket.io handle whether to use websockets or long polling.
For this case, you only need to adjust server side setup. The client connection is same as always.
Server side
//HTTP Server
var express=require('express');
//Express instance
var app = express();
//ENABLE CORS
app.all('/', function(req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "X-Requested-With");
next();
});
That's it. Hope it helps anyone else. Remember, for the second option it's very important that the //ENABLE CORS part is the first thing you apply to your express instance ( express() );
Upvotes: 0
Reputation: 141
I have a similar setup running and had to configure the following headers on the node express web server.
app = require('express')();
...
app.use(function(req, res, next) {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'GET, OPTIONS');
res.header('Access-Control-Allow-Headers', 'Content-Type');
return next();
});
Upvotes: 3