Skip to content

Commit 82aae02

Browse files
committed
spec completed, some functionality copied over from token bucket
1 parent 4349a67 commit 82aae02

File tree

2 files changed

+98
-0
lines changed

2 files changed

+98
-0
lines changed

src/@types/rateLimit.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ export interface RedisBucket {
2323
timestamp: number;
2424
}
2525

26+
export interface RedisWindow {
27+
tokens: number;
28+
timestamp: number;
29+
}
30+
2631
export type RateLimiterSelection =
2732
| 'TOKEN_BUCKET'
2833
| 'LEAKY_BUCKET'
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import Redis from 'ioredis';
2+
import { RateLimiter, RateLimiterResponse, RedisWindow } from '../@types/rateLimit';
3+
4+
/**
5+
* The SlidingWindowCounter instance of a RateLimiter limits requests based on a unique user ID.
6+
* This algorithm improves upon the FixedWindowCounter because this algorithm prevents fixed window's
7+
* flaw of allowing doubled capacity requests when hugging the window's borders with a rolling window,
8+
* allowing us to average the requests between both windows proportionately with the rolling window's
9+
* takeup in each.
10+
*
11+
* Whenever a user makes a request the following steps are performed:
12+
* 1. Fixed minute windows are defined along with redis caches if previously undefined.
13+
* 2. Rolling minute windows are defined or updated based on the timestamp of the new request.
14+
* 3. Counter of the current fixed window is updated with the new request's token usage.
15+
* 4. If a new minute interval is reached, the averaging formula is run to prevent fixed window's flaw
16+
* of flooded requests around window borders
17+
* (ex. 10 token capacity: 1m59s 10 reqs 2m2s 10 reqs)
18+
*/
19+
class SlidingWindowCounter implements RateLimiter {
20+
private windowSize: number;
21+
private capacity: number;
22+
private client: Redis;
23+
24+
/**
25+
* Create a new instance of a TokenBucket rate limiter that can be connected to any database store
26+
* @param windowSize size of each window in milliseconds (fixed and rolling)
27+
* @param capacity max capacity of tokens allowed per fixed window
28+
* @param client redis client where rate limiter will cache information
29+
*/
30+
constructor(windowSize: number, capacity: number, client: Redis) {
31+
this.windowSize = windowSize;
32+
this.capacity = capacity;
33+
this.client = client;
34+
if (windowSize <= 0 || capacity <= 0)
35+
throw SyntaxError('SlidingWindowCounter windowSize and capacity must be positive');
36+
}
37+
38+
/**
39+
*
40+
*
41+
* @param {string} uuid - unique identifer used to throttle requests
42+
* @param {number} timestamp - time the request was recieved
43+
* @param {number} [tokens=1] - complexity of the query for throttling requests
44+
* @return {*} {Promise<RateLimiterResponse>}
45+
* @memberof SlidingWindowCounter
46+
*/
47+
async processRequest(
48+
uuid: string,
49+
timestamp: number,
50+
tokens = 1
51+
): Promise<RateLimiterResponse> {
52+
// set the expiry of key-value pairs in the cache to 24 hours
53+
const keyExpiry = 86400000;
54+
55+
// attempt to get the value for the uuid from the redis cache
56+
const windowJSON = await this.client.get(uuid);
57+
58+
// // if the response is null, we need to create a window for the user
59+
// if (windowJSON === null) {
60+
// // rolling window is 1 minute long
61+
// const rollingWindowEnd = timestamp + 60000;
62+
63+
// // grabs the actual minute from the timestamp to create fixed window
64+
// const fixedWindowStart = timestamp - (timestamp % 10000);
65+
// const fixedWindowEnd = fixedWindowStart + 60000;
66+
67+
// const newUserWindow: RedisWindow = {
68+
// // conditionally set tokens depending on how many are requested compared to the capacity
69+
// tokens: tokens > this.capacity ? this.capacity : this.capacity - tokens,
70+
// timestamp,
71+
// };
72+
73+
// // reject the request, not enough tokens could even be in the bucket
74+
// if (tokens > this.capacity) {
75+
// await this.client.setex(uuid, keyExpiry, JSON.stringify(newUserWindow));
76+
// return { success: false, tokens: this.capacity };
77+
// }
78+
// await this.client.setex(uuid, keyExpiry, JSON.stringify(newUserWindow));
79+
// return { success: true, tokens: newUserWindow.tokens };
80+
// }
81+
82+
return { success: true, tokens: 0 };
83+
}
84+
85+
/**
86+
* Resets the rate limiter to the intial state by clearing the redis store.
87+
*/
88+
public reset(): void {
89+
this.client.flushall();
90+
}
91+
}
92+
93+
export default SlidingWindowCounter;

0 commit comments

Comments
 (0)