Small HTTP reverse-proxy load balancer. Round-robin or least-connections, active health checks, retry on backend failure, and a JSON status endpoint. Standard library only.
./mini-lb \
--listen :8080 \
--backends http://api-a:9000,http://api-b:9000,http://api-c:9000 \
--strategy least-connections \
--health-path /healthz \
--max-retries 2 \
--verbose$ curl -s localhost:8080/_lb/status | jq .
{
"strategy": "least-connections",
"backends": [
{ "url": "http://api-a:9000", "alive": true, "active_conns": 3, "total_served": 1204, "total_errored": 2 },
{ "url": "http://api-b:9000", "alive": false, "active_conns": 0, "total_served": 812, "total_errored": 17 }
]
}Retrying a failed request only works if nothing has been sent to the client yet. If the upstream dies after the response has already started streaming, you can't just switch backends — you'd write a second set of headers on top of a half-sent body.
So each proxy call is wrapped in a writer that records whether
WriteHeader has fired. Errors before that point are retried on the next
healthy backend; errors after it let the request finish as-is.
if pe.err != nil {
lb.pool.MarkDown(b)
if wrapped.wroteHeader {
return true // already streaming, can't retry
}
return false // safe to try another backend
}A goroutine probes health-path on every backend at health-interval.
Anything >= 500 or a network error marks it down; the next good probe
brings it back.
go build -o mini-lb .
--listen listen address (default :8080)
--backends comma-separated backend URLs (required)
--strategy round-robin | least-connections (default round-robin)
--health-path health probe path (default /healthz)
--health-interval probe interval (default 5s)
--health-timeout probe timeout (default 2s)
--max-retries retries on failure (default 2)
--verbose debug logging
pool— backends + health-check loopstrategy— Strategy interface, round-robin, least-connectionsmain— flags, dispatch, status endpoint, shutdown