Node Socketio Websocket on Subdomain - node.js

Since weeks I am trying to implement my websocket functionality on my production (ubuntu) Server (nginx). My websockets work locally, but I Keep getting Errors on production.
My socket.js Looks like this:
var fs = require('fs');
var options = {
type: "local",
key: fs.readFileSync("/etc/nginx/ssl/sub.domain.com/467605/server.key"),
cert: fs.readFileSync("/etc/nginx/ssl/sub.domain.com/467605/server.crt")
};
if (options.type == 'dev') {
var app = require('http').createServer(handler);
} else {
var app = require('http').createServer(options,handler);
}
var io = require('socket.io')(app);
var Redis = require('ioredis');
var redis = new Redis();
function handler(req, res) {
res.writeHead(200);
res.end('');
}
io.on('connection', function(socket) {});
// Redis UserSignedUp Channel, Channel if user signs up
var redisUserSignedUp = new Redis();
redisUserSignedUp.subscribe('signed-up-channel');
redisUserSignedUp.on('message', function(channel, message) {
message = JSON.parse(message);
io.emit(channel + ':' + message.event, message.data);
});
// run server on port 3333
app.listen(3333, function () {
console.log('Server running!');
});
My Event.js Looks like this:
const socket = io('sub.domain.com:3333', {
secure: true
});
// ... works locally
socket.on('signed-in-channel:App\\Events\\UserSignedIn', (data) => {
this.signedInUsers = data.username;
this.$toasted.info('Success: ' + data.username, {
theme: "primary",
duration: 10000
});
});
If I do this in my Event.js:
const socket = io('sub.domain.com:3333', { secure: true });
I get this error:
https://sub.domain.com:3000/socket.io/?EIO=3&transport=polling&t=MdZoLnn
net::ERR_SSL_PROTOCOL_ERROR
Where as if I watch for the Server ip like this:
const socket = io('123.123.123.123:3333', { secure: true });
I get this error:
https://123.123.123.123:3333/socket.io/?EIO=3&transport=polling&t=MdZpRnE
net::ERR_CONNECTION_TIMED_OUT
The site has an let's encrypt ssl certificate, further the webserver is nginx and the os is ubuntu. On my local window (wamp) it works starting it with node socket.js.

Related

Express and Websocket to run on the same port on the same file

