go 72 lines · 7 steps

Per-IP rate limiting middleware in Gin

A Gin middleware that gives each client IP its own token-bucket limiter and evicts stale ones in the background.

Explained by highlit
1package middleware
2 
3import (
4 "net/http"
5 "sync"
6 "time"
7 
8 "github.com/gin-gonic/gin"
9 "golang.org/x/time/rate"
10)
11 
12type ipLimiter struct {
13 limiter *rate.Limiter
14 lastSeen time.Time
15}
16 
17type rateLimiterStore struct {
18 mu sync.Mutex
19 clients map[string]*ipLimiter
20 r rate.Limit
21 burst int
22}
23 
24func newStore(r rate.Limit, burst int) *rateLimiterStore {
25 s := &rateLimiterStore{
26 clients: make(map[string]*ipLimiter),
27 r: r,
28 burst: burst,
29 }
30 go s.cleanup()
31 return s
32}
33 
34func (s *rateLimiterStore) get(ip string) *rate.Limiter {
35 s.mu.Lock()
36 defer s.mu.Unlock()
37 
38 c, ok := s.clients[ip]
39 if !ok {
40 c = &ipLimiter{limiter: rate.NewLimiter(s.r, s.burst)}
41 s.clients[ip] = c
42 }
43 c.lastSeen = time.Now()
44 return c.limiter
45}
46 
47func (s *rateLimiterStore) cleanup() {
48 for range time.Tick(time.Minute) {
49 s.mu.Lock()
50 for ip, c := range s.clients {
51 if time.Since(c.lastSeen) > 3*time.Minute {
52 delete(s.clients, ip)
53 }
54 }
55 s.mu.Unlock()
56 }
57}
58 
59func PerIPRateLimit(rps float64, burst int) gin.HandlerFunc {
60 store := newStore(rate.Limit(rps), burst)
61 
62 return func(c *gin.Context) {
63 if !store.get(c.ClientIP()).Allow() {
64 c.Header("Retry-After", "1")
65 c.AbortWithStatusJSON(http.StatusTooManyRequests, gin.H{
66 "error": "rate limit exceeded",
67 })
68 return
69 }
70 c.Next()
71 }
72}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1A map of per-key limiters lets you enforce fairness for each client instead of one global bucket.
  2. 2Any long-lived map keyed by unbounded input needs a cleanup routine to avoid a slow memory leak.
  3. 3A single mutex around shared map access keeps concurrent request handlers from racing.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Per-IP rate limiting middleware in Gin — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code