Use both http and https for socket.io

I am trying to make socket.io work both on http and https connections, but it seems that with my configuration it can work only on one of them.

With the below config options socket.io can access my application through https, but when trying to access through regular http it cannot connect and i receive errors.

    var app = express()
  , http= require('http').createServer(app)
  , https = require('https').createServer(options, app)
  , io = require('socket.io').listen(https, { log: false })

And later i have this

http.listen(80, serverAddress);
https.listen(443, serverAddress);

On client side i have this:

<script src='/socket.io/socket.io.js'></script>

var socket = io.connect('https://<%= serverAddress %>', {secure: true, 'sync disconnect on unload' : true});

Of course if i switch the http with the https options on the .listen and .connect functions of the server and the client respectively i am having the reverse results. Socket.io can access through http and not through https.

How is it possible to achieve this? I need it mostly because it is about a facebook app, so it must provide both http and https connection options according to facebook rules.

Edit: In case it helps about the problem, the error i am receiving is the below

Failed to load resource: the server responded with a status of 404 (Not Found) http://DOMAIN/socket.io/socket.io.js

And because of this i get others such as:

Uncaught ReferenceError: io is not defined 

Answers


I believe the problem is in your way of setting up socket.io on the server side and on the client.

Here's how I made it work (just for you).

Server:

var debug = require('debug')('httpssetuid');
var app = require('../app');
var http = require('http');
var https = require('https');
var fs = require('fs');
var exec = require('child_process').exec;
var EventEmitter = require('events').EventEmitter;
var ioServer = require('socket.io');

var startupItems = [];
startupItems.httpServerReady = false;
startupItems.httpsServerReady = false;

var ee = new EventEmitter();

ee.on('ready', function(arg) {
  startupItems[arg] = true;
  if (startupItems.httpServerReady && startupItems.httpsServerReady) {
    var id = exec('id -u ' + process.env.SUDO_UID, function(error, stdout, stderr) {
      if(error || stderr) throw new Error(error || stderr);
      var uid = parseInt(stdout);
      process.setuid(uid);
      console.log('de-escalated privileges. now running as %d', uid);
      setInterval(function cb(){
        var rnd = Math.random();
        console.log('emitting update: %d', rnd);
        io.emit('update', rnd);
      }, 5000);
    });
  };
});

app.set('http_port', process.env.PORT || 80);
app.set('https_port', process.env.HTTPS_PORT || 443);

var httpServer = http.createServer(app);

var opts = {
  pfx: fs.readFileSync('httpssetuid.pfx')
};
var httpsServer = https.createServer(opts, app);

var io = new ioServer();

httpServer.listen(app.get('http_port'), function(){
  console.log('httpServer listening on port %d', app.get('http_port'));
  ee.emit('ready', 'httpServerReady');
});

httpsServer.listen(app.get('https_port'), function(){
  console.log('httpsServer listening on port %d', app.get('https_port'));
  ee.emit('ready', 'httpsServerReady');
});

io.attach(httpServer);
io.attach(httpsServer);

io.on('connection', function(socket){
  console.log('socket connected: %s', socket.id);
});

Client:

script(src='/socket.io/socket.io.js')
script.
  var socket = io();
  socket.on('update', function(update){
    document.getElementById('update').innerHTML = update;
  });

Here are the key points for the server:

  1. require socket.io but don't call it's listen method yet (assuming http and https are already required). Instead, just keep the reference. (var ioServer = require('socket.io'))
  2. create your http & https server
  3. create a new instance of ioServer
  4. bind your http and https servers (.listen)
  5. attach http&https server instances to the io instance. (.listen is an alias for .attach)
  6. setup io events.

And the client (jade syntax but you get the idea):

  1. include socket.io script tag
  2. call io and capture reference
  3. setup your event handlers

On the client you don't need to call io.connect(). Furthermore, I'm not sure about your options there. It looks like you have a typo (, ,) and I can't find any reference to secure: true in the 1.0 documentation.


Arguably, the node.js server object for HTTP and HTTPS ought to be given the capability to listen on an arbitrary number of ports and interfaces, with and without SSL, but this does not seem to currently be implemented. (I was able to get one server to listen on two ports by passing a second server that had no request listener as the "handle" argument to server.listen(handle, [callback]) interface, in addition to server.listen(port, [hostname], [backlog], [callback]), but it did not work with SSL/non-SSL servers mixed.)

The stunnel workaround already mentioned is of course a viable option, but if it is not desirable to install a separate piece of software (to avoid non-node.js dependencies), the same tunneling can be achieved natively in node.js instead (assuming HTTP on port 80 and HTTPS on port 443):

var fs = require('fs');
var net = require('net');
var tls = require('tls');
var sslOptions = {
    key: fs.readFileSync('server-key.pem'),
    cert: fs.readFileSync('server-cert.pem')
};
tls.createServer(sslOptions, function (cleartextStream) {
    var cleartextRequest = net.connect({
        port: 80,
        host: '127.0.0.1'
    }, function () {
        cleartextStream.pipe(cleartextRequest);
        cleartextRequest.pipe(cleartextStream);
    });
}).listen(443);

This will have the same effect as using stunnel. In other words, it will avoid the need for two separate socket.io server instances, while also making the node.js "https" module redundant.


I have done something similar and it required two socket.io instances. Something like this:

var express = require('express');
var oneServer = express.createServer();
var anotherServer = express.createServer();
var io = require('socket.io');

var oneIo = io.listen(oneServer);
var anotherIo = io.listen(anotherServer);

Of course that you will need to inject messages twice: for both socket.io instances.

A good option is delegate SSL handling to stunnel and forget about SSL in your code.


I am guessing Cross origin requests could be the reason why you are getting errors. Change in protocol is considered change in domain. So for page served via http server accessing https server (websocket server attached to it) may throw security errors. See an example here on how to enable CORS in express.

Also you should change * in the header to http://serverAddress , https://serverAddress. Allowing all sites is not a good idea, use it for testing.

The same is true if you are trying to AJAX between your http and https servers. Please post the errors, just to be sure about it.


I solved the problems using a different approach, I configured the server to support only unencrypted transport, and used stunnel for the https support.

For information on how to install stunnel you can check this post.

Then, used the following con configuration:

#/etc/stunnel/stunnel.conf
cert = /var/www/node/ssl/encryption.pem
[node]
accept = 443
connect = 80

Finally, I used the following to connect the clients:

var socket = that.socket = io.connect('//'+server);

This will auto detect the browser scheme and connect using http/https accordingly.


Need Your Help

Webdriver: How to find elements when class name contains space?

python css python-2.7 selenium-webdriver webdriver

Each of the "7-pack" search results here contains a number of reviews e.g. "5 reviews", No reviews" etc.

Add view by sliding in from left

android android-animation android-view

I want to add a view to a fragment via an animation. I want the view to appear by sliding in from the left of the screen and exit by sliding out the same way.