39. Terminal project: a rate-limited reverse proxy (mini API gateway)
Goal: combine net/http/httputil.ReverseProxy, the middleware chain
from Chapter 24, and rate limiting from Chapter 29 into a small gateway —
directly in the spirit of the networking/security field guides' WAF and
API-protection material.
package main
import (
"log"
"net/http"
"net/http/httputil"
"net/url"
"sync"
"golang.org/x/time/rate"
)
type ipLimiters struct {
mu sync.Mutex
limiters map[string]*rate.Limiter
}
func newIPLimiters() *ipLimiters {
return &ipLimiters{limiters: map[string]*rate.Limiter{}}
}
func (l *ipLimiters) get(ip string) *rate.Limiter {
l.mu.Lock()
defer l.mu.Unlock()
lim, ok := l.limiters[ip]
if !ok {
lim = rate.NewLimiter(rate.Limit(5), 10) // 5 req/s, burst 10, per IP
l.limiters[ip] = lim
}
return lim
}
func withRateLimit(limiters *ipLimiters, next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ip := clientIP(r)
if !limiters.get(ip).Allow() {
http.Error(w, "rate limit exceeded", http.StatusTooManyRequests)
return
}
next.ServeHTTP(w, r)
})
}
func clientIP(r *http.Request) string {
if fwd := r.Header.Get("X-Forwarded-For"); fwd != "" {
return fwd // production: parse + validate against a trusted proxy list
}
return r.RemoteAddr
}
func main() {
target, _ := url.Parse("http://localhost:8080") // your upstream service
proxy := httputil.NewSingleHostReverseProxy(target)
limiters := newIPLimiters()
handler := withRateLimit(limiters, proxy)
log.Println("gateway listening on :9443, proxying to", target)
log.Fatal(http.ListenAndServe(":9443", handler))
}
httputil.ReverseProxy rewrites the request's Host/URL, forwards it to
target, and streams the response back — the rate limiter middleware
runs first and short-circuits with 429 Too Many Requests before the
upstream ever sees an over-quota request. Chapter 49 extends this exact
shape into a fuller WAF-style middleware with IP blocklists and request
inspection.
Applied in the series: the HTTPS guide's reverse proxy chapter covers Rewrite vs Director and X-Forwarded-For trust, and reproduces request smuggling's root cause.