Express + Socket.IO + RabbitMQ (node-amqp) - node.js

I'm having a hard time putting all these three together, probably because I'm not getting properly the concept of routing with Express.
I have a RabbitMQ queue with event updates. We can recognize these events by their id. So I want to get on a given page about an event, just the updates corresponding to its id.
Queue: 1316, 1539, 3486, 3479, 1316, 3890, 3479, ... -> Feed from the DB indefinitely.
www.example.com/event/1316 -> Gets from the queue just messages with id 1316
www.example.com/event/3479 -> Gets from the queue just messages with id 3479
My code works good when I load the first event, but when I load the second one in a different window, it gets messages from both events, and if I load a third one, guess right, it gets messages from the three ids.
app.js
var express = require('express')
, http = require('http');
var app = express();
var server = http.createServer(app);
var io = require('socket.io').listen(server, { log: false });
require('./io')(io);
var amqp = require('amqp');
var rabbitMQ = amqp.createConnection({ host: 'localhost' });
rabbitMQ.on('ready', function() {
console.log('Connected to RabbitMQ');
io.sockets.on('connection', function (socket) {
console.log('Socket connected: ' + socket.id);
rabbitMQ.queue('offer', { autoDelete: false, durable: false, exclusive: false }, function(q) {
q.bind('#'); // Catch all messages
q.subscribe(function (message) {
obj = JSON.parse(message.data.toString());
//socket.broadcast.to(obj.id).emit('message', obj);
io.sockets.in(obj.id).emit('message', obj);
});
});
});
});
var routes = require('./routes')
, event = require('./routes/event');
app.get('/', routes.index);
app.get('/event/:id', event.index);
server.listen(app.get('port'), function(){
console.log("Express server listening on port " + app.get('port'));
});
io.js
var socketio = function (io) {
if (!io) return socketio._io;
socketio._io = io;
}
module.exports = socketio;
routes/event.js
var io = require('../io')();
exports.index = function(req, res) {
io.sockets.on('connection', function (socket) {
socket.join(req.params.id);
});
res.render('event', { title: 'Event' });
};
Thanks!

