@@ -20,6 +20,7 @@ type rateLimiter struct {
2020type tokenBucket struct {
2121 tokens float64
2222 lastRefill time.Time
23+ expiresAt time.Time
2324}
2425
2526func newRateLimiter (rps int64 , burstOrWindow any ) * rateLimiter {
@@ -56,12 +57,29 @@ func newRateLimiter(rps int64, burstOrWindow any) *rateLimiter {
5657 // Unknown input types fall back to the RPS-derived burst.
5758 }
5859
59- return & rateLimiter {
60+ rl := & rateLimiter {
6061 rate : rate ,
6162 burst : float64 (burst ),
6263 now : func () time.Time { return time .Now ().UTC () },
6364 client : make (map [string ]tokenBucket ),
6465 }
66+ go rl .periodicCleanup ()
67+ return rl
68+ }
69+
70+ func (rl * rateLimiter ) periodicCleanup () {
71+ ticker := time .NewTicker (5 * time .Minute )
72+ defer ticker .Stop ()
73+ for range ticker .C {
74+ rl .mu .Lock ()
75+ now := rl .now ()
76+ for key , entry := range rl .client {
77+ if now .After (entry .expiresAt ) {
78+ delete (rl .client , key )
79+ }
80+ }
81+ rl .mu .Unlock ()
82+ }
6583}
6684
6785func (rl * rateLimiter ) allow (remoteAddr string ) bool {
@@ -86,6 +104,8 @@ func (rl *rateLimiter) allow(remoteAddr string) bool {
86104 bucket .lastRefill = now
87105 }
88106
107+ bucket .expiresAt = now .Add (5 * time .Minute )
108+
89109 if bucket .tokens < 1 {
90110 rl .client [key ] = bucket
91111 return false
@@ -106,7 +126,7 @@ func (rl *rateLimiter) middleware(next http.Handler) http.Handler {
106126 next .ServeHTTP (w , r )
107127 return
108128 }
109- if ! rl .allow (r . RemoteAddr ) {
129+ if ! rl .allow (clientIP ( r ) ) {
110130 http .Error (w , "too many requests" , http .StatusTooManyRequests )
111131 return
112132 }
@@ -127,3 +147,17 @@ func normalizeClientIP(remoteAddr string) string {
127147
128148 return remoteAddr
129149}
150+
151+ func clientIP (r * http.Request ) string {
152+ if xff := r .Header .Get ("X-Forwarded-For" ); xff != "" {
153+ if idx := strings .IndexByte (xff , ',' ); idx >= 0 {
154+ return strings .TrimSpace (xff [:idx ])
155+ }
156+ return strings .TrimSpace (xff )
157+ }
158+ host , _ , err := net .SplitHostPort (r .RemoteAddr )
159+ if err != nil {
160+ return strings .TrimSpace (r .RemoteAddr )
161+ }
162+ return host
163+ }
0 commit comments