node.js cluster: have each worker render different page - javascript

So I'm using cluster to run some chat bots for some friends. And I use express to run a single page for each bot. However, cluster doesn't like that. My code (abridged) is something akin to the following:
var configs = {
bot1:"bot1",
bot2:"bot2"
};
if (cluster.isMaster) {
for (var bot in configs) {
cluster.fork( { file:configs[bot] } );
}
} else {
var file = process.env["file"];
var page = "/" + process.env["file"];
var express = require("express");
var web = express();
web.listen(3000);
web.get(page,function(req,res){
res.send( file );
});
}
And while this works good in theory, I'm only getting one bot with an output.
If I go to example.com:3000/bot2 I get bot2 as an output.
If I go to example.com:3000/bot1, I get Cannot GET /bot1.
It seems random which one will work, but never both of them.
Apologies if it's something stupid simple, or if it can't be done. I just find cluster more effective at rebooting itself on exits and generally more stable than child_process. (sometimes, when I use child_process, I'll end up with multiple instances of the same bot, which is tacky.)

You seem to have misunderstood how cluster works. It will not help for a situation like this and is primarily designed as a way to start multiple processes listening to the same port for HTTP connections.
What you now have is:
P1 => Master process which starts P2 and P3.
P2 => Listening on port 3000 handling /bot1
P3 => Listening on port 3000 handling /bot2
When a request comes in on port 3000, Node has no idea what the URL would be. It just knows that both P2 and P3 are set up to handle requests on that port, so it will randomly choose one to handle the request.
If you send a request to /bot1 and Node randomly assigns it to be handled by P3, then you will get the error you were seeing Cannot GET /bot1, because P3 has no idea what that path means. The same is true the other way around.
Perhaps what you really want is some number of bot processes and a single process that listens on port 3000 and then forwards the messages to the bot processes using worker.send() and such.

Related

How to block an incoming socket connection if the client already has one

So i noticed that you can run 'io()' in console on client side.
I'm worried that if someone were to loop it, it would crash the node.js server.
Does anybody know how to prevent multiple connection for the same user.
It is a fairly complicated process to do that properly.
But on that same note, people won't be able to crash your server with socket.io as easy as you think they would be able to.
Node.js can handle a ton of connections at once, same with socket.io. Obviously these are both dependent on what your server actually is; but even as Raspberry Pi can handle a significant amount of connections.
But, if you truly must implement this, I'd recommend checking out this issue and just making a counter-based dictionary of IP's and to disconnect sockets if their IP goes above a specific number.
Get the client's IP address in socket.io
Very crude, but it would do what you need.
you need some helper function on server side
get user ip with this package:
npm install request-ip
create array of users:
let users = [ ];
validate and add user to array on new join request
const requestIp = require('request-ip');
const addUser = () => {
const ipMiddleware = function(req, res) {
const clientIp = requestIp.getClientIp(req);
};
const existingUser = users.find(user.clientIp === clientIp)
if (existingUser) {
return false
}
const newUser = { clientIp };
users.push(newUser)
return true
}

How to create sockets that particular to user and disposable on Socket.Io

I write a Node.Js app and I use Socket.Io as the data transfer system, so requests should be particular to per user. How can I make this?
My actual code;
node:
io.on('connection', (socket) => {
socket.on('loginP', data => {
console.log(data);
})
})
js:
var socket = io('',{forceNew : false});
$("#loginbutton").click(function() {
var sessionInfo = {
name : $("#login input[name='username']").val(),
pass : $("#login input[name='pass']").val()
}
socket.emit("loginP", sessionInfo)
})
It returns one more data for per request and this is a problem for me. Can I make this on Socket.Io or should I use another module, and If I should, which module?
If I understand your question correctly (It's possible I don't), you want to have just one connection from each user's browser to your nodejs program.
On the nodejs side, your io.on('connection'...) event fires with each new incoming user connection, and gives you the socket for that specific connection. So, keep track of your sockets; you'll have one socket per user.
On the browser side, you should build your code to ensure it only calls
var socket = io(path, ...);
once for each path (your path is ''). TheforceNew option is for situations where you have multiple paths from one program.

How webscokets connect to the correct child process inside a cluster?

I am trying to figure out how websockets work inside a Nodejs/PM2 cluster.
I have just conducted an experiment by laucnhing 4 child processes using PM2, then from client I sent multiple webocket messages to all 4 socket servers (1 ws server running within each child process). One thing I didn't expect was that Node was able to figure out what process the socket belongs to, therefor every message sent by the client was console logged by the correct child process.
So is this behavior managed by Nodejs internally by the cluster module? It also seems like this is a new feature since Node 12? I might be wrong...
Code reference (ws module uses tsl): https://github.com/websockets/ws/blob/master/lib/websocket.js#L663
P.S: Before writing an answer, please check the comments I wrote to others...
You can try this:
webSocket=function(server) {
var io = require('socket.io')(server,{ transports: ['websocket', 'polling'] });
var redis = require('socket.io-redis');
var pub = require('redis').createClient(6379,'localhost');
var sub = require('redis').createClient(6379,'localhost');
io.adapter(redis({pubClient: pub, subClient: sub}));
io.sockets.on('connection', function (socket) {
console.log('Connection Establish!!!!!');
)}
};
app.JS
https.createServer(options, app).listen(httpsAddress, function () {
console.log("Express server listening on port " + config.get('PORT'));
webSocket(this)
})
So, basically you need a centralised message broker. The N processes would have N non shared memory/config/storage. The most popular choice is redis.
Socket.io does this out of the box with redis-adapter.
So,
1) Install redis
2) Install the redis adapter from here
After that you can follow #pritamjana's code.
From the adapter doc:
By running socket.io with the socket.io-redis adapter you can run
multiple socket.io instances in different processes or servers that
can all broadcast and emit events to and from each other.
const io = require('socket.io')(3000);
const redisAdapter = require('socket.io-redis');
io.adapter(redisAdapter({ host: 'localhost', port: 6379 }));
// So any of the following commands:
io.emit('hello', 'to all clients');
io.to('room42').emit('hello', "to all clients in 'room42' room");
io.on('connection', (socket) => {
socket.broadcast.emit('hello', 'to all clients except sender');
socket.to('room42').emit('hello', "to all clients in 'room42' room except sender");
});

