Aleksey Shakhmatov 7fae6247d5
All checks were successful
CI / test (push) Successful in 30s
Add comprehensive test coverage for server/ package
Cover edge cases: statusWriter multi-call/default/unwrap, UUID v4 format
and uniqueness, non-string panics, recovery body and log attributes,
4xx log level, default status in logging, request ID propagation,
server defaults/options/listen-error/multiple-hooks/logger, router
groups with empty prefix/inherited middleware/ordering/path params/
isolation, mount trailing slash, health content-type and POST rejection.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 13:55:22 +03:00
2026-03-20 10:35:38 +00:00
2026-03-21 13:06:30 +03:00
2026-03-20 10:35:38 +00:00

httpx

HTTP client for Go microservices. Retry, load balancing, circuit breaking, all as http.RoundTripper middleware. 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

Everything is a 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.

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),
}

Requirements

Go 1.24+, stdlib only.

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