builds / load-balancer / stage-1SHEET 1 / 6 · REV ASIGN IN
ASSEMBLY DIAGRAM — YOUR LOAD BALANCERSCALE: LEARNING
browserthe outside worldFRONT DOOR⚙ buildingROTORstage 2PULSE MONITORstage 3SCALESstage 4INSTRUMENT PANELstage 5RELIEF VALVEstage 6
BUILTUNDER CONSTRUCTIONNOT YET IMAGINED INTO EXISTENCE
STAGE 1 · THE FRONT DOOR

The Front Door

Before a load balancer balances anything, it's a middleman that pretends to be the server.

What you're wiring up

A reverse proxy accepts a real HTTP request, re-sends it to a backend, and streams the answer back. That sentence hides three disciplines, and this stage is all three. You listen on one port, you dial out to another, and the client never learns there were two conversations.

The first discipline is streaming. Bytes flow through you, they don't pile up in you. If you read the whole backend response into memory before writing a single byte to the client, a 4GB download becomes 4GB of your RAM and the client waits for the last byte before seeing the first. Copy as you go.

The second is header hygiene. Some headers belong to one hop only — Connection, Keep-Alive, Transfer-Encoding, Upgrade — and forwarding them corrupts the next hop's framing. Some headers you must add: X-Forwarded-For tells the backend who really called, since from its point of view every request now comes from you. The third discipline is failing fast: a dead backend must become a 502 in seconds, never a hang.

# client → proxy
GET /hello?x=1 HTTP/1.1
Connection: keep-alive

# proxy → backend (hop-by-hop stripped, XFF appended)
GET /hello?x=1 HTTP/1.1
X-Forwarded-For: 127.0.0.1

Assembly steps

[ 01 ]
Stand up an HTTP server on --listen :8080 that answers every request itself with 503 no backend — prove the door opens.
hint

An http.Server plus one http.HandlerFunc is the whole thing. Resist frameworks; you are the framework here.

[ 02 ]
Forward the request: build a new outbound request to --backends http://127.0.0.1:9001 preserving method, path, query, headers and body, then copy the backend's status, headers and body back.
hint

io.Copy(w, resp.Body) streams. Reading the whole body into memory first will fail the streaming check.

hint

Copy headers before WriteHeader — once the first byte goes out, the headers are locked.

[ 03 ]
Strip hop-by-hop headers in both directions (Connection, Keep-Alive, Transfer-Encoding, Upgrade, Proxy-*, TE, Trailer) and append the client IP to X-Forwarded-For.
hint

r.RemoteAddr is ip:port — net.SplitHostPort it before appending.

[ 04 ]
Fail fast: unreachable backend, or no answer within --backend-timeout (default 2s), returns 502 Bad Gateway.
hint

Give the outbound http.Client a Timeout.

hint

A timeout and a connection-refused should land on the same 502 path — the client doesn't care which one it was.

Go deeper (after it passes)

You just hand-rolled what httputil.ReverseProxy hides — go read its source now and you'll recognize every line. Pairs with HFDP M10 (The Stand-In, Proxy) and Clean Code M5 (Errors Without the Clutter).