Files
httpx/README.md
Aleksey Shakhmatov 4d47918a66
All checks were successful
CI / test (push) Successful in 30s
Update documentation with server package details
Add server package description, component table, and usage example to
README. Document server architecture, middleware chain, and test
conventions in CLAUDE.md.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 22:56:12 +03:00

135 lines
4.1 KiB
Markdown

# httpx
HTTP client and server toolkit for Go microservices. Client side: retry, load balancing, circuit breaking — all as `http.RoundTripper` middleware. Server side: routing, middleware (request ID, recovery, logging), health checks, graceful shutdown. stdlib only, zero external deps.
```
go get git.codelab.vc/pkg/httpx
```
## Quick start
```go
client := httpx.New(
httpx.WithBaseURL("https://api.example.com"),
httpx.WithTimeout(10*time.Second),
httpx.WithRetry(retry.WithMaxAttempts(3)),
httpx.WithMiddleware(
middleware.UserAgent("my-service/1.0"),
middleware.BearerAuth(func(ctx context.Context) (string, error) {
return os.Getenv("API_TOKEN"), nil
}),
),
)
defer client.Close()
resp, err := client.Get(ctx, "/users/123")
if err != nil {
log.Fatal(err)
}
var user User
resp.JSON(&user)
```
## Packages
### Client
Client middleware is `func(http.RoundTripper) http.RoundTripper`. Use them with `httpx.Client` or plug into a plain `http.Client`.
| Package | What it does |
|---------|-------------|
| `retry` | Exponential/constant backoff, Retry-After support. Idempotent methods only by default. |
| `balancer` | Round robin, failover, weighted random. Optional background health checks. |
| `circuitbreaker` | Per-host state machine (closed/open/half-open). Stops hammering dead endpoints. |
| `middleware` | Logging (slog), default headers, bearer/basic auth, panic recovery. |
### Server
Server middleware is `func(http.Handler) http.Handler`. The `server` package provides a production-ready HTTP server.
| Component | What it does |
|-----------|-------------|
| `server.Server` | Wraps `http.Server` with graceful shutdown, signal handling, lifecycle logging. |
| `server.Router` | Lightweight wrapper around `http.ServeMux` with groups, prefix routing, sub-router mounting. |
| `server.RequestID` | Assigns/propagates `X-Request-Id` (UUID v4 via `crypto/rand`). |
| `server.Recovery` | Recovers panics, returns 500, logs stack trace. |
| `server.Logging` | Structured request logging (method, path, status, duration, request ID). |
| `server.HealthHandler` | Liveness (`/healthz`) and readiness (`/readyz`) endpoints with pluggable checkers. |
| `server.Defaults` | Production preset: RequestID → Recovery → Logging + sensible timeouts. |
The client assembles them in this order:
```
Request → Logging → Your Middleware → Retry → Circuit Breaker → Balancer → Transport
```
Retry wraps the circuit breaker and balancer, so each attempt can pick a different endpoint.
## Multi-DC setup
```go
client := httpx.New(
httpx.WithEndpoints(
balancer.Endpoint{URL: "https://dc1.api.internal", Weight: 3},
balancer.Endpoint{URL: "https://dc2.api.internal", Weight: 1},
),
httpx.WithBalancer(balancer.WithStrategy(balancer.WeightedRandom())),
httpx.WithRetry(retry.WithMaxAttempts(4)),
httpx.WithCircuitBreaker(circuitbreaker.WithFailureThreshold(5)),
httpx.WithLogger(slog.Default()),
)
defer client.Close()
```
## Standalone usage
Each component works with any `http.Client`, no need for the full wrapper:
```go
// Just retry, nothing else
transport := retry.Transport(retry.WithMaxAttempts(3))
httpClient := &http.Client{
Transport: transport(http.DefaultTransport),
}
```
```go
// Chain a few middlewares together
chain := middleware.Chain(
middleware.Logging(slog.Default()),
middleware.UserAgent("my-service/1.0"),
retry.Transport(retry.WithMaxAttempts(2)),
)
httpClient := &http.Client{
Transport: chain(http.DefaultTransport),
}
```
## Server
```go
logger := slog.Default()
r := server.NewRouter()
r.HandleFunc("GET /hello", func(w http.ResponseWriter, _ *http.Request) {
w.Write([]byte("world"))
})
// Groups with middleware
api := r.Group("/api/v1", authMiddleware)
api.HandleFunc("GET /users/{id}", getUser)
// Health checks
r.Mount("/", server.HealthHandler(
func() error { return db.Ping() },
))
srv := server.New(r, server.Defaults(logger)...)
log.Fatal(srv.ListenAndServe()) // graceful shutdown on SIGINT/SIGTERM
```
## Requirements
Go 1.24+, stdlib only.