package main import ( "net" "net/http" "strings" "sync" "time" ) type limiter struct { mu sync.Mutex hits map[string][]time.Time max int window time.Duration } func newLimiter(max int, window time.Duration) *limiter { return &limiter{hits: map[string][]time.Time{}, max: max, window: window} } func (l *limiter) allow(key string) bool { l.mu.Lock() defer l.mu.Unlock() now := time.Now() cutoff := now.Add(-l.window) kept := l.hits[key][:0] for _, t := range l.hits[key] { if t.After(cutoff) { kept = append(kept, t) } } if len(kept) >= l.max { l.hits[key] = kept return false } l.hits[key] = append(kept, now) if len(l.hits) > 10000 { for k, v := range l.hits { if len(v) == 0 || !v[len(v)-1].After(cutoff) { delete(l.hits, k) } } } return true } func clientIP(r *http.Request) string { if xff := r.Header.Get("X-Forwarded-For"); xff != "" { first, _, _ := strings.Cut(xff, ",") return strings.TrimSpace(first) } host, _, err := net.SplitHostPort(r.RemoteAddr) if err != nil { return r.RemoteAddr } return host }