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.3.0 to 1.3.2

.travis.yml

3

lib/memory-store.js

@@ -10,4 +10,3 @@ "use strict";

MemoryStore.prototype.get = function(key, callback) {
var entry = this.cache.get(key);
callback(null, entry);
callback(null, this.cache.get(key));
};

@@ -14,0 +13,0 @@

@@ -6,7 +6,9 @@ "use strict";

var options = require("./options");
// Default token storage (memory-bounded LRU cache)
var MemoryStore = require("./memory-store");
function Throttle(options) {
var opts = parse_options(options);
function Throttle(opts) {
opts = options.parse(opts);
var refill;

@@ -18,10 +20,10 @@

var bucket1 = Math.floor((bucket.mtime - bucket.window_start) / bucket.period);
var bucket2 = Math.floor((t - bucket.window_start) / bucket.period);
var window1 = Math.floor((bucket.mtime - bucket.window_start) / bucket_settings.period);
var window2 = Math.floor((t - bucket.window_start) / bucket_settings.period);
if (bucket1 == bucket2) {
if (window1 == window2) {
return 0;
} else {
bucket.window_start = t;
return bucket.size;
return bucket_settings.size;
}

@@ -36,10 +38,12 @@ };

var bucket_size = opts.burst || opts.rate.amount;
var bucket_settings = {
"size": opts.burst || opts.rate.amount,
"period": opts.rate.period,
"refill": refill
};
var store = opts.store || new MemoryStore(10000);
// key function, used to identify the client we are going to throttle
var key_func = opts.key || function(req) {
return req.headers["x-forwarded-for"] || req.connection.remoteAddress;
};
var key_func = opts.key || function(req) { return req.ip; };
var cost_func;

@@ -74,4 +78,4 @@

var t = Date.now();
bucket = bucket || create_bucket(bucket_size, opts.rate.period, t);
var is_allowed = update_bucket(bucket, refill, cost, t);
bucket = bucket || create_bucket(bucket_settings, t);
var is_allowed = update_bucket(bucket, bucket_settings, cost, t);

@@ -93,109 +97,19 @@ store.set(key, bucket, function(err) {

function parse_options(options) {
if (typeof(options) == "string") {
options = { "rate": options };
}
if (typeof(options) != "object") {
throw new Error("options needs to be an object.");
} else {
options = shallow_clone(options);
}
if (typeof(options.rate) != "string") {
throw new Error("'rate' needs to be a string (e.g 3/s, 5/2min, 10/day).");
}
options.rate = parse_rate(options.rate);
if (options.burst && typeof(options.burst) != "number") {
throw new Error("'burst' needs to be a number.");
}
if (options.key && typeof(options.key) != "function") {
throw new Error("'key' needs to be a function.");
}
if (options.cost && !(typeof(options.cost) == "number" || typeof(options.cost) == "function")) {
throw new Error("'cost' needs to be a number or function.");
}
if (options.on_allowed && typeof(options.on_allowed) != "function") {
throw new Error("'on_allowed' needs to be a function.");
}
if (options.on_throttled && typeof(options.on_throttled) != "function") {
throw new Error("'on_throttled' needs to be a function.");
}
return options;
}
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)?$/;
function parse_rate(rate) {
var parsed_rate = rate.match(RATE_PATTERN);
if (!parsed_rate) {
throw new Error("invalid rate (e.g 3/s, 5/2min, 10/day).");
}
var numerator = parseInt(parsed_rate[1], 10);
var denominator = parseInt(parsed_rate[2] || 1, 10);
var time_unit = parsed_rate[3];
var fixed = parsed_rate[4] == ":fixed";
function create_bucket(settings, ctime) {
return {
"amount": numerator,
"period": denominator * time_unit_to_ms(time_unit),
"fixed": fixed
};
}
function time_unit_to_ms(time_unit) {
switch (time_unit) {
case "ms":
return 1;
case "s": case "sec": case "second":
return 1000;
case "m": case "min": case "minute":
return 60 * 1000;
case "h": case "hour":
return 60 * 60 * 1000;
case "d": case "day":
return 24 * 60 * 60 * 1000;
}
}
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,
"tokens": settings.size,
// last modification time
"mtime": ctime,
// reset time (time left in this period)
"rtime": ctime + period
"rtime": ctime + settings.period
};
}
function update_bucket(bucket, refill, cost, t) {
function update_bucket(bucket, settings, cost, t) {
// Apply the refill first so it doesn't cancel out with the tokens we are
// about to drain
bucket.tokens = clamp_max(bucket.tokens + refill(bucket, t), bucket.size);
bucket.tokens = clamp_max(bucket.tokens + settings.refill(bucket, t), settings.size);
bucket.mtime = t;
bucket.rtime = Math.abs(bucket.period - t % bucket.period);
bucket.rtime = Math.abs(settings.period - t % settings.period);

@@ -202,0 +116,0 @@ if (bucket.tokens >= cost) {

{
"name": "express-throttle",
"version": "1.3.0",
"version": "1.3.2",
"description": "Request throttling middleware for Express",
"main": "index.js",
"scripts": {
"test": "istanbul cover test/throttle.js",
"test": "tap test/*.js --coverage",
"lint": "eslint lib test"

@@ -34,6 +34,5 @@ },

"express": "^4.14.0",
"istanbul": "^0.4.4",
"supertest": "^1.2.0",
"tape": "^4.6.0"
"tap": "^6.1.1"
}
}
# express-throttle
Request throttling middleware for Express framework
[![Build Status](https://travis-ci.org/GlurG/express-throttle.svg?branch=master)](https://travis-ci.org/GlurG/express-throttle)
## Installation

@@ -52,3 +54,3 @@

```
By default, throttling is done on a per ip-address basis (respecting the X-Forwarded-For header). This can be configured by providing a custom key-function:
By default, throttling is done on a per ip-address basis (see [this link](http://expressjs.com/en/api.html#req.ip) about how the ip address is extracted from the request). This can be configured by providing a custom key-function:
```js

@@ -182,4 +184,2 @@ var options = {

{
"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)

@@ -200,3 +200,3 @@ "mtime": Number, (last modification time)

function(req) {
return req.headers["x-forwarded-for"] || req.connection.remoteAddress;
return req.ip; // http://expressjs.com/en/api.html#req.ip
}

@@ -203,0 +203,0 @@ ```

"use strict";
var test = require("tape");
var tap = require("tap");
var express = require("express");

@@ -10,4 +10,4 @@ var request = require("supertest");

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

@@ -25,91 +25,5 @@

test("fail to init...", t => {
t.test("...without options", st => {
st.throws(throttle, new Error);
st.end();
});
tap.test("passthrough...", function(t) {
t.plan(3);
t.test("...with first argument not being a string or object", st => {
st.throws(() => throttle(5), new Error);
st.end();
});
t.test("...with invalid rate string (float not allowed)", st => {
st.throws(() => throttle("1.0/h"), new Error);
st.end();
});
t.test("...with invalid rate string (float not allowed)", st => {
st.throws(() => throttle("1/2.0h"), new Error);
st.end();
});
t.test("...with invalid rate option", st => {
st.throws(() => throttle("10/m:test"), new Error);
st.end();
});
t.test("...with empty option object", st => {
st.throws(() => throttle({}), new Error);
st.end();
});
t.test("...with 'burst' not being a number", st => {
st.throws(() => throttle({ "rate": "1/s", "burst": "5" }), new Error);
st.end();
});
t.test("...with 'key' not being a function", st => {
st.throws(() => throttle({ "rate": "1/s", "key": 1 }), new Error);
st.end();
});
t.test("...with 'cost' not being a number or function", st => {
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", "on_throttled": "test" }), new Error);
st.end();
});
});
test("init with...", t => {
t.test("...rate", st => {
st.doesNotThrow(() => throttle("1/200ms"));
st.doesNotThrow(() => throttle("1/s"));
st.doesNotThrow(() => throttle("1/2sec"));
st.doesNotThrow(() => throttle("1/second"));
st.doesNotThrow(() => throttle("1/m"));
st.doesNotThrow(() => throttle("1/3min"));
st.doesNotThrow(() => throttle("1/minute"));
st.doesNotThrow(() => throttle("1/4h"));
st.doesNotThrow(() => throttle("1/hour"));
st.doesNotThrow(() => throttle("1/d"));
st.doesNotThrow(() => throttle("1/5day"));
st.doesNotThrow(() => throttle("1/m:fixed"));
st.end();
});
t.test("...options object", st => {
st.doesNotThrow(() => throttle({
"rate": "1/s",
"burst": 5,
"key": () => true,
"cost": () => true,
"on_allowed": () => true,
"on_throttled": () => true
}));
st.end();
});
});
test("passthrough...", t => {
function verify(st, end) {

@@ -125,6 +39,6 @@ return function(err, res) {

t.test("...2 requests with enough gap @ rate 5/s", st => {
t.test("...2 requests with enough gap @ rate 5/s", function(st) {
var app = create_app({ "rate": "5/s", "burst": 1 });
request(app).get("/").end(verify(st));
setTimeout(() => {
setTimeout(function() {
request(app).get("/").end(verify(st, true));

@@ -134,6 +48,6 @@ }, 250); // add 50ms to allow some margin for error

t.test("...2 requests with enough gap @ rate 5/2s", st => {
t.test("...2 requests with enough gap @ rate 5/2s", function(st) {
var app = create_app({ "rate": "5/2s", "burst": 1 });
request(app).get("/").end(verify(st));
setTimeout(() => {
setTimeout(function() {
request(app).get("/").end(verify(st, true));

@@ -143,6 +57,6 @@ }, 450);

t.test("...2 requests with enough gap @ rate 5/s:fixed", st => {
t.test("...2 requests with enough gap @ rate 5/s:fixed", function(st) {
var app = create_app({ "rate": "5/s:fixed", "burst": 1 });
request(app).get("/").end(verify(st));
setTimeout(() => {
setTimeout(function() {
request(app).get("/").end(verify(st, true));

@@ -153,3 +67,5 @@ }, 1050);

test("throttle...", t => {
tap.test("throttle...", function(t) {
t.plan(3);
function verify(st, end) {

@@ -165,6 +81,6 @@ return function(err, res) {

t.test("...2 requests without enough gap @ rate 5/s", st => {
t.test("...2 requests without enough gap @ rate 5/s", function(st) {
var app = create_app({ "rate": "5/s", "burst": 1 });
request(app).get("/").end(() => true);
setTimeout(() => {
request(app).get("/").end(function() {});
setTimeout(function() {
request(app).get("/").end(verify(st, true));

@@ -174,6 +90,6 @@ }, 150);

t.test("...2 requests without enough gap @ rate 5/2s", st => {
t.test("...2 requests without enough gap @ rate 5/2s", function(st) {
var app = create_app({ "rate": "5/2s", "burst": 1 });
request(app).get("/").end(() => true);
setTimeout(() => {
request(app).get("/").end(function() {});
setTimeout(function() {
request(app).get("/").end(verify(st, true));

@@ -183,6 +99,6 @@ }, 350);

t.test("...2 requests without enough gap @ rate 5/s:fixed", st => {
t.test("...2 requests without enough gap @ rate 5/s:fixed", function(st) {
var app = create_app({ "rate": "5/s:fixed", "burst": 1 });
request(app).get("/").end(() => true);
setTimeout(() => {
request(app).get("/").end(function() {});
setTimeout(function() {
request(app).get("/").end(verify(st, true));

@@ -193,4 +109,6 @@ }, 950);

test("custom store...", t => {
t.test("...that fails to retrieve", st => {
tap.test("custom store...", function(t) {
t.plan(3);
t.test("...that fails to retrieve", function(st) {
function FailStore() { }

@@ -209,6 +127,6 @@ FailStore.prototype.get = function(key, callback) {

request(app).get("/").end(() => st.end());
request(app).get("/").end(function() { st.end(); });
});
t.test("...that fails to save", st => {
t.test("...that fails to save", function(st) {
function FailStore() { }

@@ -227,12 +145,12 @@ FailStore.prototype.get = function(key, callback) { callback(null, {}); };

request(app).get("/").end(() => st.end());
request(app).get("/").end(function() { st.end(); });
});
t.test("...that works", st => {
t.test("...that works", function(st) {
var store = new MemoryStore();
var app = create_app({ "rate": "1/s", "store": store });
request(app).get("/").end((err, res) => {
request(app).get("/").end(function(err, res) {
st.equal(res.status, 200);
store.get(res.body, (err, bucket) => {
store.get(res.body, function(err, bucket) {
st.ok(bucket);

@@ -245,18 +163,4 @@ st.end();

test("respect x-forwarded-for header", t => {
tap.test("custom key function", function(t) {
var store = new MemoryStore();
var proxy_ip = "123.123.123.123";
var app = create_app({ "rate": "1/s", "store": store });
request(app).get("/").set("x-forwarded-for", proxy_ip).end((err, res) => {
t.equal(res.status, 200);
store.get(proxy_ip, (err, bucket) => {
t.ok(bucket);
t.end();
});
});
});
test("custom key function", t => {
var store = new MemoryStore();
var custom_key = "custom_key";

@@ -269,5 +173,5 @@ var app = create_app({

request(app).get("/").end((err, res) => {
request(app).get("/").end(function(err, res) {
t.equal(res.status, 200);
store.get(custom_key, (err, bucket) => {
store.get(custom_key, function(err, bucket) {
t.ok(bucket);

@@ -279,3 +183,3 @@ t.end();

test("custom cost value", t => {
tap.test("custom cost value", function(t) {
var store = new MemoryStore();

@@ -289,8 +193,8 @@ var app = create_app({

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

@@ -303,3 +207,3 @@ t.end();

test("custom cost function passthrough", t => {
tap.test("custom cost function passthrough", function(t) {
var app = express();

@@ -323,13 +227,13 @@ var store = new MemoryStore();

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

@@ -344,3 +248,3 @@ t.end();

test("custom on_allowed function", t => {
tap.test("custom on_allowed function", function(t) {
var app = create_app({

@@ -353,3 +257,3 @@ "rate": "1/s",

request(app).get("/").end((err, res) => {
request(app).get("/").end(function(err, res) {
t.equal(res.status, 201);

@@ -361,3 +265,3 @@ t.assert(close_to(res.body.tokens, 0));

test("custom on_throttled function", t => {
tap.test("custom on_throttled function", function(t) {
var app = create_app({

@@ -370,4 +274,4 @@ "rate": "1/s",

request(app).get("/").end(() => true);
request(app).get("/").end((err, res) => {
request(app).get("/").end(function() {});
request(app).get("/").end(function(err, res) {
t.equal(res.status, 503);

@@ -374,0 +278,0 @@ t.assert(close_to(res.body.tokens, 0));

Sorry, the diff of this file is not supported yet

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