package middleware import ( "net/http" "sync" "time" "github.com/gin-gonic/gin" "golang.org/x/time/rate" ) type RateLimiter struct { visitors map[string]*visitor mu sync.RWMutex rate rate.Limit burst int } type visitor struct { limiter *rate.Limiter lastSeen time.Time } func NewRateLimiter(requestsPerMinute int) *RateLimiter { rl := &RateLimiter{ visitors: make(map[string]*visitor), rate: rate.Limit(float64(requestsPerMinute) / 60.0), burst: requestsPerMinute / 10, } if requestsPerMinute < 10 { rl.burst = 1 } // Cleanup old visitors go rl.cleanup() return rl } func (rl *RateLimiter) cleanup() { ticker := time.NewTicker(5 * time.Minute) for range ticker.C { rl.mu.Lock() for ip, v := range rl.visitors { if time.Since(v.lastSeen) > 10*time.Minute { delete(rl.visitors, ip) } } rl.mu.Unlock() } } func (rl *RateLimiter) getVisitor(ip string) *rate.Limiter { rl.mu.Lock() defer rl.mu.Unlock() v, exists := rl.visitors[ip] if !exists { v = &visitor{ limiter: rate.NewLimiter(rl.rate, rl.burst), lastSeen: time.Now(), } rl.visitors[ip] = v } v.lastSeen = time.Now() return v.limiter } func RateLimitMiddleware(rl *RateLimiter) gin.HandlerFunc { return func(c *gin.Context) { ip := c.ClientIP() if !rl.getVisitor(ip).Allow() { c.AbortWithStatusJSON(http.StatusTooManyRequests, gin.H{ "error": gin.H{ "code": "rate_limit_exceeded", "message": "Too many requests", "retry_after_seconds": 60, }, }) return } c.Next() } }