go 69 lines · 8 steps

A per-IP rate limiter middleware in Gin

A token-bucket limiter tracked per client IP, wired into Gin as middleware that rejects requests over the allowed rate.

Explained by highlit
1package middleware
2 
3import (
4 "net/http"
5 "strconv"
6 "sync"
7 "time"
8 
9 "github.com/gin-gonic/gin"
10 "golang.org/x/time/rate"
11)
12 
13type visitor struct {
14 limiter *rate.Limiter
15 lastSeen time.Time
16}
17 
18type RateLimiter struct {
19 mu sync.Mutex
20 visitors map[string]*visitor
21 rps rate.Limit
22 burst int
23}
24 
25func NewRateLimiter(rps float64, burst int) *RateLimiter {
26 rl := &RateLimiter{
27 visitors: make(map[string]*visitor),
28 rps: rate.Limit(rps),
29 burst: burst,
30 }
31 return rl
32}
33 
34func (rl *RateLimiter) limiterFor(key string) *rate.Limiter {
35 rl.mu.Lock()
36 defer rl.mu.Unlock()
37 
38 v, ok := rl.visitors[key]
39 if !ok {
40 v = &visitor{limiter: rate.NewLimiter(rl.rps, rl.burst)}
41 rl.visitors[key] = v
42 }
43 v.lastSeen = time.Now()
44 return v.limiter
45}
46 
47func (rl *RateLimiter) Middleware() gin.HandlerFunc {
48 return func(c *gin.Context) {
49 limiter := rl.limiterFor(c.ClientIP())
50 reservation := limiter.Reserve()
51 
52 if !reservation.OK() {
53 c.AbortWithStatus(http.StatusInternalServerError)
54 return
55 }
56 
57 if delay := reservation.Delay(); delay > 0 {
58 reservation.Cancel()
59 retryAfter := int(delay.Seconds() + 0.999)
60 c.Header("Retry-After", strconv.Itoa(retryAfter))
61 c.AbortWithStatusJSON(http.StatusTooManyRequests, gin.H{
62 "error": "rate limit exceeded",
63 })
64 return
65 }
66 
67 c.Next()
68 }
69}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1A token-bucket limiter smooths request rates while allowing short bursts up to a configured ceiling.
  2. 2Keeping a map of limiters keyed by client identity gives each visitor an independent quota.
  3. 3Reserving instead of blocking lets the middleware reject over-limit requests immediately with a Retry-After hint.

Related explainers

Share this explainer

Here's the card — post it anywhere.

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