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) prune(key string, now time.Time) []time.Time { 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) == 0 { delete(l.hits, key) } else { l.hits[key] = kept } if len(l.hits) > 10000 { for k, v := range l.hits { if !v[len(v)-1].After(cutoff) { delete(l.hits, k) } } } return kept } func (l *limiter) blocked(key string) bool { l.mu.Lock() defer l.mu.Unlock() return len(l.prune(key, time.Now())) >= l.max } func (l *limiter) fail(key string) { l.mu.Lock() defer l.mu.Unlock() now := time.Now() l.hits[key] = append(l.prune(key, now), now) } 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 }