I'm running two apps that sends real-time messages to each other using websocket and also generate a random link using express.js, now i hosted the server with both react apps to my vps host and want to make the websocket connection secure (wss://) but i realize i'll have to get the express server on the same port too, so the ssl/tsl works for both - so how do i do that?
Here is my full code, all on the same file:
const webSocketServerPort = 8000;
const webSocketServer = require('websocket').server;
const http = require('http');
const server = http.createServer(); server.listen(webSocketServerPort); console.log('Listening on port 8000');
const wsServer = new webSocketServer({ httpServer: server })
//GEERTOOOO
const express = require('express'); const cors = require('cors'); const fs = require('fs'); const app = express();
app.use(cors({ origin: '*' }));
app.get('/', (req, res) => { // Generate a random 6-character string const linkId = Math.random().toString(36).substr(2, 6);
// Save the link in the lex.json file fs.readFile('lex.json', (err, data) => { if (err) { console.error(err); res.status(500).send('Error generating link'); return; }
const links = JSON.parse(data);
links[linkId] = {
destination: 'http://localhost:4000/',
expires: Date.now() + 1000 * 60 * 5 // expires in 5 minutes
};
fs.writeFile('lex.json', JSON.stringify(links), (err) => {
if (err) {
console.error(err);
res.status(500).send('Error generating link');
return;
}
// Send the link back to the client
res.send(`http://localhost:3000/${linkId}`);
});
}); });
app.get('/:linkId', (req, res) => {
fs.readFile('lex.json', (err, data) => {
if (err) { console.error(err); res.status(500).send('Error retrieving link');
return;
}
const links = JSON.parse(data);
const link = links[req.params.linkId];
if (!link) {
res.status(404).send('Link not found');
return;
}
// Check if the link has expired
if (link.expires < Date.now()) {
res.status(410).send('Link has expired');
return;
}
// Redirect to the destination
res.redirect(link.destination);
}); });
app.listen(3000, () => { console.log('Server listening on port 3000'); });
//GEERTOOOO
const clients = {};
const getUniqueID = () => { const s4 = () => Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);
return s4() + s4() + '-' + s4(); }
wsServer.on('request', (request) => { var userID = getUniqueID();
const connection = request.accept(null, request.origin); clients[userID] = connection;
connection.on('message', (message) => {
if (message.type === 'utf8') {
for(var key in clients) {
if (clients[key] !== clients[userID]) {
clients[key].sendUTF(message.utf8Data);
console.log(`Sent Message to: ${clients[key]}`);
}
}
}
}) })
Note: the express server is on port 3000 and the websocket server runs on port 8000.
I,ve tried just changing the port to same thing but i get an error when trying to use the websocket server for messages.
THE PURPOSE OF ALL THIS IS JUST TO MAKE THE WEBSOCKET CONNECTION AND EXPRESS CONNECCTION SECURE SO MY APPS (with letsencrypt ssl) can connect to the servers
It is not possible to create two separate server instances, both listening on the same port. But, specifically for a webSocket, you can share one server instance between Express and the webSocket server code. This is possible because a webSocket connection always starts with an http request (thus it can be listened for using your Express http server. And, because these http requests that initiate a webSocket all contain identifying headers they can be separated out from the regular http requests for Express by looking at the headers. The webSocket server code already knows how to do that for you.
To do that, first capture the Express server instance:
const server = app.listen(3000, () => { console.log('Server listening on port 3000'); });
Then, use that server instance when you create your webSocket server.
const wsServer = new webSocketServer({ httpServer: server });
Then, remove this code because you don't want to create yet another http server instance for the webSocket server:
const server = http.createServer();
server.listen(webSocketServerPort);
console.log('Listening on port 8000');

Node Server not working with SSL

I have a node server code on named server.js
var env = require('dotenv').config({path: '../.env'});
var fs = require("fs");
var options = {
key: fs.readFileSync(env.APP_SSL_PATH + 'server.key').toString(),
cert: fs.readFileSync(env.APP_SSL_PATH + 'server.crt').toString()
};
var app = require('express')();
var http = require('https');
var server = http.createServer(options, app);
var io = require('socket.io')(server);
var redis = require('redis');
// Check if server is listening on its port
server.listen(env.APP_NODE_PORT, function() {
console.log('Listening on your port');
io.on('connection', function (socket) {
console.log("new client connected");
var redisClient = redis.createClient();
redisClient.subscribe('notification-channel', function(err, count) {
});
redisClient.on("message", function(channel, message) {
console.log( " message= "+ message + " channel = " + channel);
socket.emit(channel, message);
});
socket.on('disconnect', function() {
redisClient.quit();
});
});
});
Using http it works fine but then when I add the SSL certificate on https the code is not working already and it doesn't throw any error. I run the node server.js by
node server.js > stdout.txt 2> stderr.txt
there are no error log showing on stderr.txt file. Please help me Im new on this. On the console
Listening on your port
will logged only it does not continue and log
new client connected
This is my client side code:
<script>
var socket = io.connect('{{ env("APP_URL") }}:8890');
socket.on('notification-channel', function (data) {
var header_count_elem = $('#menubar #header-messages-count');
var count = header_count_elem.text();
if (data != 'no-sound') {
$.get('/notification/notif-alerts', function(response) {
header_count_elem.text((response.count == '0') ? '' : response.count);
if (data != 'no-sound') {
if (count != response.count) {
var sound = '<audio id="audioplayer" autoplay=true volume=1>' +
'<source src="{{ url('assets/themes/cpanel/alarms/notification_sound.mp3') }}" type="audio/mpeg">' +
'<source src="{{ url('assets/themes/cpanel/alarms/notification_sound.ogg')}}" type="audio/ogg">' +
'</audio>';
$('body').append(sound);
}
}
});
}
});

How to run socketio server properly on multiple cores using cluster module of NodeJS?

Socket IO server is running fine on single instance of NodeJs. But when I'm using cluster module of NodeJS to run the servers on multiple cores I'm getting the error, "Connection closed before receiving a handshake response". I've googled the reason and found out that,
Essence of the problem is, when you run multiple Node app threads (workers) on a server, or multiple servers, socket.io clients connections are routed by cluster in a random round-robin manner, and handshaken / authorized io client requests get handed to workers where they are not handshaken / authorized, where the mess begins. Source Link
I've tried a couple of things to make it work but no success so far. Here's the code
'use strict';
process.env.NODE_ENV = process.env.NODE_ENV || 'development';
var express = require('express');
var config = require('./config/environment');
var session = require('express-session');
var redisStore = require('connect-redis')(session);
var cluster = require('cluster');
var domain = require('domain');
var socketIo = require('./config/socketio');
var REDIS = require('redis')
var store = REDIS.createClient();
var pub = REDIS.createClient();
var sub = REDIS.createClient();
if(cluster.isMaster) {
var numWorkers = require('os').cpus().length;
for(var i = 0; i < numWorkers; i++) {
cluster.fork();
}
} else {
var d = domain.create ();
d.on ("error", function (error){
// start new server
});
// Setup server
var app = express();
var server = require('http');
d.run (function (){
server = server.createServer(app);
});
require('./config/express')(app);
require('./config/redis')();
require('./routes')(app);
server.listen(config.port, config.ip, function () {
console.log('Express server listening on %d, in %s mode', config.port, app.get('env'));
});
var redis = require('socket.io-redis');
var socketIO = require('socket.io')(server, {
serveClient: (config.env === 'production') ? false : true,
path: '/socket.io-client'
});
sub.subscribe('chat');
socketIO.adapter(redis( {host: 'localhost', port: 6379}));
socketIo.createSocketConnection('/dummy', socketIO, sub, pub, store);
exports = module.exports = app;
}
File: ./config/socketio
'use strict';
function addNamespaceForId (socketio, namespace, sub, pub, store){
socketio.of(namespace).on('connection', function(socket) {
onConnect(socketio, socket, namespace, sub, pub, store);
console.info('[%s] CONNECTED', socket.address);
sub.on('message', function(pattern, key){
store.hgetall(key, function(e, obj){
socket.send(obj.uid + ": " + obj.text)
})
})
socket.on('disconnect', function() {
console.info('[%s] DISCONNECTED', socket.address);
});
});
}
}
function onConnect(io, socket, namespace, sub, pub, store) {
socket.on('message', function(from, msg) {
store.incr("messageNextId", function(e, id){
store.hmset("messages:" + id, { uid: socket.sessionId, text: 'text;' }, function(e, r){
pub.publish("chat", "messages:" + id)
})
})
io.emit('broadcast', {
payload : from['message'],
source : from
});
io.of(namespace).emit('broadcast', {
payload : from['message'],
source : from
});
});
// When the client emits 'info', this listens and executes
socket.on('info', function(data) {
console.info('[%s] %s', socket.address, JSON.stringify(data, null, 2));
});
// Insert sockets below
require('../api/thing/thing.socket').register(socket);
}
module.exports = {
createSocketConnection : function (namespace, socketio, sub, pub, store){
addNamespaceForId(socketio, namespace, sub, pub, store);
}
};
I've also tried using adapter for redis as suggested in the documentation.
This setup works sometimes but not always. I'm unable to figure out the missing point.

