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
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:
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'))
create your http & https server
create a new instance of ioServer
bind your http and https servers (.listen)
attach http&https server instances to the io instance. (.listen is an alias for .attach)
setup io events.
And the client (jade syntax but you get the idea):
include socket.io script tag
call io and capture reference
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 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.
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.
Related
I am trying to make a simple server with socket.io and express and connect to it through a website.
when i followed a tutorial on socketio with localhost, everything worked fine, but when i put the server on a vserver, and tried to connect to it, i got this error:
Failed to load resource: net::ERR_SSL_PROTOCOL_ERROR
as well as:
GET https://54.53.0.254:47185/socket.io/?EIO=4&transport=polling&t=O09jjrs net::ERR_SSL_PROTOCOL_ERROR
here is my server code:
const express = require('express');
const app = express();
const server = app.listen(47185);
const socket = require('socket.io');
const io = socket(server)
console.log('server running on port 47185');
io.sockets.on('connection', newConnection);
function newConnection(socket) {
console.log('new connection: ' + socket.id);
socket.on('input', inputLog)
function inputLog(data) {
socket.broadcast.emit('input', data);
console.log(data);
}
}
and here is my client code (this is all that relates to socket.io, the rest is just for the website)
var options = {
rejectUnauthorized:false
}
var socket;
socket = io.connect('89.58.0.199:47185', options);
socket.on('input', foreignInput)
function foreignInput(data) {
terminal_animate('\n' + data)
}
i have tried many different fixes and googled everything i can think of, and i'm just not sure what the problem is.
can anyone help me out with this issue? thanks in advance.
In the documentation, according to the Client Initialization part, in node.js you should provide the protocol when connecting to the server.
// the following forms are similar
const socket = io("https://server-domain.com");
const socket = io("wss://server-domain.com");
const socket = io("server-domain.com"); // only in the browser when the page is served over https (will not work in Node.js)
The first two example shows the secure https/wss as protocol, for that you need to serve the required files from the server, example in the documentation.
With http/ws as protocol it should work, but the communication will not be secure.
The Server Initialization / With Express shows an example to call .listen on the return value of createServer from the http module, with the app given as a parameter.
const express = require("express");
const { createServer } = require("http");
const { Server } = require("socket.io");
const app = express();
const httpServer = createServer(app);
const io = new Server(httpServer, { /* options */ });
io.on("connection", (socket) => {
// ...
});
httpServer.listen(3000);
With a caution that says:
Using app.listen(3000) will not work here, as it creates a new HTTP server.
I'm trying to establish a WebSocket (with node.js/socket.io) on an
HTTPS protocol. But the client still keeps on polling and could not find the server although the server seems fine and listens.
This is what I have done so far. Could you see anything wrong with it?
My assumption is there is something not right with the certificates which I am using. I encrypted the server with plesk "lets encrypt" and took the certificates from this procedure is that right?
---- server side ----
var fs = require('fs');
var express = require('/opt/plesk/node/7/bin/node_modules/express');
var https = require('https');
var app = express();
var server = https.createServer({
key: fs.readFileSync('file.pem'),
cert: fs.readFileSync('file.crt')
},app);
var io = require('/opt/plesk/node/7/bin/node_modules/socket.io').listen(server);
server.listen(8080);
---- client side -----
var socket = io('/', {rejectUnauthorized: false, secure:true});
This is what the client gives me continuously:
https://foo.de:8080/socket.io/?EIO=3&transport=polling&t=MF9zjE6
Since you don't tell what error message you get it's a little hard to identify your problem. Perhaps you should try getting your socket to work and then afterwards try to implement https.
Maybe you should try and simplify it a bit like this.
Server:
var express = require('express')
var app = express()
var server = require('http').Server(app)
var io = require('socket.io')(server)
server.listen(8080);
Client:
var socket = io();
This piece of code should create your socket and your express app. Then you could try and change http to https and it should work aswell.
I tried to setup socket.io into my node server in order to make a messaging function for my app but having problem getting to function despite doing test app correct.
server.js:
/* long list of requires */
var express = require('express');
var http = require('http');
var https = require('https');
var config = require('./config');
var app = express();
var server = require('http').createServer(app);
var io = require('socket.io')(server);
io.on('connection', function(socket){
console.log('a user connected');
});
/* ssl setup */
// Create an HTTP service.
http.createServer(app).listen(8000); // opens the port
// Create an HTTPS service identical to the HTTP service.
https.createServer(options, app).listen(4433); // for the ssl
index.html
..bunch of code like bootstrap and angular.js
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.0.1/socket.io.js"></script>
<script>
var socket = io.connect('http://localhost:8000');
socket.on('connect', function(data) {
socket.emit('join', 'Hello World from client');
});
</script>
I tried to run the site on localhost:8000.
Despite all that, I'm getting error 404 in the console:
GET /socket.io/?EIO=3&transport=polling&t=LnM0Fd3 404
I'm not sure what went wrong with the set up process of socket.io
You're creating three servers, only one of which gets the Socket.io
// createServer No 1
var server = require('http').createServer(app);
var io = require('socket.io')(server);
// ...
// createServer No 2
http.createServer(app).listen(8000); // opens the port
// Create an HTTPS service identical to the HTTP service.
// createServer No 3
https.createServer(options, app).listen(4433); // for the ssl
You should probably only create one server and add the socket.io call to that one.
If you create only the Port 8000 server, you could add it like this:
// (exactly your code, except for the "var server = " in front.
var server = http.createServer(app).listen(8000); // opens the port
// the next line is new:
var io = require('socket.io')(server);
notice that you'll also have to move the io.on...... part below that line.
Another update:
The problem occurs when running on localhost as well. Since I figured out the problem comes from the proxy server, here's its code :
var serverBouncer = bouncy(function(req, res, bounce) {
var path = req.url;
var url = req.headers.host;
if (typeof url !== "string")
return;
var urlArray = url.split('.');
var bouncePort = port;
if (!isNaN(urlArray[0]))
bouncePort = parseInt(urlArray[0]);
else if (String(urlArray[0]).toLowerCase() === "www" && !isNaN(urlArray[1]))
bouncePort = parseInt(urlArray[1]);
bounce(bouncePort);
});
serverBouncer.listen(80);
Update:
I found where the problem came from!!! But I still need to find the solution... There seems to be issues with using newer versions of Socket.io (>= 1.0) with a proxy server (bouncy, in my case).
I recently updated Socket.IO from v0.9.16 to v1.4.5, as well as adding Express to the mix. However, now I cannot open multiple (number seems to vary) tabs in Chrome and Firefox without experiencing strange issues (Edge is the only one to work well). It either hangs, or partially loads html and other resources before it hangs.
After waiting, I often get the error :
Failed to load resource: the server responded with a status of 400 (Bad Request)
When I close one of the tab that's been hanging, it unblocks the other tabs that were also hanging.
The issues were not present before going through with the changes listed above.
I've been doing research for 2 full days and just now decided to post this, as I know it's very vague and I'm probably not providing enough information. As much as I'd like to, it would take a very long time to remember and list everything I tried during that time.
Using Windows 10 with Chrome v51.0.2704.103, Firefox v43.0.1. The server (CentOS) is using node v6.2.2 with mainly the following modules :
express#4.14.0
npm#3.9.5
socket.io#1.4.5
Here's some relevant server code :
var port = 8502;
var socketio = require('socket.io');
var express = require("express");
var http = require('http');
var app = express();
var server = http.createServer(app);
var io = socketio.listen(server);
server.listen(port);
app.get('/', function(req, res, next) {
//Returning index.html
});
io.on("connection", function(socket) {
//Some events...
});
Here's a bit of the client code :
var client = io.connect();
client.on('connect', function() {
//Some events
})
your binding before the server is listening, try something like this
var app = express();
server = app.listen(PORT, function () {
console.log('Example app listening on port ' + PORT + '!');
});
io.listen(server);
I managed to replace the bouncy module with nginx. See my other question for the solution.
nginx : redirect to port according to domain prefix (dynamically)
I am pretty new to nodejs and very new to socket.io and express. I have some code that is working, but am having trouble understanding exactly why it's working.
My question for the below code is how does the express app know to listen on port 80? There's a server.listen. But there is no app.listen in the code. Yet app.post() readily accepts posted data.
Please consider the following code
var https = require('https');
var url = require('url');
var fs = require('fs');
var bodyParser = require('body-parser');
var express = require('express');
var app = express();
//var io = require('socket.io');
var zlib = require('zlib');
app.use(bodyParser.urlencoded({extended: true }));
var options = {
key: fs.readFileSync('my.key'),
cert: fs.readFileSync('my.crt')
};
var serverPort = 80;
var server = https.createServer(options, app);
var io = require('socket.io')(server);
// log when the server is ready
server.listen(serverPort, function() {
console.log('Web socket server up and running at port %s', serverPort);
// this prints 80, as expected
});
app.post('/api', function (req, res) {
// working code is in here that receives the post variables and responds
}
With Express, the app object is just a request handler for some http server. If you call app.listen(), then the app object will create an http server for you. But, if you have created your own http server (which your code example does), then the app object just becomes a request listener on that server with this line of your code:
var server = https.createServer(options, app);
That creates the http server and registers app as a request listener (so it sees all incoming requests). This allows the Express app object to then process the routes that are registered with it to server routes like app.get(...) or app.post(...).
And, the port for Express is the port for the http server so it's the port that was used when the http server was created. What's important to understand here is that Express is not its own server. It's just a request listener for some http server.
In your code example, your http server is set up for port 80 so that's the port being used and Express gets registered as a request handler on all those incoming http requests on that port.
If you look at the source code for app.listen(), you see this:
app.listen = function listen() {
var server = http.createServer(this);
return server.listen.apply(server, arguments);
};
All, it does is create an http server with the app object as a listener and then call .listen() on that new server.
the app does not have to know on which port to listen. Basically, server is your HTTP server binding which listens on port 80 in your example. var server = https.createServer(options, app); then tells the server to listen on port 80 for HTTP requests and forward them to your app. The app then does the routing stuff that links the function you sepcified with app.post(...) to a specific request URL (/api in this case).
TL;DR: The app does not need to listen, because the server is the only communication interface to the outside.