Skip to content

noahkawaguchi/reverse-proxy

Repository files navigation

English | 日本語

reverse-proxy

An HTTP reverse proxy written in Rust. Supports multiple backends per route, two load balancing algorithms, periodic health checks, and graceful shutdown.

Table of Contents

  1. Features
  2. Design
  3. Configuration
  4. Getting Started
  5. Testing
  6. CI

Features

  • Load balancing algorithms: Round robin or least connections, configurable per route.
  • Health checks: Periodic probing of backends, with unhealthy backends skipped automatically and recovered backends returned to the pool.
  • Longest-prefix routing: Support for multiple routes, each with independent backends and balancing strategy.
  • Graceful shutdown: Drains in-flight requests with a configurable timeout before exiting.
  • HTTP correctness: Strips hop-by-hop headers, appends to X-Forwarded-For chains, and rewrites Host headers.

Design

Connection tracking: The Backend struct wraps an atomic health flag and an atomic active connection counter. BackendGuard is an RAII guard that increments the counter on acquisition and decrements it on drop, so the least connections algorithm always sees an accurate count, even if a request completes unsuccessfully.

Load balancer abstraction: LoadBalancer is a trait implemented by both RoundRobin and LeastConnections. New algorithms can be added without touching the proxying or routing logic.

Health checking: Each route runs an independent Tokio task that polls backends on a configurable interval. Health state is written atomically, so the proxy never needs a lock to check whether a backend is available.

Graceful shutdown: SIGINT or SIGTERM (or on non-Unix, Ctrl+C only) sets a broadcast channel flag that the server loop checks before accepting new connections. A tokio::time::timeout wraps the drain phase. If in-flight requests exceed the configured timeout, the proxy logs a warning and exits anyway instead of waiting indefinitely.

Configuration

The proxy is configured with a TOML file. By default, it looks for reverse-proxy.toml in the working directory, which can be overridden with the REVERSE_PROXY_CONFIG_PATH environment variable.

listen_addr = "127.0.0.1:3000" # Address for the reverse proxy to listen on
shutdown_timeout = 10 # Seconds to wait when draining in-flight requests during shutdown

[[routes]]
prefix = "/"
backend_addrs = ["127.0.0.1:8001", "127.0.0.1:8002", "127.0.0.1:8003"]
health_check = { path = "/health", interval_secs = 10 }
balancing_algorithm = "round_robin"

[[routes]]
prefix = "/api"
backend_addrs = ["127.0.0.1:9001"]
health_check = { path = "/ping", interval_secs = 5 }
balancing_algorithm = "least_connections"

Routes match on path prefix, with the longest matching prefix winning. Each route must have at least one backend on startup. Requests to routes where all backends are unhealthy receive 503 Service Unavailable. Requests that don't match any route receive 404 Not Found.

Getting Started

Building and running

Rust can be installed as described here.

cargo run

Log level can be changed with the RUST_LOG environment variable (e.g. RUST_LOG=debug cargo run). The default level is info.

Quick demo

Requires Python and Just.

just

This starts a Python http.server on port 8000, runs the proxy on port 3000, and makes a test request through it.

Testing

cargo test

Tests cover route resolution, header handling, health detection and recovery, load balancing algorithms, graceful shutdown, and the shutdown timeout.

CI

Tests, linting, format checking, and spell checking run via GitHub Actions on pushes and pull requests to main (as defined in .github/workflows/ci.yml).

About

HTTP reverse proxy with round robin, least connections, health checks, and more

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors