Skip to content

Repository files navigation

Go Reverse Proxy

A reverse proxy built from scratch using Go's standard library. Features round-robin load balancing, in-memory response caching, per-IP rate limiting, and background health checks.

Architecture

Client → :8080 Proxy → [Rate Limiter] → [Cache] → [Load Balancer] → Backend :8081
                                                                   → Backend :8082

Request flow:

  1. Rate limit check (per-IP, fixed window) — rejects with 429 if exceeded
  2. Cache lookup (GET requests only) — returns cached response on hit
  3. Round-robin backend selection — skips unhealthy backends
  4. Forward request via httputil.ReverseProxy
  5. Cache response (GET 200 only) with configurable TTL

Quick Start

Docker (recommended)

docker compose up --build

This starts 2 backend instances and 1 proxy on :8080.

Local

# Terminal 1 — Backend on :8081
go run cmd/backend/main.go

# Terminal 2 — Backend on :8082 (optional)
PORT=8082 go run cmd/backend/main.go

# Terminal 3 — Proxy on :8080
go run cmd/proxy/main.go

Test

curl http://localhost:8080/
curl http://localhost:8080/about
curl -X POST -H "Content-Type: application/json" -d '{"name":"test"}' http://localhost:8080/submit
curl http://localhost:8080/list

Running Tests

go test ./... -race

25 tests covering all components with race detection:

Component Tests
Rate Limiter 8 (concurrency, window reset, per-IP isolation, boundary)
Cache 6 (TTL expiry, cleanup, overwrite)
Load Balancer 7 (round-robin, unhealthy skip, fallback, concurrency)
Health Checker 3 (down on failure, up on success, down on 5xx)
Proxy Handler 4 (forwarding, caching, rate limiting, headers)
Config 3 (valid load, missing file, invalid YAML)

Configuration

All settings in config.yaml:

proxy:
  port: 8080
backends:
  - url: http://localhost:8081
  - url: http://localhost:8082
cache:
  ttl_seconds: 30
rate_limit:
  requests: 5
  window_seconds: 10
health_check:
  interval_seconds: 10
  timeout_seconds: 2
  path: /

Project Structure

cmd/
  proxy/main.go              # Proxy entry point
  backend/main.go            # Backend entry point
internal/
  proxy/
    proxy.go                 # Core handler (wires all components)
    balancer.go              # Round-robin load balancer with atomic health flags
    health.go                # Background health checker (goroutine)
    cache.go                 # In-memory cache with TTL + background cleanup
    limiter.go               # Per-IP fixed-window rate limiter
    middleware.go            # Request logging middleware
    *_test.go                # Tests for each component
  config/
    config.go                # YAML config parser
    config_test.go
  backend/
    server.go                # Backend HTTP server
    handlers.go              # Route handlers (/, /about, /submit, /list)
config.yaml                  # Local config
config.docker.yaml           # Docker config (uses container hostnames)
docker-compose.yml           # Runs proxy + 2 backends
Dockerfile.proxy
Dockerfile.backend

Design Decisions

  • sync/atomic.Bool for backend health — lock-free reads during load balancing
  • httptest.NewRecorder to intercept responses for caching before writing to client
  • Background goroutines for health checks, cache cleanup, and rate limit resets
  • Multi-stage Docker builds — final images are ~15MB (Alpine + static binary)
  • No external dependencies beyond gopkg.in/yaml.v3 for config parsing

About

Reverse proxy in Go with round-robin load balancing, health checks, in-memory caching, and per-IP rate limiting .

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages