# CLAUDE.md — httpx ## Commands ```bash go build ./... # compile go test ./... # all tests go test -race ./... # tests with race detector go test -v -run TestName ./package/ # single test go vet ./... # static analysis ``` ## Architecture - **Module**: `git.codelab.vc/pkg/httpx`, Go 1.24, zero external dependencies ### Client - **Core pattern**: middleware is `func(http.RoundTripper) http.RoundTripper` - **Chain assembly order** (client.go): Logging → User MW → Retry → CB → Balancer → Transport - Retry wraps CB+Balancer so each attempt can hit a different endpoint - **Circuit breaker** is per-host (`sync.Map` of host → Breaker) - **Sentinel errors**: canonical values live in sub-packages, root package re-exports as aliases - **balancer.Transport** returns `(Middleware, *Closer)` — Closer must be tracked for health checker shutdown - **Client.Close()** stops the health checker goroutine - **Client.Patch()** — PATCH method, same pattern as Put/Post - **NewFormRequest** — form-encoded request builder (`application/x-www-form-urlencoded`) with `GetBody` for retry - **WithMaxResponseBody** — wraps `resp.Body` with `io.LimitedReader` to prevent OOM - **middleware.RequestID()** — propagates request ID from context to outgoing `X-Request-Id` header - **`internal/requestid`** — shared context key used by both `server` and `middleware` packages to avoid circular imports ### Server (`server/`) - **Core pattern**: middleware is `func(http.Handler) http.Handler` - **Server** wraps `http.Server` with `net.Listener`, graceful shutdown via signal handling, lifecycle hooks - **Router** wraps `http.ServeMux` — supports groups with prefix + middleware inheritance, `Mount` for sub-handlers, `WithNotFoundHandler` for custom 404 - **Middleware chain** via `Chain(A, B, C)` — A outermost, C innermost (same as client side) - **statusWriter** wraps `http.ResponseWriter` to capture status; implements `Unwrap()` for `http.ResponseController` - **Defaults()** preset: RequestID → Recovery → Logging + production timeouts - **HealthHandler** exposes `GET /healthz` (liveness) and `GET /readyz` (readiness with pluggable checkers) - **CORS** middleware — preflight OPTIONS handling, `AllowOrigins`, `AllowMethods`, `AllowHeaders`, `ExposeHeaders`, `AllowCredentials`, `MaxAge` - **RateLimit** middleware — per-key token bucket (`sync.Map`), IP from `X-Forwarded-For`, `WithRate`/`WithBurst`/`WithKeyFunc`, uses `internal/clock` - **MaxBodySize** middleware — wraps `r.Body` via `http.MaxBytesReader` - **Timeout** middleware — wraps `http.TimeoutHandler`, returns 503 - **WriteJSON** / **WriteError** — JSON response helpers in `server/respond.go` ## Conventions - Functional options for all configuration (client and server) - Test helpers: `mockTransport(fn)` wrapping `middleware.RoundTripperFunc` (client), `httptest.NewRecorder`/`httptest.NewRequest` (server) - Server tests use `waitForAddr(t, srv)` helper to poll until server is ready - No external test frameworks — stdlib only - Thread safety required (`sync.Mutex`/`atomic`) - `internal/clock` for deterministic time testing