Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

express-throttle

Package Overview
Dependencies
Maintainers
1
Versions
10
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

express-throttle - npm Package Compare versions

Comparing version 1.2.0 to 1.3.0

101

lib/throttle.js

@@ -14,13 +14,13 @@ "use strict";

if (opts.rate.fixed) {
var period = opts.rate.period;
refill = function(client, dt) {
// Accumulated delta times
var t = (client.t || 0) + dt;
refill = function(bucket, t) {
bucket.window_start = bucket.window_start || t;
if (t >= period) {
client.t = t % period;
return burst;
var bucket1 = Math.floor((bucket.mtime - bucket.window_start) / bucket.period);
var bucket2 = Math.floor((t - bucket.window_start) / bucket.period);
if (bucket1 == bucket2) {
return 0;
} else {
client.t = t;
return 0;
bucket.window_start = t;
return bucket.size;
}

@@ -30,8 +30,8 @@ };

var rate = opts.rate.amount / opts.rate.period;
refill = function(client, dt) {
return rate * dt;
};
refill = function(bucket, t) {
return rate * (t - bucket.mtime);
};
}
var burst = opts.burst || opts.rate.amount;
var bucket_size = opts.burst || opts.rate.amount;
var store = opts.store || new MemoryStore(10000);

@@ -55,3 +55,7 @@

var on_throttled = opts.on_throttled || function(req, res) {
var on_allowed = opts.on_allowed || function(req, res, next, bucket) { // eslint-disable-line no-unused-vars
next();
};
var on_throttled = opts.on_throttled || function(req, res, next, bucket) { // eslint-disable-line no-unused-vars
res.status(429).end();

@@ -64,3 +68,3 @@ };

store.get(key, function(err, client) {
store.get(key, function(err, bucket) {
if (err) {

@@ -70,5 +74,7 @@ return next(err);

client = client || { "tokens": burst };
var passthrough = update_tokens(client, refill, burst, cost);
store.set(key, client, function(err) {
var t = Date.now();
bucket = bucket || create_bucket(bucket_size, opts.rate.period, t);
var is_allowed = update_bucket(bucket, refill, cost, t);
store.set(key, bucket, function(err) {
if (err) {

@@ -78,6 +84,6 @@ return next(err);

if (passthrough) {
next();
if (is_allowed) {
on_allowed(req, res, next, bucket);
} else {
on_throttled(req, res);
on_throttled(req, res, next, bucket);
}

@@ -89,12 +95,2 @@ });

function shallow_clone(obj) {
var clone = {};
for (var key in obj) {
clone[key] = obj[key];
}
return clone;
}
function parse_options(options) {

@@ -128,2 +124,6 @@ if (typeof(options) == "string") {

}
if (options.on_allowed && typeof(options.on_allowed) != "function") {
throw new Error("'on_allowed' needs to be a function.");
}

@@ -137,2 +137,12 @@ if (options.on_throttled && typeof(options.on_throttled) != "function") {

function shallow_clone(obj) {
var clone = {};
for (var key in obj) {
clone[key] = obj[key];
}
return clone;
}
var RATE_PATTERN = /^(\d+)\/(\d+)?(ms|s|sec|second|m|min|minute|h|hour|d|day)(:fixed)?$/;

@@ -174,13 +184,26 @@

function update_tokens(client, refill, burst, cost) {
var t = Date.now();
var dt = t - (client.accessed || t);
function create_bucket(size, period, ctime) {
return {
// max tokens this bucket will have (static)
"size": size,
// time in ms it takes to replenish all tokens (static)
"period": period,
// current token count
"tokens": size,
// last modification time
"mtime": ctime,
// reset time (time left in this period)
"rtime": ctime + period
};
}
function update_bucket(bucket, refill, cost, t) {
// Apply the refill first so it doesn't cancel out with the tokens we are
// about to consume
client.tokens = clamp_max(client.tokens + refill(client, dt), burst);
client.accessed = t;
// about to drain
bucket.tokens = clamp_max(bucket.tokens + refill(bucket, t), bucket.size);
bucket.mtime = t;
bucket.rtime = Math.abs(bucket.period - t % bucket.period);
if (client.tokens >= cost) {
client.tokens -= cost;
if (bucket.tokens >= cost) {
bucket.tokens -= cost;
return true;

@@ -187,0 +210,0 @@ } else {

{
"name": "express-throttle",
"version": "1.2.0",
"version": "1.3.0",
"description": "Request throttling middleware for Express",

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

@@ -47,3 +47,3 @@ # express-throttle

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

@@ -105,4 +105,3 @@ // ...

"rate": "5/s",
"burst": 10,
"on_throttled": function(req, res) {
"on_throttled": function(req, res, next, bucket) {
// Possible course of actions:

@@ -113,2 +112,3 @@ // 1) Log request

res.set("X-Rate-Limit-Limit", 5);
res.set("X-Rate-Limit-Remaining", 0);
res.status(503).send("System overloaded, try again at a later time.");

@@ -118,2 +118,13 @@ }

```
You may also customize the response on requests that are passed through:
```js
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);
res.set("X-Rate-Limit-Reset", bucket.rtime);
}
}
```
Throttling can be applied across multiple processes. This requires an external storage mechanism which can be configured as follows:

@@ -127,10 +138,10 @@ ```js

ExternalStorage.prototype.get = function(key, callback) {
fetch(key, function(entry) {
fetch(key, function(bucket) {
// First argument should be null if no errors occurred
callback(null, entry);
callback(null, bucket);
});
}
ExternalStorage.prototype.set = function(key, value, callback) {
save(key, value, function(err) {
ExternalStorage.prototype.set = function(key, bucket, callback) {
save(key, bucket, function(err) {
// err should be null if no errors occurred

@@ -166,11 +177,19 @@ callback(err);

function get(key, callback) {
fetch(key, function(err, value) {
fetch(key, function(err, bucket) {
if (err) callback(err);
else callback(null, value);
else callback(null, bucket);
});
}
function set(key, value, callback) {
// value will be an object with the following structure:
// { "tokens": Number, "accessed": Number }
save(key, value, function(err) {
function set(key, bucket, callback) {
// 'bucket' will be an object with the following structure:
/*
{
"size": Number, (max number of tokens this bucket can have)
"period": Number, (time in ms it takes to replenish all tokens)
"tokens": Number, (current number of tokens)
"mtime": Number, (last modification time)
"rtime": Number (time until next reset)
}
*/
save(key, bucket, function(err) {
callback(err);

@@ -189,9 +208,16 @@ }

`cost`: Number or function used to calculate the cost for a request. It will be called with an [express request object](http://expressjs.com/en/4x/api.html#req). Defaults to 1.
`cost`: Number or function used to calculate the cost for a request with an [express request object](http://expressjs.com/en/4x/api.html#req). Defaults to 1.
`on_throttled`: A function called when the request is throttled. It will be called with an [express request object](http://expressjs.com/en/4x/api.html#req) and [express response object](http://expressjs.com/en/4x/api.html#res). Defaults to:
`on_allowed`: A function called when the request is passed through with an [express request object](http://expressjs.com/en/4x/api.html#req), [express response object](http://expressjs.com/en/4x/api.html#res), `next` function and a `bucket` object. Defaults to:
```js
function(req, res) {
function(req, res, next, bucket) {
next();
}
```
`on_throttled`: A function called when the request is throttled with an [express request object](http://expressjs.com/en/4x/api.html#req), [express response object](http://expressjs.com/en/4x/api.html#res), `next` function and a `bucket` object. Defaults to:
```js
function(req, res, next, bucket) {
res.status(429).end();
};
```

@@ -11,3 +11,3 @@ "use strict";

function close_to(value, target, delta = 0.001) {
return Math.abs(value - target) < delta;
return Math.abs(value - target) <= delta;
}

@@ -62,3 +62,3 @@

t.test("...with 'key' not being a function", st => {
st.throws(() => throttle({ "rate": "1/s", "burst": 5, "key": 1 }), new Error);
st.throws(() => throttle({ "rate": "1/s", "key": 1 }), new Error);
st.end();

@@ -68,8 +68,13 @@ });

t.test("...with 'cost' not being a number or function", st => {
st.throws(() => throttle({ "rate": "1/s", "burst": 5, "cost": "5" }), new Error);
st.throws(() => throttle({ "rate": "1/s", "cost": "5" }), new Error);
st.end();
});
t.test("...with 'on_allowed' not being a function", st => {
st.throws(() => throttle({ "rate": "1/s", "on_allowed": "test" }), new Error);
st.end();
});
t.test("...with 'on_throttled' not being a function", st => {
st.throws(() => throttle({ "rate": "1/s", "burst": 5, "on_throttled": "test" }), new Error);
st.throws(() => throttle({ "rate": "1/s", "on_throttled": "test" }), new Error);
st.end();

@@ -102,2 +107,3 @@ });

"cost": () => true,
"on_allowed": () => true,
"on_throttled": () => true

@@ -223,4 +229,4 @@ }));

st.equal(res.status, 200);
store.get(res.body, (err, entry) => {
st.ok(entry);
store.get(res.body, (err, bucket) => {
st.ok(bucket);
st.end();

@@ -239,4 +245,4 @@ });

t.equal(res.status, 200);
store.get(proxy_ip, (err, entry) => {
t.ok(entry);
store.get(proxy_ip, (err, bucket) => {
t.ok(bucket);
t.end();

@@ -258,4 +264,4 @@ });

t.equal(res.status, 200);
store.get(custom_key, (err, entry) => {
t.ok(entry);
store.get(custom_key, (err, bucket) => {
t.ok(bucket);
t.end();

@@ -276,5 +282,5 @@ });

request(app).get("/").end((err, res) => {
store.get(res.body, (err, entry) => {
store.get(res.body, (err, bucket) => {
t.equal(res.status, 200);
t.assert(close_to(entry.tokens, 2));
t.assert(close_to(bucket.tokens, 2));

@@ -309,10 +315,10 @@ request(app).get("/").end((err, res) => {

request(app).get("/yes").end((err, res) => {
store.get(res.body, (err, entry) => {
store.get(res.body, (err, bucket) => {
t.equal(res.status, 200);
t.assert(close_to(entry.tokens, 5));
t.assert(close_to(bucket.tokens, 5));
request(app).get("/no").end((err, res) => {
store.get(res.body, (err, entry) => {
store.get(res.body, (err, bucket) => {
t.equal(res.status, 200);
t.assert(close_to(entry.tokens, 2));
t.assert(close_to(bucket.tokens, 2));

@@ -329,7 +335,22 @@ request(app).get("/no").end((err, res) => {

test("custom on_allowed function", t => {
var app = create_app({
"rate": "1/s",
"on_allowed": function(req, res, next, bucket) {
res.status(201).json(bucket);
}
});
request(app).get("/").end((err, res) => {
t.equal(res.status, 201);
t.assert(close_to(res.body.tokens, 0));
t.end();
});
});
test("custom on_throttled function", t => {
var app = create_app({
"rate": "1/s",
"on_throttled": function(req, res) {
res.status(503).json("slow down!");
"on_throttled": function(req, res, next, bucket) {
res.status(503).json(bucket);
}

@@ -341,5 +362,5 @@ });

t.equal(res.status, 503);
t.equal(res.body, "slow down!");
t.assert(close_to(res.body.tokens, 0));
t.end();
});
});

@@ -11,8 +11,2 @@ Multiple rate limits: Achieved by multiple throttles

Staggering windows
Fixed refresh rate
Support for ms
Change cache size

@@ -19,0 +13,0 @@

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