A high-performance reverse proxy with token bucket rate limiting built in Go using the Gin framework.
- Path-Based Routing: Route requests to different upstream backends based on URL path prefixes
- Token Bucket Rate Limiting: Per-client IP rate limiting with configurable burst capacity
- Longest Prefix Matching: Intelligent route matching for nested paths
- Graceful Error Handling: Proper HTTP status codes and error messages
- Concurrent Safe: Thread-safe rate limiting with efficient locking
- Easy Configuration: YAML-based configuration
go build -o bin/proxy ./cmd/proxy/./bin/proxyThe proxy will start on port 8080 (or the port specified in configs/config.yaml).
Edit configs/config.yaml:
server:
port: 8080
rate_limit:
requests_per_second: 5 # Token refill rate
burst: 10 # Maximum burst capacity
routes:
- path: "/api"
target: "https://backend.com"
- path: "/auth"
target: "https://auth.service.com"port: Server port (default: 8080)
requests_per_second: Number of tokens added per second (request rate)burst: Maximum number of tokens in the bucket (burst capacity)
path: URL path prefix to match (e.g.,/apimatches/api/users,/api/v1/data, etc.)target: Upstream URL to proxy requests to
Each client IP gets its own token bucket:
- Initial State: Bucket starts with
bursttokens - Token Refill: Tokens are added at
requests_per_secondrate - Request Handling: Each request consumes 1 token
- Rate Limiting: If no tokens available, request is rejected with HTTP 429
Example: With requests_per_second: 5 and burst: 10:
- Client can make 10 requests immediately (burst)
- After burst, client can make 5 requests per second
- Tokens refill continuously over time
Routes are matched using longest prefix matching:
routes:
- path: "/api"
target: "http://localhost:8000"
- path: "/api/v2"
target: "http://localhost:9000"- Request to
/api/v2/users→ routes tohttp://localhost:9000 - Request to
/api/users→ routes tohttp://localhost:8000 - Request to
/unknown→ returns 404
curl http://localhost:8080/pingResponse:
{
"message": "pong",
"status": "healthy"
}All other requests are proxied based on configured routes:
curl http://localhost:8080/api/users
# Proxied to: https://backend.com/api/usersWhen rate limit is exceeded:
curl http://localhost:8080/api/usersResponse (HTTP 429):
{
"error": "rate limit exceeded",
"message": "too many requests, please try again later"
}Headers:
Retry-After: 1(retry after 1 second)
Run all tests:
go test ./...Run tests with coverage:
go test -cover ./...Run specific package tests:
go test -v ./internal/ratelimit/
go test -v ./internal/proxy/
go test -v ./internal/middleware/gothrottle/
├── cmd/
│ └── proxy/
│ └── main.go # Application entry point
├── configs/
│ └── config.yaml # Configuration file
├── internal/
│ ├── config/
│ │ ├── config.go # Configuration structs
│ │ ├── loader.go # YAML loader
│ │ └── loader_test.go # Config tests
│ ├── middleware/
│ │ ├── logging.go # Request logging
│ │ ├── ratelimit.go # Rate limit middleware
│ │ └── ratelimit_test.go # Middleware tests
│ ├── proxy/
│ │ ├── balancer.go # Load balancer (round-robin)
│ │ ├── proxy.go # Reverse proxy handler
│ │ └── proxy_test.go # Proxy tests
│ └── ratelimit/
│ ├── limiter.go # Token bucket implementation
│ ├── limiter_test.go # Limiter tests
│ ├── storage.go # Per-client storage
│ └── storage_test.go # Storage tests
├── go.mod
├── go.sum
└── README.md
# Make rapid requests to test rate limiting
for i in {1..15}; do
echo "Request $i:"
curl -s http://localhost:8080/api/test | jq
doneFirst 10 requests succeed (burst capacity), then rate limited.
# Client 1 - exhaust rate limit
for i in {1..15}; do
curl -s http://localhost:8080/api/test
done
# Client 2 - still allowed (separate bucket)
curl -s -H "X-Forwarded-For: 192.168.1.2" http://localhost:8080/api/testEach client IP has its own rate limit bucket.
Create a custom config for local testing:
server:
port: 3000
rate_limit:
requests_per_second: 100
burst: 200
routes:
- path: "/api"
target: "http://localhost:8000"
- path: "/static"
target: "http://localhost:8080"- Concurrent Safe: Uses
sync.RWMutexfor efficient read/write locking - Memory Efficient: Only stores buckets for active clients
- No External Dependencies: In-memory storage (extensible to Redis)
- Efficient Matching: O(n) route matching where n is number of routes
- Redis backend for distributed rate limiting
- Per-route rate limits (different limits for different paths)
- Health checks for upstream servers
- Metrics and monitoring (Prometheus)
- Circuit breaker pattern
- Request/response transformation
- Authentication and authorization
- WebSocket support
MIT
Contributions are welcome! Please feel free to submit a Pull Request.