Rate limiting
Hub › Golang › Advanced › Rate limiting
Goal
Add a per-IP token bucket rate limiter. Clients exceeding 10 requests/second get a 429 response.
Prerequisites
Token bucket algorithm
A token bucket starts with N tokens. Each request consumes one token. Tokens refill at a fixed rate. When the bucket is empty, the request is rejected.
This allows short bursts (up to N requests) while enforcing a sustained rate limit.
Rate limiter middleware
Create ratelimit.go:
go
package main
import (
"log"
"net/http"
"sync"
"time"
)
type bucket struct {
tokens int
lastRefill time.Time
max int
interval time.Duration
}
type rateLimiter struct {
mu sync.Mutex
buckets map[string]*bucket
max int
rate time.Duration
}
func newRateLimiter(max int, interval time.Duration) *rateLimiter {
return &rateLimiter{
buckets: make(map[string]*bucket),
max: max,
rate: interval,
}
}
func (rl *rateLimiter) allow(ip string) bool {
rl.mu.Lock()
defer rl.mu.Unlock()
b, ok := rl.buckets[ip]
if !ok {
rl.buckets[ip] = &bucket{
tokens: rl.max,
max: rl.max,
interval: rl.rate,
lastRefill: time.Now(),
}
b = rl.buckets[ip]
}
// Refill
elapsed := time.Since(b.lastRefill)
b.tokens += int(elapsed / b.interval)
if b.tokens > b.max {
b.tokens = b.max
}
b.lastRefill = time.Now()
if b.tokens == 0 {
return false
}
b.tokens--
return true
}Wire it as middleware
Add the rate limiter middleware:
go
func rateLimit(next http.Handler) http.Handler {
rl := newRateLimiter(10, time.Second) // 10 req/s
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ip := r.RemoteAddr
if !rl.allow(ip) {
w.Header().Set("Retry-After", "1")
http.Error(w, "Too Many Requests", http.StatusTooManyRequests)
return
}
next.ServeHTTP(w, r)
})
}Update middleware chain
In main.go, add rate limiting after recovery but before routing:
go
handler := recovery(rateLimit(logging(requestID(mux))))Now every endpoint is rate-limited regardless of auth status. The /login endpoint is also protected against brute force.
Checkpoint
bash
go run .
# Rapid-fire requests:
for i in $(seq 1 15); do
curl -s -o /dev/null -w "%{http_code}\n" http://localhost:8080/items
done
# First ~10 return 200, then 429Next: Docker multistage — containerize the API with a production Dockerfile.