53 lines
ratelimit/limiter.go
In-memory per-client rate limiter using a fixed time window.
// Package ratelimit provides per-client request rate limiting for HTTP services.
// Each client is identified by an opaque key such as a user ID, remote IP, or API token.
// The limiter uses a fixed time window; stale buckets for inactive clients accumulate
// and should be evicted by the caller via a periodic cleanup task in production.
package ratelimit
 
import (
	"sync"
	"time"
)
 
// Limiter enforces a maximum request count per client key within a fixed time window.
type Limiter struct {
	mu      sync.Mutex
	limit   int
	window  time.Duration
	buckets map[string]*bucket
}
 
type bucket struct {
	count     int
	windowEnd time.Time
}
 
// New returns a Limiter that allows at most limit requests per window duration per key.
func New(limit int, window time.Duration) *Limiter {
	return &Limiter{
		limit:   limit,
		window:  window,
		buckets: make(map[string]*bucket),
	}
}
 
// Allow reports whether the client identified by key is permitted to make a request.
// It increments the request count for the current window and returns false
// once the count reaches or exceeds the configured limit.
func (l *Limiter) Allow(key string) bool {
	l.mu.Lock()
	defer l.mu.Unlock()
 
	now := time.Now()
	b, ok := l.buckets[key]
	if !ok || now.After(b.windowEnd) {
		l.buckets[key] = &bucket{count: 1, windowEnd: now.Add(l.window)}
		return true
	}
 
	b.count++
	if b.count > l.limit {
		return false
	}
	return true
}