express-throttle

    2.0.0 • Public • Published

    express-throttle

    Request throttling middleware for Express framework

    npm version Build Status

    Installation

    $ npm install express-throttle

    Implementation

    The throttling is done using the canonical token bucket algorithm, where tokens are "refilled" in a sliding window manner by default (can be configured to fixed time windows). This means that if we a set maximum rate of 10 requests / minute, a client will not be able to send 10 requests 0:59, and 10 more 1:01. However, if the client sends 10 requests at 0:30, he will be able to send a new request at 0:36 (since tokens are refilled continuously 1 every 6 seconds).

    Limitations

    By default, throttling data is stored in memory and is thus not shared between multiple processes. If your application is behind a load balancer which distributes traffic among several node processes, then throttling will be applied per process, which is generally not what you want (unless you can ensure that a client always hits the same process). It is possible to customize the storage so that the throttling data gets saved to a shared backend (e.g Redis). However, the current implementation contains a race-condition and will likely fail (erroneously allow/block certain requests) under high load. My plan is to address this shortcoming in future versions.

    TL;DR - Use this package in production at your own risk, beware of the limitations.

    • If you are running node as single process = you should be fine
    • If you are running node as multiple processes = be aware that throttling is done per process
    • If you have configured to use an external backend = some requests may erroneously be throttled (or passed through) under high load conditions

    Examples

    var express = require("express");
    var throttle = require("express-throttle");
      
    var app = express();
      
    // Allow 5 requests at any rate with an average rate of 5/s
    app.post("/search", throttle({ "rate": "5/s" }), function(req, res, next) {
      // ...
    });
     
    // Allow 5 requests at any rate during a fixed time window of 1 sec 
    app.post("/search", throttle({ "burst": 5, "period": "1s" }), function(req, res, next) {
      // ...
    });

    Combine it with a burst capacity of 10, meaning that the client can make 10 requests at any rate. The burst capacity is "refilled" with the specified rate (in this case 5/s).

    app.post("/search", throttle({ "burst": 10, "rate": "5/s" }), function(req, res, next) {
      // ...
    });
      
    // "Half" requests are supported as well (1 request every other second)
    app.post("/search", throttle({ "burst": 5, "rate": "1/2s" }), function(req, res, next) {
      // ...
    });

    By default, throttling is done on a per ip-address basis (see this link about how the ip address is extracted from the request). This can be configured by providing a custom key-function:

    var options = {
      "burst": 10,
      "rate": "5/s",
      "key": function(req) {
        return req.session.username;
      }
    };
     
    app.post("/search", throttle(options), function(req, res, next) {
      // ...
    });

    The "cost" per request can also be customized, making it possible to, for example whitelist certain requests:

    var whitelist = ["ip-1", "ip-2", ...];
      
    var options = {
      "burst": 10,
      "rate": "5/s",
      "cost": function(req) {
        var ip_address = req.connection.remoteAddress;
          
        if (whitelist.indexOf(ip_address) >= 0) {
          return 0;
        } else if (req.session.is_privileged_user) {
          return 0.5;
        } else {
          return 1;
        }
      }
    };
      
    app.post("/search", throttle(options), function(req, res, next) {
      // ...
    });
     
    var options = {
      "rate": "1/s",
      "burst": 10,
      "cost": 2.5 // fixed costs are also supported
    };
     
    app.post("/expensive", throttle(options), function(req, res, next) {
      // ...
    });

    Throttled requests will simply be responded with an empty 429 response. This can be overridden:

    var options = {
      "burst": 5
      "period": "1min",
      "on_throttled": function(req, res, next, bucket) {
        // Possible course of actions:
        // 1) Log request
        // 2) Add client ip address to a ban list
        // 3) Send back more information
        res.set("X-Rate-Limit-Limit", 5);
        res.set("X-Rate-Limit-Remaining", 0);
        // bucket.etime = expiration time in Unix epoch ms, only available
        // for fixed time windows
        res.set("X-Rate-Limit-Reset", bucket.etime);
        res.status(503).send("System overloaded, try again at a later time.");
      }
    };

    You may also customize the response on requests that are passed through:

    var options = {
      "rate": "5/s",
      "on_allowed": function(req, res, next, bucket) {
        res.set("X-Rate-Limit-Limit", 5);
        res.set("X-Rate-Limit-Remaining", bucket.tokens);
      }
    }

    Throttling can be applied across multiple processes. This requires an external storage mechanism which can be configured as follows:

    function ExternalStorage(connection_settings) {
      // ...
    }
     
    // These methods must be implemented
    ExternalStorage.prototype.get = function(key, callback) {
      fetch(key, function(bucket) {
        // First argument should be null if no errors occurred
        callback(null, bucket);
      });
    }
      
    ExternalStorage.prototype.set = function(key, bucket, lifetime, callback) {
      save(key, bucket, function(err) {
        // err should be null if no errors occurred
        callback(err); 
      });
    }
      
    var options = {
      "rate": "5/s",
      "store": new ExternalStorage()
    }
      
    app.post("/search", throttle(options), function(req, res, next) {
      // ...
    });

    Options

    burst: The number of requests that can be made at any rate. Defaults to X as defined below for rolling windows.

    rate: Determines the rate at which the burst quota is "refilled". This will control the average number of requests per time unit. Must be specified according to the following format: X/Yt

    where X and Y are integers and t is the time unit which can be any of the following: ms, s, sec, m, min, h, hour, d, day

    E.g 5/s, 180/15min, 1000/d

    period: The duration of the time window after which the entire burst quota is refilled. Must be specified according to the following format: Y/t, where Y and t are defined as above.

    E.g 5s, 15min, 1000d

    store: Custom storage class. Must implement a get and set method with the following signatures:

    // callback in both methods must be called with an error (if any) as first argument
     
    function get(key, callback) {
      fetch(key, function(err, bucket) {
        if (err) callback(err);
        else callback(null, bucket);
      });
    }
    function set(key, bucket, callback) {
      // 'bucket' will be an object with the following structure:
      /*
        {
          "tokens": Number, (current number of tokens)
          "mtime": Number, (last modification time)
          "etime": Number (expiration time, only available for fixed time windows)
        }
      */
      save(key, bucket, function(err) {
        callback(err);
      }
    }

    Defaults to an LRU cache with a maximum of 10000 entries.

    store_size: Determines the maximum number of entries for the default in-memory LRU cache. 0 indicates no limit, not recommended, since entries in the cache are not expired / cleaned up / garbage collected.

    key: Function used to identify clients. It will be called with an express request object. Defaults to:

    function(req) {
        return req.ip; // http://expressjs.com/en/api.html#req.ip
    }

    cost: Number or function used to calculate the cost for a request with an express request object. Defaults to 1.

    on_allowed: A function called when the request is passed through with an express request object, express response object, next function and a bucket object. Defaults to:

    function(req, res, next, bucket) {
        next();
    }

    on_throttled: A function called when the request is throttled with an express request object, express response object, next function and a bucket object. Defaults to:

    function(req, res, next, bucket) {
        res.status(429).end();
    };

    Install

    npm i express-throttle

    DownloadsWeekly Downloads

    1,496

    Version

    2.0.0

    License

    MIT

    Last publish

    Collaborators

    • glurg