Building Proxify: A Reverse Proxy in Go
A reverse proxy sits between clients and one or more upstream services. Instead of clients communicating directly with your application, every request first passes through the proxy before being forwarded to an upstream. Mature reverse proxies such as Nginx, Envoy, and HAProxy do much more than simply forward requests. They perform tasks such as load balancing, health checks, rate limiting, metrics collection, and much more. I wanted to better understand how some of these concepts work in practice, so I built a reverse proxy in Go. Along the way I implemented request forwarding, multiple load-balancing strategies, health checks, circuit breakers, rate limiting, request logging, metrics, and graceful shutdown. If you'd like to explore Proxify as we go, you can find the project here: https://github.com/Rahmannugar/proxify Table of Contents Request Lifecycle Project Structure Configuration Reverse Proxy Load Balancing Health Checks Circuit Breakers Middleware Graceful Shutdown Running Proxify with Docker 1. Request Lifecycle At a high level, every request follows the same path through the reverse proxy. A client sends an HTTP request to Proxify instead of communicating directly with an upstream service. Proxify receives the request, selects a healthy upstream using the configured load-balancing strategy, forwards the request, waits for the upstream's response, and finally returns that response to the client. Client │ ▼ +---------------+ | Proxify | +---------------+ │ Select Healthy Upstream │ ┌───────┴────────┐ ▼ ▼ Upstream A Upstream B │ ▼ HTTP Response │ ▼ Client Although the overall flow is straightforward, every step introduces additional considerations. Which upstream should receive the next request? What happens when an upstream becomes unhealthy? How can requests be distributed efficiently across multiple upstreams? How do we prevent a failing upstream from continuing to receive traffic? The remainder of this article answers those questions by gradually buildin