redis-sliding-rate-limiter
Version:
Flexible and performant rate limiter based on sliding window algorithm with arbitrary precision
59 lines (51 loc) • 1.82 kB
text/typescript
import { Strategy } from './Strategy';
import { RateLimiterResponse } from '../RateLimiter';
import {
LuaScript,
MicrosecondsToWindowSubdivision,
} from '../lua';
export class RedisStrategy extends Strategy {
async sendCommand(cmd: string, ...args: any[]): Promise<any> {
return await this.limiter.client.sendCommand!([cmd, ...args]);
}
async loadScript(): Promise<string> {
return await this.sendCommand('SCRIPT', 'LOAD', LuaScript);
}
async execScript(key: any): Promise<RateLimiterResponse> {
if (!this.scriptSha1) {
this.scriptSha1 = await this.loadScript();
}
// NB: redis library sendCommand function accepts arguments only as string or Buffer
const args = [
this.scriptSha1,
'1', // number of keys
`${key}`,
`${this.limiter.window}`,
`${MicrosecondsToWindowSubdivision[this.limiter.windowSubdivisionUnit]}`,
`${this.limiter.windowExpireMs}`,
`${this.limiter.limit}`,
`${this.limiter.limitOverhead}`
];
let res: any;
try {
res = await this.sendCommand('EVALSHA', ...args);
}
catch(err: any) {
// Script expired in Redis cache, reload and try again
if (err && err.message && err.message.includes('NOSCRIPT')) {
this.scriptSha1 = await this.loadScript();
args[0] = this.scriptSha1;
res = await this.sendCommand('EVALSHA', ...args);
}
else {
throw err;
}
}
return {
allowed: !!res[0],
remaining: Math.max(0, res[1]),
firstExpireAtMs: res[2],
windowExpireAtMs: res[3]
};
}
}