13

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

With the below config options it can access my application through https, but when trying to access it through 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, e.g. it can access through http and not through https.

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

Edit: In case it helps about the problem, the error I am receiving is this:

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 
Vassilis Barzokas
  • 3,105
  • 2
  • 26
  • 41

5 Answers5

19

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.

a_arias
  • 3,036
  • 2
  • 21
  • 20
6

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.

Otto G
  • 670
  • 7
  • 14
3

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.

MrTorture
  • 810
  • 6
  • 8
  • Will a client from `http` be able to communicate with someone connected to `https`? Regarding `stunnel` i have tried it and also `http-proxy`, but i could not made them work properly. Can you provide an example for any of these? – Vassilis Barzokas Jun 25 '13 at 06:46
  • Sorry for the delay. Yes, stunnel takes care of SSL and transparently decrypts your clients connected to https port (443) so that they appear as being connected to express http port (80). – MrTorture Jul 02 '13 at 17:28
  • Here is a gist with a working simple configuration for stunnel that listens on 443 and forwards all incoming traffic to port 80 of the same host: https://gist.github.com/mbenedettini/5911415 – MrTorture Jul 02 '13 at 17:51
1

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.

Community
  • 1
  • 1
Kuf
  • 17,318
  • 6
  • 67
  • 91
  • i used this method but its not working in my system, for http all things working perfactly, but i dont how to configure for https , i used node, socket io,redis and rails, so i always need to connect for port 9876 `http.listen(9876, function(){ console.log('listening on *:9876'); });`, i added your code in stunnel.conf. but not working. Can you help me ? – Vishal Nov 07 '17 at 11:26
0

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.

user568109
  • 47,225
  • 17
  • 99
  • 123
  • Thanks, i added the errors i receive. I tried enabling CORS and then i understood that the way i included socket.io at the `head` of the client side should change, so as the request is always to the htpps and not relative to the url. It didnt quite work though, i got some other errors like `NETWORK_ERR: XMLHttpRequest Exception 101`, it must be because i am using Websockets and if not available, XHR polling. – Vassilis Barzokas Jun 27 '13 at 08:10
  • Did this error come after the errors you added in question, or those errors you added were replaced by this error. – user568109 Jun 27 '13 at 10:09
  • The error now happens is due to same origin policy. Can you tell the line that causes this error. Maybe CORS request is failing is for some url. – user568109 Jul 01 '13 at 17:10