New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

rolling-rate-limiter

Package Overview
Dependencies
Maintainers
1
Versions
32
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

rolling-rate-limiter - npm Package Compare versions

Comparing version 0.0.0 to 0.0.1

.travis.yml

9

index.js

@@ -11,4 +11,4 @@ var assert = require("assert");

minDifference = options.minDifference,
namespace = options.namespace || ("rate-limiter-" + Math.random().toString(36).slice(2));
namespace = options.namespace || (options.redis ? ("rate-limiter-" + Math.random().toString(36).slice(2)) : "");
redis = redis || fakeredis.createClient();

@@ -25,2 +25,7 @@

return function (id, cb) {
if (!cb) {
cb = id;
id = "";
}
assert.equal(typeof cb, "function", "Callback must be a function.");

@@ -27,0 +32,0 @@

{
"name": "rolling-rate-limiter",
"version": "0.0.0",
"version": "0.0.1",
"description": "Rate limiter that supports a rolling window, either in-memory or backed by redis",

@@ -5,0 +5,0 @@ "main": "index.js",

# Rolling Rate Limiter
[![Build Status](https://travis-ci.org/classdojo/rolling-rate-limiter.svg?branch=master)](https://travis-ci.org/classdojo/rolling-rate-limiter)
## Description
This is an implementation of a rate limiter in node.js that allows for rate limiting with a rolling window. This means that if a user is allowed 5 actions per 60 seconds, any action will be blocked if 5 actions have already occured in the preceeding 60 seconds, without any set points at which this interval resets. This contrasts with many existing implementations, in which a user could make 5 requests at 0:59 and another 5 requests at 1:01. The implementation uses what I believe to be a novel algorithm, using sorted sets.
This is an implementation of a rate limiter in node.js that allows for rate limiting with a rolling window. This means that if a user is allowed 5 actions per 60 seconds, any action will be blocked if 5 actions have already occured in the preceeding 60 seconds, without any set points at which this interval resets. This contrasts with many existing implementations, in which a user could make 5 requests at 0:59 and another 5 requests at 1:01. The implementation uses what I believe to be a novel algorithm, using sorted sets. It can use either in-memory storage or Redis as a backend.
## Method of operation
* Users are namespaced by an identifier, which is combined with a rate limiter namespace to form unique keys in redis.
* Each key corresponds to a sorted set. The keys and values are both set to the (microsecond) times at which actions were attempted.
* When a new action comes in, all elements in the set with keys less than (now - rate limit window) are dropped.
* If there are still (limit) actions in the set, the current action is blocked.
* If the most recent previous key is too close to the current time, and a minimum difference has been set, the current action is blocked.
* The current action is added to the set.
* __Note__: if an action is blocked, it is still added to the set.
* All redis operations are performed as an atomic transaction.
## Examples

@@ -26,5 +17,7 @@

maxInInterval: 10
minDifference: 100 // optional, in miliseconds
minDifference: 100 // optional: the minimum time (in miliseconds) between any two actions
});
// First argument should be a unique identifier for a user.
// If the limiter does not differentiate between users, pass only a callback.
limiter("user1234", function(err, success) {

@@ -43,4 +36,4 @@ // errors if redis connection failed, etc

### With a redis instance
This allows multiple processes (e.g. multiple instances of a server application) to use a single redis to share rate limiter state.
### With a redis backend
This allows multiple processes (e.g. multiple instances of a server application) to use a single redis to share rate limiter state. Make sure that the limiters have identical configurations in each instance.
```javascript

@@ -54,19 +47,20 @@

redis: client,
namespace: "UserLoginLimiter" // optional, allows one redis instance to handle multiple rate limiters
interval: 1000 // in miliseconds
namespace: "UserLoginLimiter" // optional: allows one redis instance to handle multiple groups of rate limiters. defaults to "rate-limiter-{string of random characters}"
interval: 1000
maxInInterval: 10
minDifference: 100 // optional, in miliseconds
minDifference: 100
});
limiter("user1234", function(err, success) {
// errors if redis connection failed, etc
if (err) throw err;
// operation same as above.
if (success) {
// limit was not exceeded, action should be allowed
} else {
// limit was exceeded, action should not be allowed
}
});
```
```
## Method of operation
* Each key corresponds to a sorted set. The keys and values are both set to the (microsecond) times at which actions were attempted.
* When a new action comes in, all elements in the set with keys less than (now - rate limit window) are dropped.
* If there are still (limit) actions in the set, the current action is blocked.
* If the most recent previous key is too close to the current time, and a minimum difference has been set, the current action is blocked.
* The current action is added to the set.
* __Note__: if an action is blocked, it is still added to the set. This means that if a user is continually attempting actions more quickly than the allowed rate, __all__ of their actions will be blocked until.
* If the limiter uses a redis instance, identifiers can be namespaced by an identifier, which is combined with a rate limiter namespace to form unique keys.
* All redis operations for a single rate-limit check are performed as an atomic transaction.

@@ -14,6 +14,11 @@ var sinon = require("sinon");

increment: function(userId, cb) {
counts[userId] = counts[userId] || 0;
rateLimiter(userId, function(err, success) {
if (!cb) {
cb = userId;
userId = null;
}
counts[String(userId)] = counts[String(userId)] || 0;
var limit = userId ? rateLimiter.bind(null, userId) : rateLimiter
limit(function(err, success) {
if (success) {
counts[userId]++;
counts[String(userId)]++;
}

@@ -25,3 +30,3 @@ cb(err);

getCount: function(userId) {
return counts[userId];
return counts[userId == null ? "null" : userId];
}

@@ -84,3 +89,3 @@ };

var counter = RateLimitedCounter({
interval: 1000,
interval: 300,
maxInInterval: 30

@@ -90,6 +95,6 @@ });

async.times(100, function(n, next) {
counter.increment(1, next);
counter.increment(next);
}, function(err) {
if (err) throw err;
expect(counter.getCount(1)).to.equal(30);
expect(counter.getCount()).to.equal(30);
done();

@@ -101,3 +106,3 @@ });

var counter = RateLimitedCounter({
interval: 1000,
interval: 300,
maxInInterval: 30

@@ -120,3 +125,3 @@ });

var counter = RateLimitedCounter({
interval: 1000,
interval: 300,
maxInInterval: 30

@@ -139,3 +144,3 @@ });

});
}, 1000);
}, 500);
});

@@ -175,3 +180,3 @@ });

redis: redis.createClient(),
interval: 1000,
interval: 300,
maxInInterval: 30

@@ -181,6 +186,6 @@ });

async.times(100, function(n, next) {
counter.increment(1, next);
counter.increment(next);
}, function(err) {
if (err) throw err;
expect(counter.getCount(1)).to.equal(30);
expect(counter.getCount()).to.equal(30);
done();

@@ -193,3 +198,3 @@ });

redis: redis.createClient(),
interval: 1000,
interval: 300,
maxInInterval: 30

@@ -213,3 +218,3 @@ });

redis: redis.createClient(),
interval: 1000,
interval: 300,
maxInInterval: 30

@@ -232,3 +237,3 @@ });

});
}, 1000);
}, 300);
});

@@ -261,3 +266,3 @@ });

redis: client,
interval: 500,
interval: 300,
maxInInterval: 15,

@@ -267,3 +272,3 @@ }),

redis: client,
interval: 500,
interval: 300,
maxInInterval: 15,

@@ -294,3 +299,3 @@ })

namespace: namespace,
interval: 500,
interval: 300,
maxInInterval: 30,

@@ -301,3 +306,3 @@ }),

namespace: namespace,
interval: 500,
interval: 300,
maxInInterval: 30,

@@ -334,9 +339,2 @@ })

});
});
});
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc