go 59 lines · 8 steps

Per-IP write rate limiting in Gin

A Gin middleware that throttles write requests per client IP using token-bucket limiters.

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 mu sync.Mutex
14 visitors map[string]*rate.Limiter
15 r rate.Limit
16 b int
17}
18 
19func newIPLimiter(r rate.Limit, b int) *ipLimiter {
20 l := &ipLimiter{visitors: make(map[string]*rate.Limiter), r: r, b: b}
21 go l.cleanup()
22 return l
23}
24 
25func (l *ipLimiter) get(ip string) *rate.Limiter {
26 l.mu.Lock()
27 defer l.mu.Unlock()
28 lim, ok := l.visitors[ip]
29 if !ok {
30 lim = rate.NewLimiter(l.r, l.b)
31 l.visitors[ip] = lim
32 }
33 return lim
34}
35 
36func (l *ipLimiter) cleanup() {
37 for range time.Tick(3 * time.Minute) {
38 l.mu.Lock()
39 l.visitors = make(map[string]*rate.Limiter)
40 l.mu.Unlock()
41 }
42}
43 
44func RateLimitWrites(perMinute int, burst int) gin.HandlerFunc {
45 limiter := newIPLimiter(rate.Every(time.Minute/time.Duration(perMinute)), burst)
46 return func(c *gin.Context) {
47 switch c.Request.Method {
48 case http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete:
49 if !limiter.get(c.ClientIP()).Allow() {
50 c.Header("Retry-After", "60")
51 c.AbortWithStatusJSON(http.StatusTooManyRequests, gin.H{
52 "error": "too many write requests, slow down",
53 })
54 return
55 }
56 }
57 c.Next()
58 }
59}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1A map of per-IP limiters gives each client its own token bucket instead of one shared quota.
  2. 2Guarding shared map access with a mutex keeps concurrent request handlers from racing.
  3. 3Limiting only mutating HTTP methods protects writes while leaving reads untouched.

Related explainers

Share this explainer

Here's the card — post it anywhere.

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