Reverse Proxy / Load Balancer
Source: Custom
Topics: httputil.ReverseProxy, round-robin, health checks, atomics
Problem
Implement a LoadBalancer that distributes requests across backend servers using round-robin,
skipping unhealthy backends.
Requirements:
New(targets []*url.URL) *LoadBalancer — wrap each target as a Backend, initially healthy.
Next() (*Backend, error) — return the next healthy backend in round-robin order; ErrNoHealthyBackends if none are healthy.
ServeHTTP — proxy the request to Next()'s backend using httputil.NewSingleHostReverseProxy; respond 502 if none are healthy.
SetHealthy(target *url.URL, healthy bool) — manually mark a backend's health (used by tests and HealthCheck).
HealthCheck(ctx context.Context, interval time.Duration, path string) — periodically GET path on each backend; mark healthy on 200, unhealthy otherwise. Runs until ctx is cancelled.
Type:
var ErrNoHealthyBackends = errors.New("no healthy backends")
type Backend struct {
URL *url.URL
// healthy state, unexported
}
type LoadBalancer struct { ... }
func New(targets []*url.URL) *LoadBalancer
func (lb *LoadBalancer) Next() (*Backend, error)
func (lb *LoadBalancer) ServeHTTP(w http.ResponseWriter, r *http.Request)
func (lb *LoadBalancer) SetHealthy(target *url.URL, healthy bool)
func (lb *LoadBalancer) HealthCheck(ctx context.Context, interval time.Duration, path string)
Key concepts
- atomic.Bool / atomic.Uint64: lock-free health flags and round-robin counter.
- httputil.NewSingleHostReverseProxy: rewrites the request URL/Host and proxies it — the building block real gateways use.
- Round-robin with skip: advance the counter modulo backend count, but loop at most
len(backends) times to avoid an infinite loop when all are unhealthy.
- Health check loop:
time.NewTicker + select on ctx.Done(), same pattern as the rate limiter's refill goroutine.
Run
go test -v -race ./challenges/networking/reverse-proxy/
Sign in to submit your solution.