From 2283789b24ee26c3bcca645edec1b42ecf007d56 Mon Sep 17 00:00:00 2001 From: Ashley Streb Date: Fri, 4 Apr 2014 07:33:29 -0400 Subject: [PATCH 1/2] Add ability to set skipHeaders into the options to prevent the setting of HTTP header fields for X-RateLimit-Limit, X-RateLimit-Remaining and Retry-After. --- README.md | 14 +++++++++++++- index.js | 9 ++++++--- tests/index.js | 25 +++++++++++++++++++++++++ 3 files changed, 44 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 016cd36..d82a421 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ limiter(options) - `total`: `Number` allowed number of requests before getting rate limited - `expire`: `Number` amount of time in `ms` before the rate-limited is reset - `whitelist`: `function(req)` optional param allowing the ability to whitelist. return `boolean`, `true` to whitelist, `false` to passthru to limiter. - + - `skipHeaders`: `Boolean` whether to skip sending HTTP headers for rate limits () ### Examples ``` js @@ -89,6 +89,18 @@ limiter({ return !!req.user.is_admin } }) + +// skip sending HTTP limit headers +limiter({ + path: '/delete/thing', + method: 'post', + lookup: 'user.id', + whitelist: function (req) { + return !!req.user.is_admin + }, + skipHeaders: true +}) + ``` ### as direct middleware diff --git a/index.js b/index.js index 78a39f2..98dfd2c 100644 --- a/index.js +++ b/index.js @@ -28,14 +28,17 @@ module.exports = function (app, db) { limit.remaining = Math.max(Number(limit.remaining) - 1, 0) db.set(key, JSON.stringify(limit), function () { - res.set('X-RateLimit-Limit', limit.total) - res.set('X-RateLimit-Remaining', limit.remaining) + if (!opts.skipHeaders) { + res.set('X-RateLimit-Limit', limit.total) + res.set('X-RateLimit-Remaining', limit.remaining) + } if (limit.remaining) return next() var after = (limit.reset - Date.now()) / 1000 - res.set('Retry-After', after) + if (!opts.skipHeaders) res.set('Retry-After', after) + res.send(429, 'Rate limit exceeded') }) diff --git a/tests/index.js b/tests/index.js index 19b087a..ab51dd5 100644 --- a/tests/index.js +++ b/tests/index.js @@ -24,6 +24,7 @@ describe('rate-limiter', function () { it('should work', function (done) { var map = [10, 9, 8, 7, 6, 5, 4, 3, 2] var clock = sinon.useFakeTimers() + limiter({ path: '/route', method: 'get', @@ -69,4 +70,28 @@ describe('rate-limiter', function () { }) v.waterfall(out, done) }) + + describe('options', function() { + it('should process skipHeaders', function (done) { + limiter({ + path: '/route', + method: 'get', + lookup: ['connection.remoteAddress'], + total: 0, + expire: 1000 * 60 * 60, + skipHeaders: true + }) + + app.get('/route', function (req, res) { + res.send(200, 'hello') + }) + + request(app) + .get('/route') + .expect(function(res) { if ('X-RateLimit-Limit' in res.headers) return 'X-RateLimit-Limit Header not to be set' }) + .expect(function(res) { if ('X-RateLimit-Remaining' in res.headers) return 'X-RateLimit-Remaining Header not to be set' }) + .expect(function(res) { if ('Retry-After' in res.headers) return 'Retry-After not to be set' }) + .expect(429, done) + }) + }) }) From 3c6f79aa7f392da970d0edb821ccab02eab9372a Mon Sep 17 00:00:00 2001 From: Ashley Streb Date: Wed, 9 Apr 2014 17:07:26 -0400 Subject: [PATCH 2/2] Add ability to pass ignoreErrors to the configuration options. When ignoreErrors is true, if an error is encountered connecting to redis the next() middleware is called. --- README.md | 2 ++ index.js | 1 + tests/index.js | 23 +++++++++++++++++++++++ 3 files changed, 26 insertions(+) diff --git a/README.md b/README.md index d82a421..b1f5a8f 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,8 @@ limiter(options) - `expire`: `Number` amount of time in `ms` before the rate-limited is reset - `whitelist`: `function(req)` optional param allowing the ability to whitelist. return `boolean`, `true` to whitelist, `false` to passthru to limiter. - `skipHeaders`: `Boolean` whether to skip sending HTTP headers for rate limits () + - `ignoreErrors`: `Boolean` whether errors generated from redis should allow the middleware to call next(). Defaults to false. + ### Examples ``` js diff --git a/index.js b/index.js index 98dfd2c..e49dcf1 100644 --- a/index.js +++ b/index.js @@ -12,6 +12,7 @@ module.exports = function (app, db) { var key = 'ratelimit:' + opts.path + ':' + opts.method + ':' + lookups db.get(key, function (err, limit) { + if (err && opts.ignoreErrors) return next() var now = Date.now() limit = limit ? JSON.parse(limit) : { total: opts.total, diff --git a/tests/index.js b/tests/index.js index ab51dd5..1fd1cc6 100644 --- a/tests/index.js +++ b/tests/index.js @@ -93,5 +93,28 @@ describe('rate-limiter', function () { .expect(function(res) { if ('Retry-After' in res.headers) return 'Retry-After not to be set' }) .expect(429, done) }) + + it('should process ignoreErrors', function (done) { + limiter({ + path: '/route', + method: 'get', + lookup: ['connection.remoteAddress'], + total: 10, + expire: 1000 * 60 * 60, + ignoreErrors: true + }) + + app.get('/route', function (req, res) { + res.send(200, 'hello') + }) + + sinon.stub(redis, 'get', function(key, callback) { + callback({err: true}) + }) + + request(app) + .get('/route') + .expect(200, done) + }) }) })