Reputation: 2869
My code is just a regular app:
app
.use(sassMiddleware({
src: __dirname + '/sass',
dest: __dirname + '/',
// This line controls sass log output
debug: false,
outputStyle: 'compressed'
}))
// More libraries
...
.get('/', auth.protected, function (req, res) {
res.sendfile(__dirname + '/views/index.html');
})
.post('/dostuff', auth.protected, function (req, res) {
console.log(req.body)
res.redirect('back')
child = require('child_process').spawn(
'./script.rb',
['arguments'],
{ stdio: ['ignore', 'pipe', 'pipe'] }
);
child.stdout.pipe(process.stdout);
child.stderr.pipe(process.stdout);
})
My original goal is to limit the number of spawns that /dostuff can spawn to a single instance. I was thinking that there might be a simple way to limit the number of users on the entire app, but can't seem to find any.
I was trying to look for some session limiting mechanism but can't find one either, only various rate limiters but I don't think that's what I want.
Since the app is running in docker I limit the number of tcp connections on the port using iptalbes but this has proven to be less then ideal since the app retains some connections in established
state which prevents efficient hand off from one user to another.
So... any programmatic way of doing this?
UPDATE
The app is not an api server. /dostuff
is actually triggered by the user from a webpage. That's why simple rate limiting is not the best option. Also the times of execution for the ruby script are variable.
ANSWER
Based on the answer below from @jfriend00, by fixing a couple of logical errors I came up with:
.post('/dostuff*', auth.protected, function (req, res) {
console.log(req.body)
if (spawnCntr >= spawnLimit) {
res.status(502).send('Server is temporarily busy');
console.log("You already have process running. Please either abort the current run or wait until it completes".red)
return;
}
let childClosed = false
function done () {
if (!childClosed) {
--spawnCntr;
childClosed = true;
}
}
++spawnCntr;
child = require('child_process').spawn(
blahblah
);
child.on('close', done);
child.on('error', done);
child.on('exit', done);
child.stdout.pipe(process.stdout);
child.stderr.pipe(process.stdout);
res.redirect('back');
})
I am still going to accept his answer although incomplete it helped a lot.
Upvotes: 0
Views: 919
Reputation: 708206
You can keep a simple counter of how many spawn()
operations are in process at the same time and if a new request comes in and you are currently over that limit, you can just return a 502 error (server temporarily busy).
let spawnCntr = 0;
const spawnLimit = 1;
app.post('/dostuff', auth.protected, function (req, res) {
console.log(req.body)
if (spawnCntr > spawnLimit) }
return res.status(502).send("Server temporarily busy");
}
let childClosed = false;
function done() {
// make sure we count it closing only once
if (!childClosed) {
--spawnCntr;
childClosed = true;
}
}
++spawnCntr;
let child = require('child_process').spawn(
'./script.rb',
['arguments'],
{ stdio: ['ignore', 'pipe', 'pipe'] }
);
// capture all the ways we could know it's done
child.on('close', done);
child.on('error', done);
child.on('exit', done);
child.stdout.pipe(process.stdout);
child.stderr.pipe(process.stdout);
res.redirect('back');
});
Note: The code in your question does not declare child
as a local variable which looks like a bug waiting to happen.
Upvotes: 1
Reputation: 1620
You can use the node package Express Rate Limit - https://www.npmjs.com/package/express-rate-limit.
For an API-only server where the rate-limiter should be applied to all requests:
var RateLimit = require('express-rate-limit');
app.enable('trust proxy'); // only if you're behind a reverse proxy (Heroku, Bluemix, AWS if you use an ELB, custom Nginx setup, etc)
var limiter = new RateLimit({
windowMs: 15*60*1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs
delayMs: 0 // disable delaying - full speed until the max limit is reached
});
// apply to all requests
app.use(limiter);
For a "regular" web server (e.g. anything that uses express.static()), where the rate-limiter should only apply to certain requests:
var RateLimit = require('express-rate-limit');
app.enable('trust proxy'); // only if you're behind a reverse proxy (Heroku, Bluemix, AWS if you use an ELB, custom Nginx setup, etc)
var apiLimiter = new RateLimit({
windowMs: 15*60*1000, // 15 minutes
max: 100,
delayMs: 0 // disabled
});
// only apply to requests that begin with /api/
app.use('/api/', apiLimiter);
Upvotes: 0