You are receiving them all because you join but never leave the room. If you look at Socket IO Rooms from the wiki, at the bottom, it provides io.sockets.manager.roomClients[socket.id] as a way to get a list of rooms that the socket has joined (which I suspect will include all three if you've visited all three links).
You might want to try going through this list of rooms and leave any that aren't the current room, and see if that solves the problem.
Edit
Ok, so, there are two reasons/solutions to this. I just tested my theory, and it is valid - you receive messages for each room you've joined, and will continue to do so until you leave them. So here are the options:
1. leave all other rooms when they join a room
io.sockets.on('connection', function (socket) {
var room = req.params.id;
var roomKeys = Object.keys(io.sockets.manager.roomClients[socket.id]);
roomKeys.forEach(function(key) {
if (key === '' || key === '/' + room) return;
socket.leave(key.slice(1));
});
socket.join(room);
});
As said, I tested this. It works.
2. Don't send a message event, send a {room name} event
Instead of emitting a 'message' event, you could emit a '{room name}' event. Instead of your q.subscribe() callback containing io.sockets.in(obj.id).emit('message', obj);, you would just do socket.emit(obj.id, obj); and you'd have the javascript client only listen for that page's event types (based on the URL path).
I also tested this. It also works. It's also simpler (I think) because it only requires the .emit() in your q.subscribe() callback, which means you save the 'room management' stuff.

After trying and failing, I have understood what I was doing wrong cause using io.sockets.on('connection') inside of the router was duplicating the event. So at the end, the simplest way of thinking is the right one.
app.js
var room = '';
var roomHandler = function(req, res, next) {
if (req.path.match('event')) {
room = req.params.id;
}
next(); // Passing the request to the next handler in the stack.
}
io.sockets.on('connection', function (socket) {
socket.join(room);
});
rabbitMQ.on('ready', function() {
rabbitMQ.queue('offer', { autoDelete: false, durable: false, exclusive: false }, function(q) {
q.bind('#'); // Catch all messages
q.subscribe(function (message) {
obj = JSON.parse(message.data.toString());
io.sockets.in(obj.id).emit('message', obj);
});
});
});
app.get('/', routes.index);
app.get('/event/:id', roomHandler, event.index);

Related

Multiply io.on("connection")

After every page updating I have +1 socket connection..
module.exports = function(io, client) {
var GameController = {
gamePage: function(req, res) {
client.hget('games', 'game.' + req.params.id, function (err, result) {
if (err) return result(err);
var game = JSON.parse(result);
io.on('connection', function (socket) {
console.log('send');
console.log(socket.id);
io.emit('get_bets', game.players);
});
res.render('pages/game', {
title: 'Game - ' + req.params.id,
user: req.user,
game: game
});
});
};
return GameController;
});
route file:
module.exports = function(io, client) {
var express = require('express');
var router = express.Router();
var GameController = require('controllers/GameController')(io, client);
router.get('/:id', GameController.gamePage);
...
return router;
};
Client side on react:
var Game = React.createClass({
getInitialState: function() {
this.socket = io();
return {
bets: null
}
},
socketGetBets: function() {
var that = this;
this.socket.on('get_bets', function(data) {
console.log('get bets');
that.setState({ bets: data });
});
this.socket.on('rand', function(data) {
console.log(data);
});
},
...
But after debug I find what problem not in client side.
app.js file:
var socket_io = require('socket.io');
var io = socket_io();
app.io = io;
//route
var game = require('./routes/games')(io, client);
bin/www file:
var server = http.createServer(app);
var io = app.io;
io.attach( server );
After page updating, io.on("connection") event show me "send" message in console, after second page updating, I have "send" "send", third update - "send" "send" "send" etc. Than Memory leak warning appeared. Console log socked.id show the same value many time.
Every time you call on, whether it's io.on or socket.on, you are registering an event handler. This being the case, you probably don't want to be calling io.on('connection') inside of a route, as you will register a new connection handler every time that route is accessed. This is why you are seeing cumulative messages being logged in the console.
In fact, you probably don't want to mix express routing with socket functions at all, as they are different protocols and will work independent of each other.
// server side
// this should only be called once per connection.
io.on('connection', function (socket) {
// handle socket protocol stuff.. fetching server data, sending data
socket.on('fetch bets', function() {
// get game.players from server
// updating other sockets
io.emit('get_bets', game.players);
})
})
app.get('/route', function (req, res) {
// handle http protocol stuff.. fetching server data, sending data
// send data back to caller
res.json(data)
})
The same applies to socket.on in your client side. It looks like you're adding a new 'get_bets' event handler everytime you call socketGetBets.
Instead you probably want to register that event handler one single time, likely in componentDidMount or componentWillMount. Also, because a socket connection can be considered global for your application, you can create the connection above your app.
// client side
var socket = io()
var Game = React.createClass({
getInitialState: function() {
return {
bets: null
}
},
componentWillMount: function() {
var that = this
socket.on('get_bets', function(data) {
that.setState({ bets: data })
})
}
...

Node-amqp and socket.io strange behaviour

I m actually trying to implement the pub/sub pattern using node-amqp (https://github.com/postwait/node-amqp).
I have some problems to implement it.
What I need :
Publish message from a user
Broadcast it to others user
Sending the message to offline users that will consume it the next time they'll be connected
What I've actually :
(function () {
var amqp = require('amqp');
var connection = amqp.createConnection({ host: 'http://127.0.0.1:5672/' });
var app = require('express')();
var server = require('http').Server(app);
var io = require('socket.io')(server);
app.get('/', function (req, res) {
res.sendfile(__dirname + '/index.html');
});
server.listen(8888);
// Wait for connection to become established.
connection.on('ready', function () {
var sendMessage = function (queue, msg) {
connection.publish(queue, JSON.stringify(msg));
}
io.sockets.on('connection', function (socket) {
socket.on('message', function (msg) {
sendMessage('my-queue', msg);
});
connection.queue('my-queue', {autoDelete: false}, function (q) {
q.bind('#');
q.subscribe(function (message) {
socket.broadcast.emit('news',message);
});
});
});
});
})()
On the index.html page, I connect to the socket server
I have a button that send a message
I open two different browser on the index page, and my users are both connected
If I send a message to the server, it send it to the other users
If I send a second message to the server, it send the message to the user that sent the message.
It's switching, every pair message (because I have two users), the other users get the message, if it's an impair message, the current user sending the message receive the message.
What is this behaviour ?
Can you help me correcting my code to implement my needs a good way ?
NB : I use RabbitMQ with standard config on a windows 7 x64 computer
EDIT : I made a solution and every consumer can get the message with :
(function () {
var amqp = require('amqp');
var connection = amqp.createConnection({ host: 'http://127.0.0.1:5672/' });
var app = require('express')();
var server = require('http').Server(app);
var io = require('socket.io')(server);
app.get('/', function (req, res) {
res.sendfile(__dirname + '/index.html');
});
server.listen(8888);
// Wait for connection to become established.
connection.on('ready', function () {
connection.exchange('logs', {type: 'fanout', autoDelete: false}, function (exchange) {
var sendMessage = function (queue, msg) {
exchange.publish(queue, JSON.stringify(msg));
}
io.sockets.on('connection', function (socket) {
socket.on('message', function (msg) {
sendMessage('', msg);
});
connection.queue(socket.id, {exclusive: true}, function (q) {
q.bind('logs', '');
q.subscribe(function (message) {
socket.emit('news', message);
});
});
});
});
});
})()
My last problem is that I cant manage offline messages now... Any solutions ? (bounty end tomorrow :-/)
The problem is RabbitMQ will send each message to a single user on purpose. That user acknowledges that it received the message (amqp does this for you automatically) and then the work is done as far as RabbitMQ is concerned, so it deletes the message.
The reason your users take turns receiving a message is that RabbitMQ tries to spread to load of incoming messages evenly over users.
Your question has been answered before here. Check it out for a solution to your problem!
You need to create separate queues for each of the consumers (in your case - users), and route messages to all of them from exchange. That way, when you publish a message, it will be placed in all users' queues, from which each of them will be able to consume it independently.
You have this:
http://www.rabbitmq.com/tutorials/tutorial-two-python.html
And you need this:
http://www.rabbitmq.com/tutorials/tutorial-three-python.html

emitting to single client socket io 1.3.2

I've looked at several answers on here, but I think they are referring to older versions of socket.io as their solutions have not worked for me. I'm getting the data back in the browser with
io.emit('update', data)
but it's emitting to all clients so the same data is showing up in multiple windows when I go to the same URL. Do I have to store the client id somewhere upon connection or can I just get it back before emitting? Please be specific. I tried a few other solutions from SO, but I got a lot of ReferenceError 'id' is not defined or sockets instead of socket.
Server set up and connection:
var app = express();
var server = require('http').createServer(app)
var io = require('socket.io')(server)
app.get('/aPath', function (req, res, next) {
res.writeHead(200)
var data = {
"val1": req.query.val1,
"val2": req.query.val2,
"val3": req.query.val3,
"val4": req.query.val4,
"val5": req.query.val5,
"val6": req.query.val6,
}
/*console.log(io.sockets.id)*/
//io.to(io.sockets.id).emit('update', data)
//io.sockets.socket(id).emit('update', data)
io.emit('update', data)
res.end("OK")
})
io.on('connection', function (socket) {
console.log('websocket user connected')
});
Since a third-party client is sending the info via a restful interface, you will need to include reference data for the client in that request in the form of a header or query string.
I suggest using Redis to store the active socket users for quick reference. This will allow you to have multiple applications in deployment that use a singular redis instance to keep the data in sync. You can also do the same in app memory, but that just doesn't scale well.
first, you need to use middleware to authenticate user and cache the socket.id
var app = express();
var server = require('http').createServer(app);
var io = require('socket.io')(server);
var redis = require('redis');
io.use(function(socket, next){
// validate user
// cache user with socket.id
var userId = validatedUser;
socket.handshake.userId = userId;
redis.set(userId, socket.id, function (err, res) {
next()
});
});
next handle all socket communication
io.on('connection', function (socket) {
console.log('websocket user connected');
//next handle all socket communication
socket.on('endpoint', function (payload) {
//do stuff
socket.emit('endpoint.response', {/*data*/});
});
//Then remove socket.id from cache
socket.on('disconnect', function (payload) {
//remove user.id from cache
redis.del(socket.handshake.userId, function (err, res) {
console.log('user with %s disconnected', socket.id);
});
});
});
Handle third party event.
app.get('/aPath', function (req, res, next) {
// get user from third party
var userId = req.query.userId
var data = {
"val1": req.query.val1,
"val2": req.query.val2,
"val3": req.query.val3,
"val4": req.query.val4,
"val5": req.query.val5,
"val6": req.query.val6,
};
// get cached socketId from userId
redis.get(userId, function (err, socketId) {
// return ok to third party;
res.status(200).send("OK");
only emit if socketid still exists
if (err || !socketId) return;
// now emit to user
io.to(socketId).emit('update', data):
});
});

How to emit event in socket.io based on client?

I am working on realtime data visualization application using node.js, express and socket.io.
Requirement:
Have to emit the events based on the client request.
For example: If user enter the url as http://localhost:8080/pages socket.io should emit the topic pages to client and another user request for http://localhost:8080/locations socket should emit location to that particular user.
Code
var server = app.listen("8080");
var socket = require('socket.io');
var io = socket.listen(server);
var config = {};
io.on('connection', function (socket) {
config.socket = io.sockets.socket(socket.id);
socket.on('disconnect', function () {
console.log('socket.io is disconnected');
});
});
app.get('/*', function(req, res) {
var url = req.url;
var eventName = url.substring('/'.length);
//pages and locations
config.socket.volatile.emit(eventName, result);
});
Client Code:
//No problem in client code.Its working correctly.
Sample code as follows
socket.on('pages', function (result) {
console.log(result);
});
Problem:
It is emitting pages and locations to both the clients.
Any suggestion to overcome this problem.
I couldn't understand your approach on this, but because you said you're rendering different pages, It means you can serve different code, so what about doing it like this:
Server Side:
var server = app.listen("8080");
var socket = require('socket.io');
var io = socket.listen(server);
var config = {};
app.get('/pages', function(req, res) {
res.render('pages.html');
});
app.get('/locations', function(req, res) {
res.render('locations.html');
});
io.on('connection', function (socket) {
socket.on('pagesEvent', function(data){
socket.volatile.emit('pages', {your: 'data'});
});
socket.on('locationsEvent', function(data){
socket.volatile.emit('locations', {your: 'data'});
});
});
On Client side:
pages.html:
socket.on('connect', function(){
socket.emit('pagesEvent', {});
});
socket.on('pages', function(data){
// do stuff here
});
locations.html:
socket.on('connect', function(){
socket.emit('locationsEvent', {});
});
socket.on('locations', function(data){
// do stuff here
});
You are doing it wrong, WebSockets supposed to work same in both directions. Client emit event to Server, server emit back to Client/Subscribers.
The way you are doing things, seems like a way of implementing API, but for some reason you are trying to implement it with WebSockets, instead of XHR.

Working with Routes in express js and socket.io and maybe node in general

I am trying to write a multi channel application in socket.io. The channel you are in should be defined by the url you are on. If I do the joining part in the app.js with permanent values everything works. As soon as I change it so that the route for route.page does the joining I get the error, that sockets is not available in the context. What would be the correct way so that I can dynamically join the channel?
/app.js
var io = socketio.listen(app);
require('./io')(io);
io.sockets.on('connection', function (socket) {
socket.on('debug', function (message) {
socket.get('channel', function (err, name) {
socket.in(name).broadcast.emit('debug', message);
});
});
});
/io.js
var socketio = function (io) {
if (!io) return socketio._io;
socketio._io = io;
}
module.exports = socketio;
/routes/index.js
var io = require('../io')();
exports.page = function(req, res){
var channel = req.params.id;
res.render('page', { title: 'PAGE', channel: channel });
io.sockets.on('connection', function (socket) {
socket.join(channel);
socket.set('channel', channel );
});
};
The easiest way I've found to do multiple channels is off of different URLs.
For example I have the client do the following:
io.connect('/game/1')
io.connect('/system')
and on the server I have
io.of('/game/1').on('connect' function(socket) {...})
io.of('/system').on('connect' function(socket) {...})
It looks like I'm connecting twice here, but socket.io is smart enough to use a single websocket for this connection (at least it says so in the how-to-use).

Resources