A lightweight, high-performance HTTP reverse proxy in Go with pluggable rate limiting strategies. Zero external dependencies beyond gopkg.in/yaml.v3.
- Reverse proxy powered by
net/http/httputil.ReverseProxy - Three rate limiting strategies behind a common
RateLimiterinterface:- Token Bucket — configurable rate + burst, lazy refill
- Sliding Window — sub-window based counter with automatic eviction
- Fixed Window — simple counter with periodic reset
- Per-endpoint + per-client limiting — key = client IP + request path
- YAML configuration — define per-endpoint rules and defaults
- HTTP 429 with
Retry-Afterheader and JSON error body - pprof endpoints at
/debug/pprof/for production profiling - Stdlib only — no external dependencies except YAML parsing
┌───────────────────────────────────────────────────┐
│ GoRateGuard │
│ │
HTTP Request ──► │ ┌─────────────┐ ┌──────────────┐ ┌──────┐ │
│ │ Middleware │───►│ Rate Limiter │ │Proxy │ │
│ │ (IP + Path)│ │ │ │ │ │
│ └──────┬──────┘ │ ┌──────────┐ │ │ ──► │──► Backend
│ │ │ │ Token │ │ │ │ │
│ 429 ◄┤ │ │ Bucket │ │ └──────┘ │
│ Retry- │ │ ├──────────┤ │ │
│ After │ │ │ Sliding │ │ │
│ │ │ │ Window │ │ │
│ │ │ ├──────────┤ │ │
│ │ │ │ Fixed │ │ │
│ │ │ │ Window │ │ │
│ │ │ └──────────┘ │ │
│ │ └──────────────┘ │
└─────────┼───────────────────────────────────────┘
│
HTTP 429 + JSON body
cmd/gorateguard/main.go → entry point, loads config, starts server
internal/
├── config/config.go → YAML config parsing & validation
├── proxy/proxy.go → reverse proxy handler
├── limiter/
│ ├── limiter.go → RateLimiter interface
│ ├── token_bucket.go → token bucket implementation
│ ├── sliding_window.go → sliding window counter
│ ├── fixed_window.go → fixed window counter
│ ├── limiter_test.go → unit tests
│ └── limiter_bench_test.go → benchmarks
└── middleware/middleware.go → rate limit middleware
config.yaml → sample configuration
go build -o gorateguard ./cmd/gorateguard# Start a backend server (e.g., a simple echo server on port 9090)
# Then start GoRateGuard:
./gorateguard -config config.yamlGoRateGuard will listen on :8080 (default) and proxy requests to http://localhost:9090.
# Send requests to a rate-limited endpoint
for i in $(seq 1 25); do
curl -s -o /dev/null -w "%{http_code}\n" http://localhost:8080/api/submit
doneWhen rate limited, you'll receive:
HTTP/1.1 429 Too Many Requests
Retry-After: 1
Content-Type: application/json
{"error": "rate limit exceeded", "retry_after": 0.8}server:
port: 8080
target: "http://localhost:9090"
default_strategy: "token_bucket"
endpoints:
- path: "/api/search"
strategy: "sliding_window"
requests: 100
window: "60s"
- path: "/api/submit"
strategy: "token_bucket"
rate: 10
burst: 20
- path: "/api/status"
strategy: "fixed_window"
requests: 1000
window: "60s"
default_limit:
requests: 50
window: "60s"
rate: 5
burst: 10| Field | Description |
|---|---|
server.port |
Port to listen on |
server.target |
Backend URL to proxy to |
default_strategy |
Fallback strategy for unmatched paths |
endpoints[].path |
URL path prefix to match |
endpoints[].strategy |
token_bucket, sliding_window, or fixed_window |
endpoints[].requests |
Max requests per window (sliding/fixed) |
endpoints[].window |
Window duration, e.g. 60s, 5m |
endpoints[].rate |
Tokens per second (token bucket) |
endpoints[].burst |
Max burst capacity (token bucket) |
Allows bursts up to the configured capacity, then throttles to a steady rate. Tokens refill lazily on each request — no background goroutines.
Best for: APIs where occasional bursts are acceptable.
Divides the window into 10 sub-windows and evicts expired ones on each request. Provides smoother rate limiting than fixed windows.
Best for: APIs requiring consistent throughput distribution.
Simple counter that resets at the start of each window. Lowest overhead, but allows bursts at window boundaries.
Best for: High-throughput endpoints where simplicity matters.
pprof endpoints are available at /debug/pprof/:
# CPU profile (30 seconds)
go tool pprof http://localhost:8080/debug/pprof/profile?seconds=30
# Heap profile
go tool pprof http://localhost:8080/debug/pprof/heap
# Goroutine dump
curl http://localhost:8080/debug/pprof/goroutine?debug=2# Run all unit tests
go test -v ./...
# Run with race detector
go test -race ./...Run on Apple M2, Go 1.26, macOS (arm64):
goos: darwin
goarch: arm64
cpu: Apple M2
BenchmarkTokenBucket-8 14,650,060 72.35 ns/op 0 B/op 0 allocs/op
BenchmarkTokenBucket_Parallel-8 6,178,888 202.9 ns/op 8 B/op 1 allocs/op
BenchmarkSlidingWindow-8 18,244,938 100.5 ns/op 0 B/op 0 allocs/op
BenchmarkSlidingWindow_Parallel-8 4,444,251 230.6 ns/op 8 B/op 1 allocs/op
BenchmarkFixedWindow-8 24,105,178 50.44 ns/op 0 B/op 0 allocs/op
BenchmarkFixedWindow_Parallel-8 6,568,222 184.7 ns/op 8 B/op 1 allocs/op
All strategies sustain millions of ops/sec with zero allocations in serial mode.
# Reproduce
go test -bench=. -benchmem ./internal/limiter/module github.com/karthikSunkari/GoRateGuard
go 1.21
Dependencies: gopkg.in/yaml.v3 (YAML parsing only)
MIT