Skip to content
boldbug1Public

About

a lightweight reverse proxy from scratch in Go

Resources

Stars

2 stars

Watchers

0 watching

Forks

Latest commit

 

History

16 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 

Repository files navigation

revprox

A lightweight, robust, and zero-dependency HTTP reverse proxy and load balancer implemented from scratch in Go using only standard library primitives (net/http, net/url, net, sync, time).

revprox demonstrates how core reverse proxy and load balancing mechanics—such as atomic round-robin routing, active background health checking, RFC-compliant hop-by-hop header filtering, X-Forwarded-* header handling, real-time SSE streaming, sync.Pool buffer recycling, and graceful shutdown—work under the hood without relying on black-box frameworks or httputil.ReverseProxy.


Architecture Overview

flowchart LR
    Client([Client Request]) --> Proxy["revprox (:3000)"]

    subgraph Pool ["ServerPool Load Balancer"]
        direction TB
        HC["Health Checker<br/>(Background Probe)"]
        RR{"Round-Robin<br/>Selector"}
        HC -.->|Syncs Health| RR
    end

    Proxy --> RR

    RR -->|Forward| B1["Backend 1 (:9001)<br/>Status: Online"]
    RR -.->|Bypass| B2["Backend 2 (:9002)<br/>Status: Offline"]
    RR -->|Forward| B3["Backend 3 (:9003)<br/>Status: Online"]
Loading

Features

  • Zero External Dependencies: Built entirely with Go's standard library.
  • Round-Robin Load Balancing:
    • Thread-safe, lock-free round-robin peer selection powered by sync/atomic.
    • Automatically skips offline backends and routes traffic exclusively to healthy nodes.
    • Returns 503 Service Unavailable if all backends are down.
  • Active Background Health Checking:
    • Periodically probes all configured upstreams using lightweight TCP handshakes (net.DialTimeout).
    • Thread-safe status updates using sync.RWMutex.
    • Immediate startup probe: Detects offline nodes before the very first client request arrives.
    • Smart logging: Only logs state changes (is now DOWN ❌ / is now healthy) to prevent console spam.
  • Hop-by-Hop Header Sanitization (RFC 7230 §6.1):
    • Dynamically extracts and removes headers specified inside the client's or upstream's Connection header before stripping Connection itself.
    • Removes default hop-by-hop headers:
      • Transfer-Encoding
      • Upgrade
      • Proxy-Authorization
      • Trailer
      • Te
      • Proxy-Authenticate
      • Keep-Alive
      • Connection
    • Sanitizes both outbound requests to the upstream server and incoming responses forwarded back to the client.
  • Header Forwarding (X-Forwarded-*):
    • X-Forwarded-For: Appends the client IP (extracted cleanly using net.SplitHostPort), preserving any existing proxy chains.
    • X-Forwarded-Host: Preserves the original Host sent by the client.
    • X-Forwarded-Proto: Indicates protocol scheme (http or https depending on client TLS state).
  • Streaming & SSE (Server-Sent Events) Support:
    • Immediate chunk flushing with http.Flusher for unbuffered event delivery.
    • Clears write deadlines via http.ResponseController.SetWriteDeadline(time.Time{}) so long-lived streams are not killed by server timeouts.
    • Early client disconnect detection (broken pipe) aborts upstream reading immediately.
    • Memory recycling: Uses sync.Pool (bufferPool) to recycle 32 KB byte buffers, minimizing garbage collection churn.
  • Graceful Shutdown:
    • Captures SIGINT (Ctrl+C) and SIGTERM via signal.NotifyContext.
    • Drains in-flight requests cleanly within a 10-second grace window using server.Shutdown(shutCtx).
  • Server & Transport Hardening:
    • Custom http.Transport with a 5-second ResponseHeaderTimeout.
    • HTTP Server timeouts configured to prevent slow-client resource exhaustion and Slowloris attacks:
      • ReadHeaderTimeout: 3 seconds
      • ReadTimeout: 10 seconds
      • WriteTimeout: 15 seconds
      • IdleTimeout: 60 seconds
  • Gateway Error Handling:
    • Distinguishes network timeouts (504 Gateway Timeout) from connection refusals (502 Bad Gateway).
  • Built-in Mock Backend:
    • Companion test backend in server/ with /api/ping, /api/echo, and /api/stream (SSE) endpoints.