Correct way to restart a socket.io server

I'm trying to restart a socket.io server. I start the server and get a welcome message for new connections, but when I close and restart the server I get no further welcome message.
Hopefully I'm missing something simple :\
var http = require('http').Server
var socketIO = require('socket.io')
var socketIOClient = require('socket.io-client')
var port = 3000
var url = 'ws://localhost:' + port
function newServer(serverName, cb)
{
var server = http().listen(port, function()
{
console.log(serverName, 'listening')
var io = socketIO(server)
var clientSocket = socketIOClient(url,
{ reconnection: false })
clientSocket.on('connect', function()
{
// never get 'two connect'
console.log(serverName, 'connect')
io.close()
})
clientSocket.on('disconnect', function()
{
console.log(serverName, 'disconnect')
cb()
})
})
}
function startServerOne(cb)
{
newServer('one', cb)
}
function startServerTwo(cb)
{
newServer('two', cb)
}
startServerOne(startServerTwo)
The parameter I was looking for was "forceNew". It's undocumented in socket.io-client documentation.
This seems to force the socket.io-client to create a new manager instead of using the cached one (which I assume is connected to a server that's no longer running).
The option is described on the socket.io blog and can be seen in the code here with a discussion of the issue here
Full working example:
var http = require('http').Server
var socketIO = require('socket.io')
var socketIOClient = require('socket.io-client')
var port = 3000
var url = 'ws://localhost:' + port
function newServer(serverName, cb)
{
var server = http().listen(port, function()
{
console.log(serverName, 'listening')
var io = socketIO(server)
var clientSocket = socketIOClient(url,
{
reconnection: false,
//////////////////////////////
// this forces a new connection!
forceNew: true
//////////////////////////////
})
clientSocket.on('connect', function()
{
// never get 'two connect'
console.log(serverName, 'connect')
io.close()
})
clientSocket.on('disconnect', function()
{
console.log(serverName, 'disconnect')
cb()
})
})
}
function startServerOne(cb)
{
newServer('one', cb)
}
function startServerTwo()
{
newServer('two', function()
{
console.log('high five everyone')
})
}
startServerOne(startServerTwo)
When you restart the server you kill all connections. Clients should actively reconnect.
You should take a look towards auto-reconnect configuration of client sockets

How to set RedisStore -- Node, Express, Socket.io, Heroku

I'm using Node & Express 4.0 deployed on Heroku, and I'm trying to implement Socket.io with Redis as aa session store. So I have this as my current code:
var app = express();
var server = require('http').createServer(app);
var io = require('socket.io').listen(server);
var RedisStore = io.RedisStore;
if (process.env.REDISTOGO_URL) {
// inside if statement
var rtg = require("url").parse(process.env.REDISTOGO_URL);
var redis = require("redis").createClient(rtg.port, rtg.hostname);
redis.auth(rtg.auth.split(":")[1]);
} else {
var redis = require("redis").createClient();
}
/** Initialize RedisStore for socket.io **/
io.set('store', new RedisStore({
redis : redis
}));
But I get the following error:
14:25:03 web.1 | io.set('store', new RedisStore({
14:25:03 web.1 | ^
14:25:03 web.1 | TypeError: undefined is not a function
I've also seen this way of defining a RedisStore:
var redis = require('socket.io/node_modules/redis');
var RedisStore = require('socket.io/lib/stores/redis');
However, my installed version of socket.io, installed using npm install --save socket.io, doesn't include stores in the lib directory:
EDIT
I saw this on the socket.io page in regards to their 1.0 release:
// 2. Implement the socket.io-redis adapter
var io = require('socket.io')(3000);
var redis = require('socket.io-redis');
io.adapter(redis({ host: 'localhost', port: 6379 }));
But there's no other documentation I could find regarding this new module, and since I'm new to this whole stack, I don't think I could figure it out on my own.
The trend among node.js modules is to remove functionality that isn't truly core to the module.
Which is why socket.io 1.0 no longer supports redis out of the box.
So step one is to track down the functionality you need.
http://socket.io/docs/server-api/
https://github.com/Automattic/socket.io-adapter
https://github.com/Automattic/socket.io-redis
Then you need to install the other module npm install socket.io-redis --save
And finally configure your app.
var app = express();
var server = require('http').createServer(app);
var io = require('socket.io').listen(server);
var redis = require('socket.io-redis');
io.adapter(redis(process.env.REDISTOGO_URL));
The nice part is the socket.io-redis adapter accepts redis urls and defaults to localhost:6379 so you (should) be able to simple pass in the REDISTOGO_URL
I had to parse libraries above to get this example, so I figured I would post a full blown example, but I must admit there are a couple of things off, this uses REDISCLOUD, it is on Heroku, it does work. I'll post this elsewhere and maybe put it in a doc too.
var redis = require('redis');
var ioredis = require('socket.io-redis'); //Adapter
var url = require('url');
var redisURL = url.parse(process.env.REDISCLOUD_URL );
var pub = redis.createClient(redisURL.port, redisURL.hostname, {return_buffers: true});
var sub = redis.createClient(redisURL.port, redisURL.hostname, {return_buffers: true});
pub.auth(redisURL.auth.split(":")[1]);
sub.auth(redisURL.auth.split(":")[1]);
var redisOptions = {
pubClient: pub,
subClient: sub,
host: redisURL.hostname,
port: redisURL.port
};
io.adapter(ioredis(redisOptions));
Following code works for me with Heroku Redis, hope this helps.
var app = express();
var server = require('http').Server(app);
var io = require('socket.io')(server);
var redis = require('redis');
var redisAdapter = require('socket.io-redis');
io.adapter(redisAdapter({
pubClient: redis.createClient(process.env.REDIS_URL, {return_buffers: true}),
subClient: redis.createClient(process.env.REDIS_URL, {return_buffers: true})
}));
For the ones interested,
this is my prototype chat server running on newest socket.io with express, multiple cores and redis as an intermediate.
Broadcasted messages are send to all room users no matter if they are connected to a different node and port instance.
Just run
node server.js
and on other machines
node client.js
or for testing node client.js 7001, 7002, 7003 ......
server.js
var options = {
//workers: 2, // total workers (default: cpu cores count).
first_port: 7000, // 8000, 8001 are worker's ports (default: 8000).
proxy_port: 5000, // default (5000).
session_hash: function (req, res) { return req.connection.remoteAddress; },
no_sockets: false // allow socket.io proxy (default: false).
};
require('sticky-socket-cluster')(options, start);
function start(port) {
// requirements
var express = require('express');
var http = require('http');
var socketio = require('socket.io');
var path = require('path');
var sticky = require('sticky-session');
var app = express();
var server = http.createServer(app);
var io = socketio.listen(server);
var redis = require('socket.io-redis');
io.adapter(redis({ host: 'localhost', port: 6379 }));
server.listen(port, function() {
console.log(' - listening on ' + port+ ' ' + __dirname);
});
// require our chatserver
var ChatServer = require('./chatserver');
// initialize a new chat server.
new ChatServer({io: io, port: port}).init();
}
chatserver.js
RoomUtil = (function(){
roomMessages = {};
return {
getMessages : function(room_id,limit,cb){
//TODO
cb(roomMessages[room_id] || []);
},
postMessage : function(message,room_id,cb){
if (!roomMessages[room_id]) roomMessages[room_id] = [];
roomMessages[room_id].push(message);
cb();
}
}
})();
var Server = function(options) {
var self = this;
self.io = options.io;
// users array
self.users = [];
// initialize function
self.init = function() {
console.log("init");
// Fired upon a connection
self.io.on('connection', function(socket) {
console.log("incoming connection");
// var ru = new RoomUser();
self.handleConnection(socket,options.port);
});
}
// socket handler for an incoming socket
self.handleConnection = function(socket,port) {
// wait for a login message
socket.emit("incoming connection",{});
socket.on("joinroom",function(data,joinroom_callback){
console.log("attempt to join room ",data.room_id," on port ",port);
if (!data.room_id){
console.log("cannon join room -> no room id given");
return socket.disconnect();
}
else{
var room_id = data.room_id;
socket.join(room_id,function(){
console.log(socket.rooms);
RoomUtil.getMessages(data.room_id,50,function(messages){
console.log("client succesfully joined room ",data.room_id);
joinroom_callback(null,{'messages':messages});
});
socket.on("login",function(data,login_callback){
if (!data.username){
login_callback("invalid userdata",null);
}
else{
login_callback(null,1);
socket.on("post_message",function(data,message_callback){
if (!data.message || data.message == ""){
console.log("empty message posted. ignore");
message_callback("invalid_message",null);
}
else{
console.log("received message on port ",port,data.message);
message_callback(null,1);
RoomUtil.postMessage(data.message,room_id,function(){
RoomUtil.getMessages(room_id,50,function(messages){
console.log("emit messages to room id ",room_id);
//socket.to(room_id).emit('update_messages', messages);
//socket.broadcast.to(room_id).emit('update_messages', messages);
//socket.broadcast.to(room_id).emit('update_messages', messages);
//self.io.to(room_id).emit('update_messages', messages);
self.io.in(room_id).emit('update_messages', messages);
});
})
}
});
}
});
});
}
});
}
}
module.exports = Server;
client.js
var servercon = 'http://localhost:'+(process.argv[2] || 5000);
console.log("going to connect to "+servercon)
var socket = require('socket.io-client')(servercon);
var readline = require('readline');
var rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
socket.on('connect', function(){
console.log("connected, going to login");
socket.emit("joinroom",{"room_id":123123}, function(error,data){
if (error){
console.log("cannot join room ",error);
}
else{
console.log("succesfully joined room -> going to login now");
console.log("received messages count",data.messages.length);
socket.emit("login",{username:"John Mckain"}, function(error, message){
if (error){
console.log("error logging in ",error);
}
else{
console.log("logged in succesfully -> post message now");
var readline = function(){
rl.question("type in a message -> ", function(message) {
socket.emit("post_message",{'message':message}, function(error, message){
if (error){
console.log("error posting message");
readline();
}
else{
console.log("succesfully posted message");
readline();
}
});
});
}
readline();
}
});
socket.on("update_messages",function(data){
console.log("received new messages count ",data.length,data);
});
}
});
});
socket.on('event', function(data){
console.log("event send",data);
});
socket.on('disconnect', function(e){
console.log("disconnected",e);
});
socket.on("welcome",function(data){
console.log("on welcome ",data)
})
socket.on("pong",function(e){
console.log("pong")
})

Resources