user3081519
user3081519

Reputation: 2869

In expressjs how would I limit the number of users who can make a request at the same time?

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

Answers (2)

jfriend00
jfriend00

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

Damien
Damien

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

Related Questions