-
Notifications
You must be signed in to change notification settings - Fork 15
/
Copy pathtoken-bucket.ts
88 lines (73 loc) · 2.17 KB
/
token-bucket.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
import { NextFunction, Request, Response } from 'express';
import { RateLimiter, TokenBucketArgs } from '../types';
export class TokenBucketRateLimiter implements RateLimiter {
/**
* A map that stores the number of available tokens for each IP address.
* Each IP has its own bucket.
*
*/
tokens: Map<string, number>;
/**
* Stores the Timer object reference.
*
* @type {NodeJS.Timer}
*/
timer: NodeJS.Timer;
capacity: number;
timePeriodInMs: number;
constructor({ capacity, timePeriodInMs }: TokenBucketArgs) {
this.capacity = capacity;
this.tokens = new Map<string, number>();
// Verify timePeriodInMs
if (timePeriodInMs > 0) {
this.timePeriodInMs = timePeriodInMs;
} else {
throw new Error(`Invalid timePeriod ${timePeriodInMs}. It should be >=0`);
}
// Start add tokens with the provided timer period
// TODO: Check race conditions
this.timer = setInterval(() => this.addTokens(), timePeriodInMs);
}
/**
* This function adds a token to all the buckets.
*
* @private
*/
private addTokens(): void {
this.tokens.forEach((value, key) => {
if (value >= this.capacity) {
return;
}
this.tokens.set(key, value + 1);
});
}
handleRequest(req: Request, res: Response, next: NextFunction): void {
const ip = req.headers['x-forwarded-for'] || req.socket.remoteAddress;
// Make sure a valid IP is present in the request.
if (typeof ip !== 'string') {
res
.status(400)
.send('Invalid x-forwarded-for header or remote address\n');
return;
}
const tokensInBucket = this.tokens.get(ip);
// First time encountering this ip
// Initialize a new Bucket for this.
if (tokensInBucket === undefined) {
this.tokens.set(ip, this.capacity - 1);
next();
return;
}
// If no tokens left to utilize, reject the request
if (tokensInBucket === 0) {
res.status(429).send('Too many requests. Please try again later\n');
return;
}
// Decrement the number of tokens for this IP
this.tokens.set(ip, tokensInBucket - 1);
next();
}
cleanup(): void {
clearInterval(this.timer);
}
}