How to limit the amount of requests per ip in Node.JS?

If you want to build this yourself at the app server level, you will have to build a data structure that records each recent access from a particular IP address so that when a new request arrives, you can look back through the history and see if it has been doing too many requests. If so, deny it any further data. And, to keep this data from piling up in your server, you'd also need some sort of cleanup code that gets rid of old access data.

Here's an idea for a way to do that (untested code to illustrate the idea):

function AccessLogger(n, t, blockTime) {
    this.qty = n;
    this.time = t;
    this.blockTime = blockTime;
    this.requests = {};
    // schedule cleanup on a regular interval (every 30 minutes)
    this.interval = setInterval(this.age.bind(this), 30 * 60 * 1000);
}

AccessLogger.prototype = {
    check: function(ip) {
        var info, accessTimes, now, limit, cnt;

        // add this access
        this.add(ip);

        // should always be an info here because we just added it
        info = this.requests[ip];
        accessTimes = info.accessTimes;

        // calc time limits
        now = Date.now();
        limit = now - this.time;

        // short circuit if already blocking this ip
        if (info.blockUntil >= now) {
            return false;
        }

        // short circuit an access that has not even had max qty accesses yet
        if (accessTimes.length < this.qty) {
            return true;
        }
        cnt = 0;
        for (var i = accessTimes.length - 1; i >= 0; i--) {
            if (accessTimes[i] > limit) {
                ++cnt;
            } else {
                // assumes cnts are in time order so no need to look any more
                break;
            }
        }
        if (cnt > this.qty) {
            // block from now until now + this.blockTime
            info.blockUntil = now + this.blockTime;
            return false;
        } else {
            return true;
        }

    },
    add: function(ip) {
        var info = this.requests[ip];
        if (!info) {
            info = {accessTimes: [], blockUntil: 0};
            this.requests[ip] = info;
        }
        // push this access time into the access array for this IP
        info.accessTimes.push[Date.now()];
    },
    age: function() {
        // clean up any accesses that have not been here within this.time and are not currently blocked
        var ip, info, accessTimes, now = Date.now(), limit = now - this.time, index;
        for (ip in this.requests) {
            if (this.requests.hasOwnProperty(ip)) {
                info = this.requests[ip];
                accessTimes = info.accessTimes;
                // if not currently blocking this one
                if (info.blockUntil < now) {
                    // if newest access is older than time limit, then nuke the whole item
                    if (!accessTimes.length || accessTimes[accessTimes.length - 1] < limit) {
                        delete this.requests[ip];
                    } else {
                        // in case an ip is regularly visiting so its recent access is never old
                        // we must age out older access times to keep them from 
                        // accumulating forever
                        if (accessTimes.length > (this.qty * 2) && accessTimes[0] < limit) {
                            index = 0;
                            for (var i = 1; i < accessTimes.length; i++) {
                                if (accessTimes[i] < limit) {
                                    index = i;
                                } else {
                                    break;
                                }
                            }
                            // remove index + 1 old access times from the front of the array
                            accessTimes.splice(0, index + 1);
                        }
                    }
                }
            }
        }
    }
};

var accesses = new AccessLogger(10, 3000, 15000);

// put this as one of the first middleware so it acts 
// before other middleware spends time processing the request
app.use(function(req, res, next) {
    if (!accesses.check(req.connection.remoteAddress)) {
        // cancel the request here
        res.end("No data for you!");
    } else {
        next();
    }
});

This method also has the usual limitations around IP address monitoring. If multiple users are sharing an IP address behind NAT, this will treat them all as one single user and they may get blocked due to their combined activity, not because of the activity of one single user.


But, as others have said, by the time the request gets this far into your server, some of the DOS damage has already been done (it's already taking cycles from your server). It might help to cut off the request before doing more expensive operations such as database operations, but it is even better to detect and block this at a higher level (such as Nginx or a firewall or load balancer).


I don't think that is something that should be done at the http server level. Basically, it doesn't prevent users to reach your server, even if they won't see anything for 15 minutes.

In my opinion, you should handle that within your system, using a firewall. Although it's more a discussion for ServerFault or SuperUser, let me give you a few pointers.

  1. Use iptables to setup a firewall on your entry point (your server or whatever else you have access to up the line). iptables allows you to set a limit of max connections per IP. The learning curve is pretty steep though if you don't have a background in Networks. That is the traditional way.

    Here's a good resource geared towards beginners : Iptables for beginners

    And something similar to what you need here : Unix StackExchange

  2. I recently came across a really nice package called Uncomplicated Firewall (ufw) it happens to have an option to limit connection rate per IP and is setup in minutes. For more complicated stuff, you'll still need iptables though.

    In conclusion, like Brad said,

    let your application servers do what they do best... run your application.

    And let firewalls do what they do best, kick out the unwanted IPs from your servers.


It is not good if you use Nodejs filter the connection or apply the connection policy as that.

It is better if you use Nginx in front of NodeJS

Client --> Nginx --> Nodejs or Application.

It is not difficult and cheap because Ngnix is opensource tooo.

Good luck.

Tags:

Node.Js