Find free port not in use for apps - find some algorithm

I use the following API in my program to detrmine free port and provide it to application to run
portscanner.findAPortNotInUse(3000, 65000, '127.0.0.1', function(error, port) {
console.log('AVAILABLE PORT AT: ' + port)
})
https://github.com/baalexander/node-portscanner
This free port are given to application for use and working OK.
The problem is that if I provide a free port to application A and the application is doesn't occupied it yet(sometimes it takes some time...) and there is coming other application B and request a free port so it give to APP B the port of app A
Which cause to problem...
is there any elegant way to solve it?
my application doesn't have state so it cannot save to which app get which port...
There is solution that we can randomize the range but this is not robust ...
In my application Im getting the URL of the app that I should provide the free port to run.
update
I cannot use some broker or someting else that will controll this outside I need to find some algorithm (maybe with some smart random ) that can help me to do it internally i.e. my program is like singleton and I need some trick how to give port between 50000 to 65000 that will reduce the amount of collision of port that was provided to the apps
update 2
I've decided to try something like the following what do you think ?
using lodash https://lodash.com/docs/4.17.2#random to determine ports between with loops that provide 3(or more if that make sense) numbers for ranges like following
portscanner.findAPortNotInUse([50001, 60000, 600010], '127.0.0.1', function(err, port) {
if(err) {
console.log("error!!!-> " +err);
}else {
console.log('Port Not in Use ' + port);
}
//using that in a loop
var aa = _.random(50000, 65000);
Then If I got false in the port i.e. all 3 port are occupied ,run this process again for 3 other random number.comments suggestion are welcomed!!!
I try to find some way to avoid collision as much as possible...
I would simply accept the fact that things can go wrong in a distributed system and retry the operation (i.e., getting a free port) if it failed for whatever reason on the first attempt.
Luckily, there are lots of npm modules out there that do that already for you, e.g. retry.
Using this module you can retry an asynchronous operation until it succeeds, and configure waiting strategies, and how many times it should be retried maximally, and so on…
To provide a code example, it basically comes down to something such as:
const operation = retry.operation();
operation.attempt(currentAttempt => {
findAnUnusedPortAndUseIt(err => {
if (operation.retry(err)) {
return;
}
callback(err ? operation.mainError() : null);
});
});
The benefits of this solution are:
Works without locking, i.e. it is efficient and makes low usage of resources if everything is fine.
Works without a central broker or something like that.
Works for distributed systems of any size.
Uses a pattern that you can re-use in distributed systems for all kinds of problems.
Uses a battle-tested and solid npm module instead of handwriting all these things.
Does not require you to change your code in a major way, instead it is just adding a few lines.
Hope this helps :-)
If your applications can open ports with option like SO_REUSEADDR, but operation system keeps ports in the list in TIME_WAIT state, you can bind/open port you want to return with SO_REUSEADDR, instantly close it and give it back to application. So for TIME_WAIT period (depending on operation system it can be 30 seconds, and actual time should be decided/set up or found by experiment/administration) port list will show this port as occupied.
If your port finder does not give port numbers for ports in TIME_WAIT state, problem solved by relatively expensive open/close socket operation.
I'd advise you look for a way to retain state. Even temporary state, in memory, is better than nothing at all. This way you could at least avoid giving out ports you've already given out. Because those are very likely not free anymore. (This would be as simple as saving them and regenerating a random port if you notice you found a random port you've already given out). If you don't want collisions, build your module to have state so it can avoid them. If you don't want to do that, you'll have to accept there are going to be collisions sometimes when there don't need to be.
If the URLs you get are random, the best you can do is guess randomly. If you can derive some property in which the URLs uniquely and consistently differ, you could design something around that.
Code example:
function getUnusedPort(url) {
// range is [0, 65001). (inclusive zero, exclusive 65001)
const guessPort = () => Math.floor(Math.random() * 15001) + 50000;
let randomPort = guessPort();
while (checkPortInUse(randomPort)) {
randomPort = guessPort();
}
return randomPort;
}
Notes:
checkPortInUse will probably be asynchronous so you'll have to
accommodate for that.
You said 'between 50000 and 65000'. This is from 50000 up to and including 65000.
When managing multiple applications or multiple servers, where one must be right the first time (without retrying), you need a single source of truth. Applications on the same machine can talk to a database, a broker server or even a file, so long as the resource is "lockable". (Servers work in similar ways, though not with local files).
So your flow would be something like:
App A sends request to service to request lock.
When lock is confirmed, start port scanner
When port is used, release lock.
Again, this could be a "PortService" you write that hands out unused ports, or a simple lock in some shared resource so two things are getting the same port at the same time.
Hopefully you can find something suitable to work for your apps.
As you want to find an port that is not in use in your application, you could do is run following command:
netstat -tupln | awk '{print $4}' | cut -d ':' -f2
so in your application you will use this like:
const exec = require('child_process').exec;
exec('netstat -tupln | awk '{print $4}' | cut -d ':' -f2', (error, stdout, stderr) => {
if (error) {
console.error(`exec error: ${error}`);
return;
}
var listPorts = stdout.split(/\n/)
console.log(listPorts); // list of all ports already in use
var aa = _.random(50000, 65000); // generate random port
var isFree = (listPorts.indexOf(aa)===-1) ? true : false;
if(isFree){
//start your appliation
}else{
// restart the search, write this in a function and start search again
}
});
this should give you list of all ports that are in use,so use any port except ones in the listPorts.

Socket.io - io.to.emit emits to all connected users

I have a MEAN app setup with npm socket.io with expressjs and btford.socket-io on the client.
angular.module('myApp',['btford.socket-io'])
.factory('socket',function(socketFactory){
return socketFactory();
}
).controller('myAppCtrl',['$scope','socket',
function(a,b){
b.on('test',function(data){
console.log(data);
});
}
]);
Here's the node-express setup:
var app = express(),
server = app.listen(3000);
var socket = require('socket.io'),
io = socket.listen(server);
require('/config/routes/index.js')(app,io);
require('/config/routes/test.js')(app,io);
Routes : (config/routes/index.js)
module.exports = function(app,io){
app.get('/',function(req,res){
io.on('connection',function(socket){
socket.join(req.session._id);
});
res.render('index');
});
};
config/routes/test.js
module.exports = function(app,io){
app.get('/route1',function(req,res){
io.to(req.session._id).emit('test',{
data : 'Works'
});
res.render('route1');
});
};
A) Whenever the user goes to route1, the emit event is being fired and sent to all the users.
B) Is there a better approach to avoid using unique room for each user? This is not a chat application but rather implements push notifications
You have a couple major misunderstandings about how sockets and requests work. They are very separate operations and are not connected the way you appear to think they are. I will try to explain the problems with your code.
In this block of code:
module.exports = function(app,io){
app.get('/',function(req,res){
io.on('connection',function(socket){
socket.join(req.session._id);
});
res.render('index');
});
};
You are processing the / page request and EVERY time that request is hit, you add yet another event handler for io.on('connection', ...), thus you could have many of those.
Further, if the connection event happens BEFORE the user hits the / page, then you will miss it entirely and that socket will not be placed into the proper chat room.
Then, in this block of code:
module.exports = function(app,io){
app.get('/route1',function(req,res){
io.to(req.session._id).emit('test',{
data : 'Works'
});
res.render('route1');
});
};
io.to() takes a string that is the name of a chat room. So, this will send a message to every socket that is in the req.session._id chat room. For this to work, you'd have to make absolutely sure that req.session._id was completely unique to this user and that the desired user had already joined a chat room by this name. This could work, but it depends upon those specific things being correct.
You need to think of the connection from socket.io separately from a request. They are NOT tied together the way that you think they are. Your connection listener is for any connection not just a connection related to a given request... what you are trying to do simply will not work that way.
Imagine that your socket.io portions of your project are completely separate from the http requests in the web/express portions of your application. This is how you should think of passing messages.
Also worth consideration is that if you are using cluster or similar scaling methods, your default socket.io setup in one instance doesn't communicate with other instances.

Categories