package server import ( "net" "net/http" "sync" "time" ) type visitor struct { tokens float64 lastSeen time.Time } type RateLimiter struct { mu sync.Mutex visitors map[string]*visitor rate float64 burst float64 } func NewRateLimiter(requestsPerMinute int) *RateLimiter { rl := &RateLimiter{ visitors: make(map[string]*visitor), rate: float64(requestsPerMinute) / 60.0, burst: float64(requestsPerMinute), } go rl.cleanup() return rl } func (rl *RateLimiter) Allow(ip string) bool { rl.mu.Lock() defer rl.mu.Unlock() v, exists := rl.visitors[ip] now := time.Now() if !exists { rl.visitors[ip] = &visitor{tokens: rl.burst - 1, lastSeen: now} return true } elapsed := now.Sub(v.lastSeen).Seconds() v.tokens += elapsed * rl.rate if v.tokens > rl.burst { v.tokens = rl.burst } v.lastSeen = now if v.tokens < 1 { return false } v.tokens-- return true } func (rl *RateLimiter) cleanup() { for { time.Sleep(5 * time.Minute) rl.mu.Lock() cutoff := time.Now().Add(-10 * time.Minute) for ip, v := range rl.visitors { if v.lastSeen.Before(cutoff) { delete(rl.visitors, ip) } } rl.mu.Unlock() } } func clientIP(r *http.Request) string { if fwd := r.Header.Get("X-Forwarded-For"); fwd != "" { return fwd } host, _, err := net.SplitHostPort(r.RemoteAddr) if err != nil { return r.RemoteAddr } return host } func RateLimit(limiter *RateLimiter, next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if !limiter.Allow(clientIP(r)) { http.Error(w, "Rate limit exceeded", http.StatusTooManyRequests) return } next(w, r) } }