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
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1A map of per-IP limiters gives each client its own token bucket instead of one shared quota.
- 2Guarding shared map access with a mutex keeps concurrent request handlers from racing.
- 3Limiting only mutating HTTP methods protects writes while leaving reads untouched.
Related explainers
javascript
const express = require('express'); const { createProxyMiddleware, fixRequestBody } = require('http-proxy-middleware'); const router = express.Router();
Building an API gateway proxy in Express
reverse-proxy
middleware
api-gateway
Intermediate
6 steps
python
import asyncio from dataclasses import dataclass import aiohttp
Bounded-concurrency HTTP fetching with asyncio
async
concurrency
semaphore
Intermediate
8 steps
rust
use std::sync::Arc; use axum::{ extract::{Multipart, State}, http::StatusCode,
Throttling file uploads in Axum with a Semaphore
concurrency
backpressure
multipart
Advanced
8 steps
go
package middleware import ( "net/http"
A content-type guard middleware in Gin
middleware
closures
http
Intermediate
7 steps
go
package api import ( "errors"
Turning Gin validation errors into JSON
validation
error-handling
http-handlers
Intermediate
9 steps
javascript
import { NextResponse } from 'next/server'; import { Redis } from '@upstash/redis'; const redis = Redis.fromEnv();
Sliding-window rate limiting in a Next.js route
rate-limiting
redis
sorted-set
Advanced
8 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/per-ip-write-rate-limiting-in-gin-explained-go-e657/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.