Scaling Socket.IO and broadcast to all belonging clients - node.js

I'm using
Express with "connect-redis" session store
And I tied it with Socket.IO through configuring "authorization"
So that I don't have to actually use
socket.get
Or
socket.set
To store and retrieve a client's belonging variables.
But I'm not sure that If I broadcast a message to specific group of people, connected in different server, get message successfully.
So that it is truly scaled!
But It seems to be not truly scaled but only use Redis as storing values individually.
Also I tried to use
RedisStore
which is given by Socket.IO
does not work.
It is saying
DEBUG: TypeError: Converting circular structure to JSON
at Redis.stringify [as pack] (native)
at Redis.publish (/var/www/node_modules/socket.io/lib/stores/redis.js:106:31)
at Manager.handleClient (/var/www/node_modules/socket.io/lib/manager.js:646:18)
at Manager.handleHTTPRequest (/var/www/node_modules/socket.io/lib/manager.js:595:8)
at Manager.handleRequest (/var/www/node_modules/socket.io/lib/manager.js:557:12)
at HTTPServer.<anonymous> (/var/www/node_modules/socket.io/lib/manager.js:112:10)
at HTTPServer.emit (events.js:70:17)
at HTTPParser.onIncoming (http.js:1507:12)
at HTTPParser.onHeadersComplete (http.js:102:31)
at Socket.ondata (http.js:1403:22)
How can I scale Socket.IO with broadcasting to all scaled clients available?
Edit: I followed the Express-Session-Socket.IO tutorial and I have found that
io.authorization actually makes problem but it is okay without io.authorization.
How is that?

Socket.IO has his own authorization. It authorize for each socket. To customize the socket.IO Auth, using my code, write inside the io.configure function:
io.set('authorization', function (handshakeData, callback) {
//your auth logic
});
With my example RedisStore you scale to all bradcasting clients available.

Related

Websocket Endpoints and Express Router

I was trying to implement web-sockets (npm ws) in my express application, but got stuck on how I should implement my websockets so that they work with express router.
Currently, my endpoints look like this...
router.post('/create-note', jwtAuthentication, NotesController.createNote);
router.get('/get-notes/:id',jwtAuthentication, NotesController.getUserNotes);
router.??('/socket-endpoint', NotesController.wssNote);
As you can see, I am unsure of what method to call on my router. I have tried using 'get' and 'post, but for some reason it only works after I try a second connection on postman. (I click connect, nothing happens, I then click disconnect and connect again and it works.)
I know that I can pass in the path when creating the WebSocketServer...
var wss = new WebSocketServer({server: server, path: "/hereIsWS"});
This does work, but if it is possible to use routers with web-sockets, I think it would make my whole project much cleaner and more organised.
I have come across people recommending 'express-ws', but was wondering if there was a better method to solve my problem, specifically a method that does not involve other packages.
Thanks in advance!
You do not use Express routers with webSockets. That's not the proper architecture for webSockets. Your webSocket server can share an http server with Express, but that's pretty much all the two have to do with one another.
webSockets connect on a particular path which you pass to the webSocketServer() constructor as it appears you already know. Once they are connected they stay connected and form a TCP pipe that you can send packets of data from client to server or from server to client. There is no Express routing used for that.
You can create your own message handling within a webSocket message by creating a message name as part of the webSocket payload if you want (this is something that the socket.io layer on top of webSockets does for you), but it has nothing to do with Express at that point. That's just in how you choose to handle the incoming webSocket packets.
if there was a better method to solve my problem
What is your specific problem? Perhaps if you stated the specific problem you want help with, we could provide further assistance.
To handle incoming webSocket messages, you can follow the example in the ws server doc:
wss.on('connection', function connection(ws) {
ws.on('message', function message(data) {
console.log('received: %s', data);
});
ws.send('something');
});
To further break up this to handle different types of incoming webSocket messages, you have to create your own message format that you can branch on or use socket.io instead on both client and server that does that for you.

Connect to a socket.io server from a Node.js server using the 'ws' package

I have a Node.js server which utilizes the popular ws package for using web sockets. I'd like to use this library to connect to an third party server which is running socket.io.
If I were to use socket.io on my server, the connection code would be something like this:
const socket = socketIo('https://api.example.com/1.0/scores')
I've attempted to connect to the same service using the ws package, and modifying the url:
const wsClient = new WebSocket('wss://api.example.com/1.0/scores');
but this results in the following:
Error: Unexpected server response: 200
Question:
What needs to be done to connect to a third party server running socket.io from a server running the ws package?
Additional Info:
I've noticed in my searches that some people have suggested appending
/socket.io/?EIO=3&transport=websocket to the end of the url. This
does not throw the same error as above (> Error: Unexpected server
response: 200) nor throw any visible error, but does not appear to
work (no data is received from the remote server).
Using new WebSocket('ws://api.example.com/1.0/scores?EIO=3&transport=websocket'); to open the connection (via ws) results in the following stack trace:
{ Error: Parse Error
at Socket.socketOnData
at emitOne
at Socket.emit
// ...
}
The socket.io api utilizes websockets but it also has a lot of other functions built on top of it in order to do things such as HTTP handshakes, session ids, and it can even handle fail overs to other protocols when needed.
You got half of the issue so far. Adding the line socket.io/?EIO=3&transport=websocket you're specifying parameters for the socket.io server to take.
EIO=3 specifies the version number for engine.io in which socket.io is using. In this case you are saying engine.io version = 3
transport=websocket specifies which transport protocol to use. As i said earlier, socket.io uses other protocols in cases such as fail overs. This portion forces socket.io to use websocket as the preferred protocol.
Now the next half is the WebSocket. WebSocket allows for Extensions which includes different kinds of compression that are commonly used when sending data. Which I believe is what is causing your Parse Error
Try this (found here):
const WebSocket = require('ws');
const ws = new WebSocket('ws://server/socket.io/?EIO=3&transport=websocket', {
perMessageDeflate: false
});
By setting perMessageDeflate: false you are specifying "Do not compress data". Since as i said this is a WebSocket Extension there are different variations as well. Try these instead if it doesn't work
x-webkit-deflate-frame
perframe-deflate
As a disclaimer this information is from the research that I have done. Im not a "socket.io specialist" so if there's anything incorrect please comment and i'll edit the post.
Because Socket.IO doesn't guarantee that there will be a WebSockets server hosted like you're seeming to expect, you should instead use their standard client package.
npm i socket.io-client
Then use the package in your code:
const ioClient = require('socket.io-client')('https://example.com/1.0/scores')
The full docs for socket.io-client are available on their GitHub repo.
Note: Honestly, though, it's just better at this point to use WebSockets instead if possible. WebSockets has become well-supported in browsers and is quite standard. Socket.IO is rarely necessary and could add some overhead.

socket.io ensure state on client reconnection

I am currently working on a project with socket.io, and i'm not sure to fully understand the mechanism of reconnection.
Since a disconnection could happen client side, i would like to know how to maintain the state of the socket on the server. I already know that socket.io-client will try to reconnect automatically, but i would like to know if it is possible to ensure the state of the socket on the server side.
I was thinking of a cookie based session, with express for example, but again i am not sure if i'm taking the good way about this. Is there another solution i should consider?
For the record, i successfully configured HAProxy with a cookie based sticky-sessions mechanism. Could it be possible to mix this mechanism with a cookie session on the socket.io server ?
Thanks
William
I think cookie based sessions are your best option. Look into the session.socket.io module. Looks like it was built specifically for this.
var SessionSockets = require('session.socket.io');
var sessionSockets = new SessionSockets(io, sessionStore, cookieParser);
sessionSockets.on('connection', function (err, socket, session) {
//your regular socket.io code goes here
//and you can still use your io object
session.foo = 'bar';
//at this point the value is not yet saved into the session
session.save();
//now you can read session.foo from your express routes or connect middlewares
});
Alternatively you could implement sessions yourself using express as you mentioned. I don't know of any easy way to integrate with HAProxy.

Routing with socket.io

I'm writing an express app.js with socket.io, and came across a problem.
I can't figure out how to use the routes.
I want the client to write for example localhost:3000/?id=3 and get something according to the id.
But in the socket.io connection event I dont know the url or the params (or is there a way?)
io.on('connection', function (socket) {/*should be something according to the id in the url*/});
untill now I just checked the id with
app.get('/', function (req, res) {
//req.query.id
});
Anyone knows a way around this?
Thank you!
It appears you may be a bit confused about how you use webSockets. If you want to make an http request such as localhost:3000/?id=3, then you don't use webSockets. You use the normal routing mechanisms in Express.
A webSocket connection is created and then persists. From then on, you define messages with optional data as arguments for those messages and you can send these messages either direction on the webSocket. webSocket messages are sent on an existing webSocket, not to a URL. You could create a message for sending URLs from client to server if you wanted. If that was the case, you could do this in the client:
socket.emit("sendURL", url);
And, then you would listen for the "sendURL" message on the server.

Node JS and Socket.IO. Client getting disconnected. During request

I've ran into a fairly difficult to debug error with my node web server.
Background
I'm creating a node server with socket.io to provide a restful service, connected to mongodb which use web sockets(socket.io) for server-client messages.
Issue
In my node app, I've used an npm package called node-scheduler, in which I do some processing at set times(these are very dynamic times but work fairly well to date).
So I'll set off a job, using node-scheduler and when it ends you can provide a function.
In this function I emit a web socket message, exactly how I emit messages in the rest of the application but my client side never receives the message.
Checking the logs the client disconnections then re connections after the function has completed.
I've debugged a little further, and I send two messages to the client in this function. Only one of them is processed by the client. May be a client issue not a server issue.
Any ideas for solutions or suggestions would greatly be appreciated.
Well generally socket.io is only meant to be used as a "channel". You should have the Client exist as a separate entity in memory or something, and update the socket if and when it reconnects. Otherwise you're just sending to the past (disconnected) sockets.
Using passport you can identify a client as a user.
app.get('/', function(req, res){
// req.user;
});
Using passport.socketio you can get the same user in your socket
io.on('connection', function(socket){
// socket.request.user;
socket.request.user.socket = socket;
// this will be updated with the latest socket in case of a future reconnection
// So now you can be sure that user object will always have the latest socket
nodeScheduler(function(){
carryOutJobs(function callback(){
socket.request.user.socket.emit('done');
// will always emit to the "latest" socket.
});
});
});

Resources