Skip to content

Commit 8d3628b

Browse files
committed
Merge pull request #8 from cl-aaron/master
Refactored to use ratelimiter module, clarify names, and formatting
2 parents b766d86 + ebee352 commit 8d3628b

5 files changed

Lines changed: 45 additions & 43 deletions

File tree

README.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
A simple ip based rate limiting plugin for Hapi using Redis.
44

5-
WARNING: This is not sufficient protection against DDoS attacks.
65

76
##Installation
87
npm install hapi-ratelimit
@@ -16,7 +15,7 @@ var server = Hapi.createServer();
1615
var rateOpts = {
1716
redis:{port:#redis-port#, host:#redis-host#},
1817
namespace:"clhr", //namespace for redis keys
19-
global: {limit: 200, bucketLength: 60 } //Set limit to -1 or leave out global to disable global limit
18+
global: {limit: 200, duration: 60 } //Set limit to -1 or leave out global to disable global limit
2019
//The global limit is not given priority over local limits
2120
};
2221
server.route({
@@ -25,7 +24,7 @@ server.route({
2524
handler: someHandler,
2625
configs: {
2726
plugins: {
28-
"hapi-ratelimit": {limit: 100, bucketLength: 60} //limits to one hundred hits per minute on a specific route
27+
"hapi-ratelimit": {limit: 100, duration: 60} //limits to one hundred hits per minute on a specific route
2928
}
3029
}
3130
});

lib/index.js

Lines changed: 26 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,58 @@
11
'use strict';
22

33
var redis = require('redis');
4+
var Limiter = require('ratelimiter');
45

56
var internals = {};
67

78
internals.defaults = {
89
namespace: 'clhr',
910
global: {
1011
limit: -1,
11-
bucketLength: 1
12+
duration: 1
1213
},
1314
redis: {}
1415
};
1516

17+
var MILLISECONDS = 1000;
18+
1619
exports.register = function(plugin, options, next) {
1720
var settings = plugin.hapi.utils.applyToDefaults(internals.defaults, options);
1821
var redisClient = redis.createClient(options.redis.port, options.redis.host, options.redis.options);
1922

2023
plugin.ext('onPreAuth', function(request, callback) {
2124
var route = request.route;
22-
var limit = route.plugins && route.plugins['hapi-ratelimit'];
23-
if (!limit && settings.global.limit > 0) {
24-
limit = settings.global;
25+
var routeLimit = route.plugins && route.plugins['hapi-ratelimit'];
26+
if (!routeLimit && settings.global.limit > 0) {
27+
routeLimit = settings.global;
2528
}
26-
if (limit) {
29+
if (routeLimit) {
2730
var ipts = settings.namespace + ':' + request.info.remoteAddress + ':' + route.path;
31+
var routeLimiter = new Limiter({
32+
id: ipts,
33+
db: redisClient,
34+
max: routeLimit.limit,
35+
duration: routeLimit.duration * MILLISECONDS
36+
});
2837
var error = null;
29-
redisClient.get(ipts, function(err, token) {
38+
routeLimiter.get(function(err, rateLimit) {
3039
if (err) {
3140
return callback(err);
3241
}
3342
request.plugins['hapi-ratelimit'] = {};
34-
request.plugins['hapi-ratelimit'].limit = limit.limit;
35-
var left = limit.limit - token;
36-
request.plugins['hapi-ratelimit'].remaining = left > 0 ? left - 1 : 0; //they've already reached it at this point
37-
request.plugins['hapi-ratelimit'].reset = limit.bucketLength;
43+
request.plugins['hapi-ratelimit'].limit = rateLimit.total;
44+
request.plugins['hapi-ratelimit'].remaining = rateLimit.remaining;
45+
request.plugins['hapi-ratelimit'].reset = rateLimit.reset;
3846

39-
if (token) {
40-
if (token >= limit.limit) {
41-
error = plugin.hapi.error.badRequest('Rate limit exceeded');
42-
error.output.statusCode = 429; // Assign a Too Many Requests response
43-
error.output.headers['X-Rate-Limit-Limit'] = request.plugins['hapi-ratelimit'].limit;
44-
error.output.headers['X-Rate-Limit-Remaining'] = request.plugins['hapi-ratelimit'].remaining;
45-
error.output.headers['X-Rate-Limit-Reset'] = request.plugins['hapi-ratelimit'].reset;
46-
error.reformat();
47-
}
48-
redisClient.incr(ipts, function(err) {
49-
if (err) {
50-
return callback(err);
51-
}
52-
return callback(error);
53-
});
54-
} else {
55-
redisClient.multi([
56-
['INCR', ipts],
57-
['EXPIRE', ipts, limit.bucketLength]
58-
]).exec(function(err) {
59-
if (err) {
60-
return callback(err);
61-
}
62-
return callback();
63-
});
47+
if (rateLimit.remaining <= 0) {
48+
error = plugin.hapi.error.badRequest('Rate limit exceeded');
49+
error.output.statusCode = 429; // Assign a Too Many Requests response
50+
error.output.headers['X-Rate-Limit-Limit'] = request.plugins['hapi-ratelimit'].limit;
51+
error.output.headers['X-Rate-Limit-Remaining'] = request.plugins['hapi-ratelimit'].remaining;
52+
error.output.headers['X-Rate-Limit-Reset'] = request.plugins['hapi-ratelimit'].reset;
53+
error.reformat();
6454
}
55+
return callback(error);
6556
});
6657
} else {
6758
return callback();

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "hapi-ratelimit",
3-
"version": "0.0.4",
3+
"version": "0.0.5",
44
"description": "A rate limiting plugin for HAPI.",
55
"main": "index.js",
66
"scripts": {
@@ -16,7 +16,8 @@
1616
"author": "Aaron Elligsen",
1717
"license": "MIT",
1818
"dependencies": {
19-
"redis": "~0.10.0"
19+
"redis": "~0.10.0",
20+
"ratelimiter": "~1.0.1"
2021
},
2122
"devDependencies": {
2223
"hapi": "~3.1.0",

tests/global-limit.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,18 @@ describe('Hapi route based rate limiting', function() {
1515
request(url).get('/testglobal').expect(429, cb);
1616
}
1717
it('Should return 429 if global limit is reached', function(done) {
18-
async.series([testok, testok, testok, testok, testok, testok, testok, testok, testok, testok, test429], done);
18+
async.series([
19+
testok,
20+
testok,
21+
testok,
22+
testok,
23+
testok,
24+
testok,
25+
testok,
26+
testok,
27+
testok,
28+
test429
29+
], done);
1930
});
2031

2132
});

tests/testserver.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ server.route([{
1818
plugins: {
1919
'hapi-ratelimit': {
2020
limit: 2,
21-
bucketLength: 5
21+
duration: 5
2222
}
2323
}
2424
}
@@ -39,7 +39,7 @@ var rateopts = {
3939
},
4040
global: {
4141
limit: 10,
42-
bucketLength: 20
42+
duration: 20
4343
}
4444
};
4545
server.pack.require('../../hapi-ratelimit', rateopts, function(err) {

0 commit comments

Comments
 (0)