I recently started refactoring some code to separate front and back end which I previously made into one combined service.
I've now got a problem where I need the web server to emit to the data server (after validation). I previously managed this by calling the emit from inside the express route after adding this code
var io = require('socket.io')(server);
app.io = io;
app.use(function(req, res, next) {
req.io = socket;
next();
});
// Then inside of the route
app.post('/make', function(request, response) {
if (request.session.loggedin = true) {
var sio = request.io;
var to = request.body.to;
var amt = request.body.amt;
var from = request.body.from;
if (to.length > 0 && amt != 0) {
if (amt <= request.session.balance) {
sio.emit('send-transactions', {"to": to, "from": from, "amt": amt});
Now that these are separate, the web server is just connecting as client (using socket.io-client) so I can't seem to get it to work.
I'm using,
var socket = require('socket.io-client')('wss://ip:port', {secure:true,rejectUnauthorized:false, reconnect:true});
I tried changing all references of "io" to "socket" as my vars are different but I can't figure out if it's the client library that isn't capable of doing so .
Tl;dr - I am trying to emit from web server (technically client) to back end (socket.io server) to fire the event which would then broadcast to all clients.
Just wanted to close this one off so here was the solution:
As my routes were previously intended for server to emit to all clients, using the same route as a client was not being fired on the server side. I just needed to change my route to the server side one and the full sequence is now complete and emitting to that route then gets emitted to all clients
Related
I'm using Express (v4.17.3), Socket.io, and Node.Js's http module. I'm adding a middleware for express to capture all incoming requests but that's failing.
I'll first show the code I'm using and the output then explain my understanding/expectation of the output (I'm new to Node and all the mentioned libraries so perhaps I'm missing something)
First of all, below is the code I'm referring to. Using Express's middleware I'm trying to capture all the incoming requests and log them, and doing the same for the http on("request"). However, requests going to socket.io aren't captured by the middleware.
// Express
const express = require("express");
const app = express()
// Socket
const server = require('http').createServer(app);
const {Server} = require("socket.io");
const io = new Server(server)
// Want to listen to all incoming requests using the middleware (this doesn't work)
app.use((req,res,next)=>{
console.log(`Express request = ${req.url}`)
next()
})
// Listening to all incoming requests (this works)
server.on("request", (req, res)=>{
console.log(`Http request = ${req.url}`)
})
server.listen(8080, () => {
console.log(`Listening on port 8080`)
})
output when I GET /
Express request = /
Http request = /
Http request = /socket.io/socket.io.js
Http request = /socket.io/?EIO=4&transport=polling&t=O0va...
Http request = /socket.io/?EIO=4&transport=polling&t=O0va24A&sid=c...
Http request = /socket.io/?EIO=4&transport=polling&t=O0va24F&sid=c...
Http request = /socket.io/?EIO=4&transport=polling&t=O0va27x&sid=c...
My expected output is to have equal logs for the middleware app.use() and on("request") ("Express request = " & "Http request = ")
My understanding:
1- When I add a middleware for express as in the code below, any incoming requests should be captured here first before going anywhere else. (correct?)
app.use((req,res,next)=>{...})
2- When I'm passing the express app as an argument to http'screateServer, that the express app will be treated as a listener and any request events will be passed to it. (correct?)
const server = require('http').createServer(app);
So if my understanding is correct, why aren't all the requests captured by the request event passed to the middleware as well?
This is normal. Socket.io puts itself in front of express (or any other listener for incoming requests on the http server) so that it takes the request before Express sees it. Thus, Express (or it's middleware) never see any socket.io connection requests.
Socket.io has its own middleware layer that you can use to participate in the initialization of socket.io requests.
Or, you can register for incoming socket.io connections (to be called after they are already connected) with the io.on('connection', ...) event handler.
When I add a middleware for express as in the code below, any incoming requests should be captured here first before going anywhere else. (correct?)
That is true except for code that registers directly request handlers right on the http server and inserts itself before Express in the listener chain, thus preventing Express from seeing any requests that are destined for socket.io.
When I'm passing the express app as an argument to http'screateServer, that the express app will be treated as a listener and any request events will be passed to it. (correct?)
That is true. But socket.io jumps in front of Express and takes/hides any requests it wants so that Express never sees them.
If you're curious, here's the socket.io code that jumps in line to the front of all listeners for the http server thus bypassing the express listener:
attachServe(srv) {
debug("attaching client serving req handler");
const evs = srv.listeners("request").slice(0);
srv.removeAllListeners("request");
srv.on("request", (req, res) => {
if (this.clientPathRegex.test(req.url)) {
this.serve(req, res);
}
else {
for (let i = 0; i < evs.length; i++) {
evs[i].call(srv, req, res);
}
}
});
}
It grabs all the existing listeners into an array. Then, it removes them all. Then, it registers for the request event itself and, if it is a socket.io request, then it does not call the prior listeners. If it is not a socket.io prefix, then it manually calls the prior listeners.
I believe you need to log the messages sent on socket.
io.on('connection', (socket) => { socket.on('chat message', (msg) => { console.log('message: ' + msg); }); });
I'm building a web app where the view needs to be updated on a data change. For this purpose I'm using socket.io. This is the setup in the server.js file:
const io = require('socket.io')(http);
/* WEB SOCKET INITIALISATION
----------------------------------------- */
io.on('connection', function(socket) {
io.emit('new connection', 'new connection');
socket.on('disconnect', function () {
io.emit('offline', 'offline')
});
});
When looking into my console, it log's 'new connection' as soon as I'm on the page. Now I want to use the socket in one of the routes i've made. To make this possible I bind io to the place where i require the route, like this:
const dashboardRouter = require('./routes/dashboard')(io);
This is how a part of the route looks like:
/* MAKE SOCKET.IO ACCESSABLE
--------------------------------------------------------------- */
module.exports = function(io) {
/* INDEX ROUTE
-------------------------------------------------------------- */
router.get('/', account.checkSession, playwalls.statistics, playwalls.getYours, function(req, res, next) {
io.emit('update', 'testen');
res.locals.username = req.session.username;
res.render('dashboard/index');
});
/* EXPORT ROUTER
--------------------------------------------------------------- */
return router;
}
The problem is that when I emit update, it doesn't log in the console. When I do console.log(io) inside the route, it looks like socket.io is available though. I get no errors inside the terminal console.
This is what the client side JavaScript file looks like:
(function() {
const socket = io();
socket.on('update', function(data) {
console.log('Works');
});
socket.on('new connection', function(data) {
console.log(data);
});
}());
What am I doing wrong here? How can I make sure that the socket emits the message? Hope someone can help me with this!
Thanks in advance.
This route:
router.get('/', account.checkSession, playwalls.statistics, playwalls.getYours, function(req, res, next) {
io.emit('update', 'testen');
res.locals.username = req.session.username;
res.render('dashboard/index');
});
Will broadcast to all the other connected pages, but will not show anything for the particular user that triggered this route. That's because the user that triggered this route is in the middle of requesting and loading a page and they are not yet connected to socket.io.
The sequence of operations for the user who made this requests is as follows:
Browser closes down previous web page which includes disconnecting socket.io and shutting down Javascript.
Browser requests / URL.
Server calls your route handler for that URL.
Your route handler calls io.emit() which sends to all currently connected clients, but the client making this request is not currently connected.
Route handler renders the page and sends it back to the browser.
Browser receives and renders the page
Browser runs Javascript in the page, resulting in socket.io connection being made.
As you can see from this sequence, the browser making the request is not connected with socket.io when you call io.emit(). Basically, you can't emit to the page that is being rendered in the same route that renders it.
have a server that uses socket.io. When a user connects it will assign them the user id thats made on the server then increment it by 1 so the next user with have a different id.
I want to use cookies for this, to check if they have previously logged in, if so, use that id, if not, use the one on the server.
the way to create a cookie is by using
res.cookie('cookie', 'monster')
but im not where i would put it, i tried putting it in the connect function but res wouldnt exist. and if i put it outside the function, how would i call it? Here is my code. This is the start of my server:
//Require npm modules
var express = require('express');
var http = require('http');
var events = require('events');
var io = require('socket.io');
var ejs = require('ejs');
var app = express();
//Set the default user Id to 1 and the default username to Guest
exports.Server = Server = function()
{
this.userId = 1;
this.userName = "Guest";
};
app.set('view engine', 'ejs');
app.get('/game/:id', function (req, res)
{
res.render('game', {game: req.params.id});
});
Server.prototype.initialise = function(port)
{
//Create the server using the express module
this.server = http.createServer(app);
//Declare the 'public' folder and its contents public
app.use(express.static('public'));
//Listen to any incoming connections on the declared port and start using websockets
this.server.listen(port);
this.startSockets();
this.em = new events();
consoleLog('SERVER', 'Running on port: ' + port);
};
Server.prototype.startSockets = function()
{
//When a user connects to the server on the 'game' socket
this.socket = io.listen(this.server);
this.socket.of('game').on('connection', function(user)
{
res.cookie('cookie', 'monster')
//Set their usedId and username
user.userId = this.userId;
user.userName = this.userName + " " + this.userId;
//Increment the user id by 1 so each user with get a unique id
this.userId++;
//Send a response back to the client with the assigned username and user id and initialise them
user.emit('connected', user.userId, user.userName);
this.em.emit('initialiseUser', user.userId, user.userName);
So where i have the res.cookie is where i want to be able to read and write cookies, any help is appriciated
I think what you are looking for is the middleware pattern employed by express. You can define as many of these middleware calls as you wish, and they are the perfect scope for calling any other functions which may need the res instance (or the req instance for that matter).
app.use(function (req, res, next) {
// call function, passing in res here
next();
})
Reference: https://expressjs.com/en/guide/using-middleware.html
EDIT:
This answer is not correct for your situation. In a node/express server not using socket connections, then yes, you could easily use the above pattern anywhere you need the request and response objects in scope.
However, once you setup the socket io server, the game changes. During the socket communications, there are no express request and response objects in scope anymore, everything is handled directly between your socket handling code and the client. So the answer is you need to handle the situation in a socket io way, not in an express way.
Please see: Adding a cookie value on Socket.IO
My node.js server uses cluster module in order to work on multiple processes.
If the server receives requests from clients with Socket.IO, it conveys the data to another server with redis publish. And it receive refined data with redis subscribe, and then it just toss this data to clients.
I use one node process to receive data with redis sub, and other processes to send data to clients with socket.io.
And the client connect socket.io when page loaded.
Here, this is my problem.
The connect event occured repeatedly not even the page loaded.
When the client connect, I get the socket.id from that socket, and I use it later when I want to send data to that client socket. But this connect occur repeatedly, I think socket that client use changed. So, the first socket.id that I remembered will be useless. I can't send data from that socket.id. I stored auth information in the socket object, so the changed client socket is no help.
index.pug
$(document).ready(function(){
var socket = io.connect();
(...)
app.js
var cluster = require('cluster');
var socketio = require('socket.io');
var NRP = require('node-redis-pubsub');
var nrpForChat = new NRP(config.chatRedisConfig);
var nrpForCluster = new NRP(config.clusterRedisConfig);
var startExpressServer = function(){
var app = express();
var server = require('http').createServer(app);
var io = socketio.listen(server);
var redis = require('socket.io-redis');
io.adapter(redis({ host: 'localhost', port: 6380 }));
io.sockets.on('connection', function(socket){
socketController.onConnect(io, socket, nrpForChat);
});
server.listen(config.port, function(){
console.log('Server app listening on port '+config.port);
});
nrpForCluster.on('to:others:proc', function(data){
var socket = io.sockets.connected[data.target.sockid];
if (socket) {
if (data.event == '_net_auth') {
if (data.data.res){
socket.enterId = data.data.data.enterId;
socket.memberKey = data.data.data.memberKey;
socket.sid = data.data.data.sid;
socket.emit(data.event, data.data);
}else{
console.log('auth failed.');
}
}
} else {
socket.emit(data.event, data.data);
}
});
module.exports = app;
}
var numCpus = require('os').cpus().length;
if (cluster.isMaster) {
for (var i = 0; i < numCpus; i++) {
cluster.fork();
}
}
else {
if (cluster.worker.id == numCpus) {
nrpForChat.on('chat:to:relay', function(data){
nrpForCluster.emit('to:others:proc', data);
});
if (numCpus == 1) {
startExpressServer();
}
}
else {
startExpressServer();
}
}
By default, socket.io connects with several consecutive http requests. It essentially starts in HTTP polling mode and then after some initial data exchange, it switches to a webSocket transport.
Because of this, a cluster that does not have any sort of sticky load balancing will not work. Each of the initial consecutive http requests that are all supposed to go to the same server process will probably be sent to different server processes in the cluster and the initial connection will not work.
There are two solutions that I know of:
Implement some sort of sticky load balancing (in the clustering module) so that each client repeatedly goes to the same server process and thus all the consecutive http requests at the beginning of a connection will go to the same server process.
Switch your client configurations to immediately switch to the webSocket transport and never use the HTTP polling. The connection will still start with an http request (since that's how all webSocket connections start), but that exact same connection will be upgraded to webSocket so there will only ever be one connection.
FYI, you will also need to make sure that the reconnect logic in socket.io is properly reconnecting to the original server process that is was connected to.
socket.io has node.js clustering support in combination with redis. While the socket.io documentation site has been down for multiple days now, you can find some info here and Scaling Socket.IO to multiple Node.js processes using cluster and here's a previously cached version of the socket.io doc for clustering.
I've got the following setup (important bits only for brevity):
app.js
...
const app = express();
const server = require('http').Server(app);
const io = require('socket.io')(server);
server.listen(port, function() {
console.log(`Server is listening on port: ${port}`);
});
io.on('connection', function (socket) {
console.log('connection');
});
const routes = require('./routes/index')(io, passport);
app.use('/', routes);
index.js (server)
router.get('/game/:id', isAuthenticated, (req, res) => {
if (req.id)
{
var game = Game.findOne({_id: req.id}, (err, obj) => {
io.on('getGameInfo', (socket) => {
io.emit('gameInfo', obj);
});
res.render('game', obj);
});
}
else
{
// Id not valid, do something
}
});
client:
const socket = io('http://localhost:3000');
socket.on('gameInfo', function(data) {
console.log(data);
}.bind(this));
socket.on('connect', () => {
socket.emit('getGameInfo');
});
So basically I want to emit a getGameInfo call once I know the client has connected, and the getGameInfo listener has been set up in the game route. But when I emit the getGameInfo from the client, the server callback isn't being hit. I'm not sure if I'm missing something obvious, or if this is a closure issue, or if I'm just having one of those days, or if I'm going about this entirely the wrong way.
There are multiple problems here. I'll start by showing the correct way to listen for an incoming socket.io message on the server:
io.on('connection', function (socket) {
// here's where you have a new socket and you can listen for messages
// on that socket
console.log('connection');
socket.on('gameInfo', (data) => {
socket.emit('gameInfo', obj);
});
});
Some of the issues:
On the server, you listen for messages via the socket object, not via the io object. So, you would typically add these event listeners in the io.on('connection', ...) handler because that's where you first see newly connected sockets.
You pretty much never want to add event listeners inside an Express route handler because that is called many times. In addition, at the moment the route handler is called, the browser has not yet received the page and will not yet be connected so even if this was an OK place to do stuff, the page is not yet connected anyway.
When you want to send a message back to just one connection, you send it with socket.emit(), not io.emit(). io.emit() broadcasts to all connected clients which I don't think is what you want.
I'd suggest you not overload the same message name for client and server to mean two different things as this can lead to confusion when reading code or if you ever share some code between client and server. You client is really sending a "getGameInfo" message and then your server responds with a "gameInfo" message that contains the gameInfo.
If, in a route handler, you want to .emit() to the socket from that page which it looks like you are trying to do, then you have to do some work to create a link between the session of the current page and the socket for that page. There are a number of ways to do that. If you're using any session middleware, you can record the socket in the session at the point the socket connects. Then, from your express routes, you can get that socket from the session object at any time.