Directory Structure

revprox/
├── main.go         # Core reverse proxy, load balancer, and health checker
├── go.mod          # Go module declaration (github.com/boldbug1/revprox)
├── server/         # Sample backend service for local testing
│   ├── main.go     # Backend test server with /api/ping, /api/echo, /api/stream
│   └── go.mod      # Backend module declaration (temp-server)
└── README.md       # Project documentation

Getting Started

Prerequisites

  • Go 1.22+ (tested with Go 1.26+)

1. Start the Mock Backend Server

Start the companion test backend on port 9001:

cd server
go run main.go

Output:

Starting hidden backend on http://127.0.0.1:9001

2. Start the Load Balancer & Reverse Proxy

In a separate terminal, launch revprox from the root directory:

go run main.go

Output:

Registered backend: http://127.0.0.1:9001/api/
Registered backend: http://127.0.0.1:9002/api/
Registered backend: http://127.0.0.1:9003/api/
[HealthCheck] status changed: http://127.0.0.1:9002/api/ is now DOWN ❌
[HealthCheck] status changed: http://127.0.0.1:9003/api/ is now DOWN ❌
proxy listening on :3000

Note

revprox automatically discovers that 9002 and 9003 are offline during startup and directs 100% of traffic to healthy node 9001.


Verification & Testing

1. Basic Ping Test

Send requests to revprox at http://localhost:3000/ping:

curl -i http://localhost:3000/ping

Expected Response:

HTTP/1.1 200 OK
Content-Type: application/json
Date: ...
Content-Length: 18

"{message: pong}"

2. Echo & Header Sanitization Test

Send custom hop-by-hop and client headers:

curl -i -X POST http://localhost:3000/echo \
  -H "Connection: close, X-Custom-Hop" \
  -H "X-Custom-Hop: should-be-stripped" \
  -H "Keep-Alive: timeout=10" \
  -d "hello reverse proxy"

Observe that:

  1. X-Custom-Hop, Keep-Alive, and Connection are stripped before reaching upstream.
  2. X-Forwarded-For, X-Forwarded-Host, and X-Forwarded-Proto are automatically attached.
  3. The upstream server's own Keep-Alive: timeout=5 header is stripped before sending the response back to your client.

3. Server-Sent Events (SSE) Streaming Test

Test real-time unbuffered event streaming through the proxy:

curl -N http://localhost:3000/stream

Observe that:

  1. Events stream immediately every 1 second without buffering delays.
  2. Terminating curl (Ctrl+C) immediately propagates cancellation upstream to terminate the backend stream goroutine.
  3. http.ResponseController clears the write deadline, preventing the 15-second WriteTimeout from killing the connection.

4. Load Balancing & Failover Test

  1. Start another backend instance on port 9002.
  2. Within 5 seconds, revprox logs:
    [HealthCheck] status changed: http://127.0.0.1:9002/api/ is now healthy
    
  3. Send multiple curl requests: revprox will alternate requests between :9001 and :9002.
  4. Kill the server on :9001: revprox flags it as DOWN ❌ and seamlessly routes all traffic to :9002 without downtime.

Configuration

Default configurations can be adjusted in main.go:

Variable / Setting Default Value Description
port :3000 Port revprox listens on
backendURLs 9001, 9002, 9003 List of upstream backend URLs
Health Check Interval 5 * time.Second Frequency of background health probes
Health Check Timeout 2 * time.Second Max wait time for TCP handshake per backend
ResponseHeaderTimeout 5 * time.Second Max duration waiting for upstream response headers
ReadHeaderTimeout 3 * time.Second Max time allowed to read client request headers
ReadTimeout 10 * time.Second Max time to read the entire request
WriteTimeout 15 * time.Second Max time to write standard response
IdleTimeout 60 * time.Second Max time to keep idle connections open
Shutdown Drain Timeout 10 * time.Second Maximum window to drain in-flight requests on exit

License

This project is licensed under the MIT License. Feel free to use, modify, and distribute.

About

a lightweight reverse proxy from scratch in Go

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages