11import Redis from 'ioredis' ;
2- import { RateLimiter , RateLimiterResponse } from '../@types/rateLimit' ;
2+ import { RateLimiter , RateLimiterResponse , RedisBucket , RedisLog } from '../@types/rateLimit' ;
33
44/**
55 * The SlidingWindowLog instance of a RateLimiter limits requests based on a unique user ID.
@@ -33,7 +33,40 @@ class SlidingWindowLog implements RateLimiter {
3333 this . capacity = capacity ;
3434 this . client = client ;
3535 if ( windowSize <= 0 || capacity <= 0 )
36- throw SyntaxError ( 'SlidingWindowLog windowSize and capacity must be positive' ) ;
36+ throw SyntaxError ( 'SlidingWindowLog window size and capacity must be positive' ) ;
37+
38+ // TODO: Define lua script for server side computation
39+ // while x.timestamp + window_size < timestamp lpop
40+ // //https://stackoverflow.com/questions/35677682/filtering-deleting-items-from-a-redis-set
41+ this . client . defineCommand ( 'popWindow' , {
42+ // 2 value timestamp and complexity of this request
43+ lua : `
44+ local totalComplexity = 0 -- complexity of active requests
45+ local expiredMembers = 0 -- number of requests to remove
46+ local key = keys[1] -- uuid
47+ local current_time = keys[2]
48+
49+ for index, value in next, redis.call(key, ????) do
50+ -- string comparisson of timestamps
51+ if .... then
52+
53+ else
54+ totalComplexity += ????
55+ end
56+ end
57+
58+ redis.call(pop, ???)
59+
60+ if total_complexity < window_size then
61+ then
62+ end
63+ return {
64+
65+ }
66+ ` ,
67+ numberOfKeys : 3 , // uuid
68+ readOnly : true ,
69+ } ) ;
3770 }
3871
3972 /**
@@ -50,9 +83,41 @@ class SlidingWindowLog implements RateLimiter {
5083 ) : Promise < RateLimiterResponse > {
5184 // set the expiry of key-value pairs in the cache to 24 hours
5285 const keyExpiry = 86400000 ; // TODO: Make this a global for consistency across each algo.
53- if ( tokens > this . capacity ) return { success : false , tokens : this . capacity } ;
86+ // if (tokens > this.capacity) return { success: false, tokens: this.capacity };
87+
88+ // Each user's log is represented by a redis list with a score = request timestamp
89+ // and a value equal to the complexity
90+ // Drop expired requests from the log. represented by a sorted set in redis
91+
92+ // Get the log from redis
93+ let requestLog : RedisLog = JSON . parse ( ( await this . client . get ( uuid ) ) || '[]' ) ;
94+
95+ // if (requestLog.length === 0) {
96+ // if (tokens > 0) requestLog.push({ timestamp, tokens });
97+ // // return { success: true, tokens: this.capacity - tokens };
98+ // }
99+ const cutoff = timestamp - this . windowSize ;
100+ let tokensInLog = 0 ;
101+ requestLog = requestLog . filter ( ( bucket : RedisBucket ) => {
102+ if ( bucket . timestamp > cutoff ) {
103+ // get complexity sum of active requests
104+ tokensInLog += bucket . tokens ;
105+ return true ;
106+ }
107+ // drop expired requests
108+ return false ;
109+ } ) ;
54110
55- throw new Error ( 'SlidingWindowLog.processRequest not implemented' ) ;
111+ // allow/disallow current request
112+ if ( tokensInLog + tokens <= this . capacity ) {
113+ // update the log
114+ if ( tokens > 0 ) requestLog . push ( { timestamp, tokens } ) ;
115+ await this . client . setex ( uuid , keyExpiry , JSON . stringify ( requestLog ) ) ;
116+ tokensInLog += tokens ;
117+ return { success : true , tokens : this . capacity - tokensInLog } ;
118+ }
119+ await this . client . setex ( uuid , keyExpiry , JSON . stringify ( requestLog ) ) ;
120+ return { success : false , tokens : this . capacity - tokensInLog } ;
56121 }
57122
58123 /**
0 commit comments