I am building a web app that uses socket.io to trigger remote procedure calls that passes sessions specific data back go the client. As my app is getting bigger and getting more users, I wanted to check to see if my design is reasonable.
The server that receives websocket communications and triggers RPCs looks something like this:
s.on('run', function(input) {
client.invoke(input.method, input.params, s.id, function(error, res, more) {
s.emit('output', {
method: input.method,
error: error,
response: res,
more: more,
id: s.id
});
});
});
However, this means that the client has to first emit the method invocation, and then listen to all method returns and pluck out its correct return value:
socket.on('output', function(res) {
if (res.id === socket.sessionid) {
if (!res.error) {
if(res.method === 'myMethod') {
var myResponse = res.response;
// Do more stuff with my response
});
}
}
});
It is starting to seem like a messy design as I add more and more functions... is there a better way to do this?
The traditional AJAX way of attaching a callback to each function would be a lot nicer, but I want to take advantage of the benefits of websockets (e.g. less overhead for rapid communications).
Related
I'm using socket.io and used rate-limiter-flexible and limiter to limit request rate, but I noticed the route /socket.io isn't receiving 429 from either, in fact, I can't log any requests coming from this route by using app.use('/socket.io')..
I think socket.io is doing some treatment on this route under the hood, is that correct? If so, how can I make sure requests to /socket.io also receive 429 after the limit is reached?
Rate limiter on Connect:
this.io.on(this.SocketConstants.CONNECTION, async (client) => {
this.client = client;
try {
await this.rateLimiter.consume(client.handshake.address);
} catch (rejRes) {
// On flood
client.error('Too many requests');
client.disconnect(true);
}
Rate limiter on http server:
class RateLimiting {
constructor() {
this.limiter = new RateLimiter(2, 'minute', true);
this.index = this.index.bind(this);
}
index(req,res,next){
try {
this.limiter.removeTokens(1, function(err, remainingRequests) {
if (remainingRequests < 1) {
res.writeHead(429, {'Content-Type': 'text/plain;charset=UTF-8'});
res.end('429 Too Many Requests - your IP is being rate limited');
// res.status(429).json({ message: 'Too many requests' });
} else {
next();
}
});
}
catch(err){
console.log('Error', err);
}
}
}
Edit
To anyone that has a similar problem, I ended up trying several different ways to accomplish this and the easiest where these:
Write your own allowRequest
AllowRequest is a pass/fail function that you can use to override the default "checkRequest" function (reference here)
checkBucket(err, remainingRequests) {
remainingRequests < 1 ? false : true;
}
allowRequest(req, callback) {
const limit = this.limiter.removeTokens(1, this.checkBucket);
callback(limit ? null : 'Too many requests', limit);
}
And Server is started as
this.io = this.socketServer(server,
{
allowRequest: this.allowRequest
});
Pick a different route, use express to add any middlewares needed and disable the default route
You can accomplish this by setting serveClient to false and setting the path to whatever you need.
this.io = this.socketServer(server,
{
path: '/newSocketRoute',
serveClient: false
});
This didn't quite work for me, so there's probably something wrong in the way I'm serving the socket files, but this is what it looked like:
app.get("/socket", this.limiter.initialize(), function(req, res) {
if (0 === req.url.indexOf('/newSocketRoute/socket.io.js.map')) {
res.sendFile(join(__dirname, "../../node_modules/socket.io-client/dist/socket.io.js.map"));
} else if (0 === req.url.indexOf('/newSocketRoute/socket.io.js')) {
res.sendFile(join(__dirname, "../../node_modules/socket.io-client/dist/socket.io.js"));
}
});
Socket.io attaches itself to your http server by inserting itself as the first listener to the request event (socket.io code reference here) which means it totally bypasses any Express middleware.
If you're trying to rate limit requests to /socket.io/socket.io.js (the client-side socket.io code), then you could create your own route for that file in Express using your own custom router with a different path and have your client just use the Express version of the path and you could then disable serving that file through socket.io (there's an option to disable it).
If you're trying to rate limit incoming socket.io connections, then you may have to modify your rate limiter so it can participate in the socket.io connect event.
Now that I think of it, you could hack the request event listener list just like socket.io does (you can see the above referenced code link for how it does it) and you could insert your own rate limiting before it gets to see the request. What socket.io is doing is implementing a poor man's middleware and cutting to the front of the line so that they can get first crack at any incoming http request and hide it from others if they have handled it. You could do the same with your rate limiter. Then, you'd be in an "arm's race" to see who gets first crack. Personally, I'd probably just hook the connect event and kill the connection there if rate limiting rules are being violated. But, you could hack away and get in front of the socket.io code.
I'm running into an issue with my http-proxy-middleware stuff. I'm using it to proxy requests to another service which i.e. might resize images et al.
The problem is that multiple clients might call the method multiple times and thus create a stampede on the original service. I'm now looking into (what some services call request coalescing i.e. varnish) a solution that would call the service once, wait for the response and 'queue' the incoming requests with the same signature until the first is done, and return them all in a single go... This is different from 'caching' results due to the fact that I want to prevent calling the backend multiple times simultaneously and not necessarily cache the results.
I'm trying to find if something like that might be called differently or am i missing something that others have already solved someway... but i can't find anything...
As the use case seems pretty 'basic' for a reverse-proxy type setup, I would have expected alot of hits on my searches but since the problemspace is pretty generic i'm not getting anything...
Thanks!
A colleague of mine has helped my hack my own answer. It's currently used as a (express) middleware for specific GET-endpoints and basically hashes the request into a map, starts a new separate request. Concurrent incoming requests are hashed and checked and walked on the separate request callback and thus reused. This also means that if the first response is particularly slow, all coalesced requests are too
This seemed easier than to hack it into the http-proxy-middleware, but oh well, this got the job done :)
const axios = require('axios');
const responses = {};
module.exports = (req, res) => {
const queryHash = `${req.path}/${JSON.stringify(req.query)}`;
if (responses[queryHash]) {
console.log('re-using request', queryHash);
responses[queryHash].push(res);
return;
}
console.log('new request', queryHash);
const axiosConfig = {
method: req.method,
url: `[the original backend url]${req.path}`,
params: req.query,
headers: {}
};
if (req.headers.cookie) {
axiosConfig.headers.Cookie = req.headers.cookie;
}
responses[queryHash] = [res];
axios.request(axiosConfig).then((axiosRes) => {
responses[queryHash].forEach((coalescingRequest) => {
coalescingRequest.json(axiosRes.data);
});
responses[queryHash] = undefined;
}).catch((err) => {
responses[queryHash].forEach((coalescingRequest) => {
coalescingRequest.status(500).json(false);
});
responses[queryHash] = undefined;
});
};
Broadly, I have the following workflow:
User asks for an article with a certain title
Client side, Socket.io emits an event and passes the title as data
Server side, node fires an http request to an API and gathers relevant information about that article
When finished, the server emits that information to the client.
Since 4 depends on 3, my understanding is that it needs to be captured in a callback to effect synchronous behavior. That gave me this:
io.on('connection', function(socket){
socket.on('need data', function(msg) {
getLinkBacks(msg, socket);
});
});
var getLinkBacks = function(title, socket) {
request.get(/* relevant url */, function(err, res, body) {
socket.emit("data", body);
});
};
None of the socket.io documentation talks about async methods and it feels pretty weird to be passing the socket, rather than a callback function, which would be more Node-y. Am I using poor technique or thinking about the problem wrong or is this the standard way to emit the response of an asynchronous method?
Note: I would have put this on Code Review, but they don't have a tag for Socket.IO, which made me think it would fit better here.
I agree with you, Node's style is passing a callback function, so I would rewrite this code as follows:
io.on('connection', function(socket){
socket.on('need data', function(msg) {
getLinkBacks(msg, function(content) {
socket.emit("data", content);
});
});
});
var getLinkBacks = function(title, fn) {
request.get(/* relevant url */, function(err, res, body) {
fn(body);
});
};
This will keep each part of your app isolated, and getLinkBacks function will not have to know about what socket is. This is of course a good programming practice. Besides you could reuse getLinkBacks function in other parts of your app, which are not connected with socket object.
P.S. I would recommend Robert Martin's "Clean Code: A Handbook of Agile Software Craftsmanship". He gives very valuable advises about how to structure your code to make it "clean".
Good luck!
I have a Node.js application with a frontend app and a backend app, the backend will manage the list and "push" an update to the frontend app, the call to the frontend app will trigger a list update so that all clients receive the correct list data.
The problem is on the backend side, when I press the button, I perform an AJAX call, and that AJAX call will perform the following code (trimmed some operations out of it):
Lists.findOne({_id: active_settings.active_id}, function(error, lists_result) {
var song_list = new Array();
for (i=0; i < lists_result.songs.length; i++) {
song_list.push(lists_result.songs[i].ref);
}
Song.find({
'_id': {$in: song_list}
}, function(error, songs){
// DO STUFF WITH THE SONGS
// UPDATE SETTINGS (code trimmed)
active_settings.save(function(error, updated_settings) {
list = {
settings: updated_settings,
};
var io = require('socket.io-client');
var socket = io.connect(config.app_url);
socket.on('connect', function () {
socket.emit('update_list', {key: config.socket_key});
});
response.json({
status: true,
list: list
});
response.end();
}
});
});
However the response.end never seems to work, the call keeps hanging, further more, the list doesn't always get refreshed so there is an issue with the socket.emit code. And the socket connection stays open I assume because the response isn't ended?
I have never done this server side before so any help would be much appreciated. (the active_settings etc exists)
I see some issues that might or might not be causing your problems:
list isn't properly scoped, since you don't prefix it with var; essentially, you're creating a global variable which might get overwritten when there are multiple requests being handled;
response.json() calls .end() itself; it doesn't hurt to call response.end() again yourself, but not necessary;
since you're not closing the socket(.io) connection anywhere, it will probably always stay open;
it sounds more appropriate to not set up a new socket.io connection for each request, but just once at your app startup and just re-use that;
I am using node.js for server side development and backbone.js for client side development. i want to fetch data from multiple table(more than 3) by sending only one request to node.js. but i cant merge all that result with each other beacuse of asynchronous execution of node.js. i have done this but it sending a lots of get request to node js for getting data from all the tables and because of these performance of my site is become slower. please help me if anyone having any idea.
I would create a method which aggregates the results from each of the requests and sends the response back. Basically each of your three async db calls would pass their data to the same method. That method would check to see if it had all of the data it needed to complete the request, and if it did, send the response.
Here is a pseudo code example:
function handleRequest(req, res) {
var results = {};
db.getUsers(function(data) {
aggregate('users', data);
});
db.getPosts(function(data) {
aggregate('posts', data);
});
db.getComments(function(data) {
aggregate('comments', data);
});
function aggregate(name, data) {
results[name] = data;
if(results.users && results.posts && results.comments) {
res.send(results);
}
}
}
This is simplified greatly, you should also of course check for errors and timeouts to the db calls, but this will allow you to wait for all the async commands to complete before sending the data.