Skip to content

Commit 0e559f8

Browse files
committed
add X-RateLimit-* headers according to industry standards
1 parent 734db40 commit 0e559f8

File tree

2 files changed

+30
-6
lines changed

2 files changed

+30
-6
lines changed

lib/middleware.js

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
var rateLimiter = require('./rate-limiter');
1+
var rateLimiter = require('./rate-limiter');
2+
3+
var defaultFailMessage = 'Rate limit exceeded, retry after {after}ms';
24

35
module.exports = function(opts) {
46
var limiter = rateLimiter(opts);
@@ -7,8 +9,24 @@ module.exports = function(opts) {
79
if (err) {
810
next();
911
} else {
12+
res.set({
13+
'X-RateLimit-Limit': rate.limit,
14+
'X-RateLimit-Remaining': rate.current > rate.limit ? 0 : rate.limit - rate.current,
15+
'X-RateLimit-Reset': (rate.reset / 1000) | 0
16+
});
1017
if (rate.current > rate.limit) {
11-
res.writeHead(429);
18+
var after = rate.reset - Date.now();
19+
var failMessage = defaultFailMessage;
20+
res.set('Retry-After', (after / 1000) | 0);
21+
if(opts.failMessage) {
22+
if(typeof opts.failMessage === 'function') {
23+
failMessage = opts.failMessage(req);
24+
}
25+
else {
26+
failMessage = opts.failMessage;
27+
}
28+
}
29+
res.status(429).send((opts.failMessage || defaultFailMessage).replace(/{after}/g, after));
1230
res.end();
1331
} else {
1432
next();

lib/rate-limiter.js

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,27 @@ module.exports = function(opts) {
1010
.setex(tempKey, opts.window(), 0)
1111
.renamenx(tempKey, realKey)
1212
.incr(realKey)
13-
.ttl(realKey)
13+
.pttl(realKey)
1414
.exec(function(err, results) {
1515
if(err) {
1616
callback(err);
1717
} else {
18-
if (results[3] == -1) { // automatically recover from possible race condition
19-
opts.redis.expire(realKey,opts.window());
18+
var reset, ttl = results[3];
19+
if(ttl == -1) { // automatically recover from possible race condition
20+
opts.redis.expire(realKey, opts.window());
21+
reset = Date.now() + opts.window() * 1000;
22+
}
23+
else {
24+
reset = Date.now() + ttl;
2025
}
2126
var current = results[2];
2227
callback(null, {
2328
key: key,
2429
current: current,
2530
limit: opts.limit(),
2631
window: opts.window(),
27-
over: (current > opts.limit())
32+
over: (current > opts.limit()),
33+
reset: reset
2834
});
2935
}
3036
});

0 commit comments

Comments
 (0)