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
2026-03-20 10:35:38 +00:00
2026-03-20 10:35:38 +00:00

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

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

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:

// Just retry, nothing else
transport := retry.Transport(retry.WithMaxAttempts(3))
httpClient := &http.Client{
    Transport: transport(http.DefaultTransport),
}
// 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

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.

Description
No description provided
Readme MIT 174 KiB
Languages
Go 100%