httpx

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 23, 2026 License: MIT Imports: 15 Imported by: 0

README

httpx

HTTP client and server toolkit for Go microservices. Client side: retry, load balancing, circuit breaking, request ID propagation, response size limits — all as http.RoundTripper middleware. Server side: routing, middleware (request ID, recovery, logging, CORS, rate limiting, body limits, timeouts), health checks, JSON helpers, 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)

// PATCH request
resp, err = client.Patch(ctx, "/users/123", strings.NewReader(`{"name":"updated"}`))

// Form-encoded request (OAuth, webhooks, etc.)
req, _ := httpx.NewFormRequest(ctx, http.MethodPost, "/oauth/token", url.Values{
    "grant_type": {"client_credentials"},
    "scope":      {"read write"},
})
resp, err = client.Do(ctx, req)

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, request ID propagation.
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.CORS Cross-origin resource sharing with preflight handling and functional options.
server.RateLimit Per-key token bucket rate limiting with IP extraction and Retry-After.
server.MaxBodySize Limits request body size via http.MaxBytesReader.
server.Timeout Context-based request timeout, returns 503 on expiry.
server.WriteJSON JSON response helper, sets Content-Type and status.
server.WriteError JSON error response ({"error": "..."}) helper.
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(
    // Custom JSON 404 instead of plain text
    server.WithNotFoundHandler(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
        server.WriteError(w, 404, "not found")
    })),
)

r.HandleFunc("GET /hello", func(w http.ResponseWriter, r *http.Request) {
    server.WriteJSON(w, 200, map[string]string{"message": "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,
    append(server.Defaults(logger),
        // Protection middleware
        server.WithMiddleware(
            server.CORS(
                server.AllowOrigins("https://app.example.com"),
                server.AllowMethods("GET", "POST", "PUT", "PATCH", "DELETE"),
                server.AllowHeaders("Authorization", "Content-Type"),
                server.MaxAge(3600),
            ),
            server.RateLimit(
                server.WithRate(100),
                server.WithBurst(200),
            ),
            server.MaxBodySize(1<<20), // 1 MB
            server.Timeout(30*time.Second),
        ),
    )...,
)
log.Fatal(srv.ListenAndServe()) // graceful shutdown on SIGINT/SIGTERM

Client request ID propagation

In microservices, forward the incoming request ID to downstream calls:

client := httpx.New(
    httpx.WithMiddleware(middleware.RequestID()),
)

// In a server handler — the context already has the request ID from server.RequestID():
func handler(w http.ResponseWriter, r *http.Request) {
    // ID is automatically forwarded as X-Request-Id
    resp, err := client.Get(r.Context(), "https://downstream/api")
}

Response body limit

Protect against OOM from unexpectedly large upstream responses:

client := httpx.New(
    httpx.WithMaxResponseBody(10 << 20), // 10 MB max
)

Examples

See the examples/ directory for runnable programs:

Example Description
basic-client HTTP client with retry, timeout, logging, and response size limit
form-request Form-encoded POST requests (OAuth, webhooks)
load-balancing Multi-endpoint client with weighted balancing, circuit breaker, and health checks
server-basic Server with routing, groups, JSON helpers, health checks, and custom 404
server-protected Production server with CORS, rate limiting, body limits, and timeouts
request-id-propagation Request ID forwarding between server and client for distributed tracing

Requirements

Go 1.24+, stdlib only.

Documentation

Overview

Package httpx provides a high-level HTTP client with composable middleware for retry, circuit breaking, load balancing, structured logging, and more.

The client is configured via functional options and assembled as a middleware chain around a standard http.RoundTripper:

Logging → User Middlewares → Retry → Circuit Breaker → Balancer → Transport

Quick start

client := httpx.New(
    httpx.WithBaseURL("https://api.example.com"),
    httpx.WithTimeout(10 * time.Second),
    httpx.WithRetry(),
    httpx.WithCircuitBreaker(),
)
defer client.Close()

resp, err := client.Get(ctx, "/users/1")

Request builders

NewJSONRequest and NewFormRequest create requests with appropriate Content-Type headers and GetBody set for retry compatibility.

Error handling

Failed requests return *httpx.Error with structured fields (Op, URL, StatusCode). Sentinel errors ErrRetryExhausted, ErrCircuitOpen, and ErrNoHealthy can be checked with errors.Is.

Sub-packages

  • middleware — client-side middleware (logging, auth, headers, recovery, request ID)
  • retry — configurable retry with backoff and Retry-After support
  • circuitbreaker — per-host circuit breaker (closed → open → half-open)
  • balancer — client-side load balancing with health checking
  • server — production HTTP server with router, middleware, and graceful shutdown

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrRetryExhausted = retry.ErrRetryExhausted
	ErrCircuitOpen    = circuitbreaker.ErrCircuitOpen
	ErrNoHealthy      = balancer.ErrNoHealthy
)

Sentinel errors returned by httpx components. These are aliases for the canonical errors defined in sub-packages, so that errors.Is works regardless of which import the caller uses.

Functions

func NewFormRequest

func NewFormRequest(ctx context.Context, method, rawURL string, values url.Values) (*http.Request, error)

NewFormRequest creates an http.Request with a form-encoded body and sets Content-Type to application/x-www-form-urlencoded. The GetBody function is set so that the request can be retried.

func NewJSONRequest

func NewJSONRequest(ctx context.Context, method, url string, body any) (*http.Request, error)

NewJSONRequest creates an http.Request with a JSON-encoded body and sets Content-Type to application/json.

func NewRequest

func NewRequest(ctx context.Context, method, url string, body io.Reader) (*http.Request, error)

NewRequest creates an http.Request with context. It is a convenience wrapper around http.NewRequestWithContext.

Types

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client is a high-level HTTP client that composes middleware for retry, circuit breaking, load balancing, logging, and more.

func New

func New(opts ...Option) *Client

New creates a new Client with the given options.

The middleware chain is assembled as (outermost → innermost):

Logging → User Middlewares → Retry → Circuit Breaker → Balancer → Base Transport

func (*Client) Close

func (c *Client) Close()

Close releases resources associated with the Client, such as background health checker goroutines. It is safe to call multiple times.

func (*Client) Delete

func (c *Client) Delete(ctx context.Context, url string) (*Response, error)

Delete performs a DELETE request to the given URL.

func (*Client) Do

func (c *Client) Do(ctx context.Context, req *http.Request) (*Response, error)

Do executes an HTTP request.

func (*Client) Get

func (c *Client) Get(ctx context.Context, url string) (*Response, error)

Get performs a GET request to the given URL.

func (*Client) HTTPClient

func (c *Client) HTTPClient() *http.Client

HTTPClient returns the underlying *http.Client for advanced use cases. Mutating the returned client may bypass the configured middleware chain.

func (*Client) Patch

func (c *Client) Patch(ctx context.Context, url string, body io.Reader) (*Response, error)

Patch performs a PATCH request to the given URL with the given body.

func (*Client) Post

func (c *Client) Post(ctx context.Context, url string, body io.Reader) (*Response, error)

Post performs a POST request to the given URL with the given body.

func (*Client) Put

func (c *Client) Put(ctx context.Context, url string, body io.Reader) (*Response, error)

Put performs a PUT request to the given URL with the given body.

type Error

type Error struct {
	// Op is the operation that failed (e.g. "Get", "Do").
	Op string
	// URL is the originally-requested URL.
	URL string
	// Endpoint is the resolved endpoint URL (after balancing).
	Endpoint string
	// StatusCode is the HTTP status code, if a response was received.
	StatusCode int
	// Retries is the number of retry attempts made.
	Retries int
	// Err is the underlying error.
	Err error
}

Error provides structured error information for failed HTTP operations.

func (*Error) Error

func (e *Error) Error() string

func (*Error) Unwrap

func (e *Error) Unwrap() error

type ErrorMapper

type ErrorMapper func(resp *http.Response) error

ErrorMapper maps an HTTP response to an error. If the response is acceptable, the mapper should return nil. Used by Client to convert non-successful HTTP responses into Go errors.

type Option

type Option func(*clientOptions)

Option configures a Client.

func WithBalancer

func WithBalancer(opts ...balancer.Option) Option

WithBalancer configures the load balancer strategy and options.

func WithBaseURL

func WithBaseURL(url string) Option

WithBaseURL sets the base URL prepended to all relative request paths.

func WithCircuitBreaker

func WithCircuitBreaker(opts ...circuitbreaker.Option) Option

WithCircuitBreaker enables per-host circuit breaking.

func WithEndpoints

func WithEndpoints(eps ...balancer.Endpoint) Option

WithEndpoints sets the endpoints for load balancing.

func WithErrorMapper

func WithErrorMapper(m ErrorMapper) Option

WithErrorMapper sets a function that maps HTTP responses to errors.

func WithLogger

func WithLogger(l *slog.Logger) Option

WithLogger enables structured logging of requests and responses.

func WithMaxResponseBody

func WithMaxResponseBody(n int64) Option

WithMaxResponseBody limits the number of bytes read from response bodies by Response.Bytes (and by extension String, JSON, XML). If the response body exceeds n bytes, reading stops and returns an error. A value of 0 means no limit (the default).

func WithMiddleware

func WithMiddleware(mws ...middleware.Middleware) Option

WithMiddleware appends user middlewares to the chain. These run between logging and retry in the middleware stack.

func WithRetry

func WithRetry(opts ...retry.Option) Option

WithRetry enables retry with the given options.

func WithTimeout

func WithTimeout(d time.Duration) Option

WithTimeout sets the overall request timeout.

func WithTransport

func WithTransport(rt http.RoundTripper) Option

WithTransport sets the base http.RoundTripper. Defaults to http.DefaultTransport.

type Response

type Response struct {
	*http.Response
	// contains filtered or unexported fields
}

Response wraps http.Response with convenience methods.

func (*Response) BodyReader

func (r *Response) BodyReader() io.Reader

BodyReader returns a reader for the response body. If the body has already been read via Bytes/String/JSON/XML, returns a reader over the cached bytes.

func (*Response) Bytes

func (r *Response) Bytes() ([]byte, error)

Bytes reads and returns the entire response body. The body is cached so subsequent calls return the same data.

func (*Response) Close

func (r *Response) Close() error

Close drains and closes the response body.

func (*Response) IsError

func (r *Response) IsError() bool

IsError returns true if the status code is 4xx or 5xx.

func (*Response) IsSuccess

func (r *Response) IsSuccess() bool

IsSuccess returns true if the status code is in the 2xx range.

func (*Response) JSON

func (r *Response) JSON(v any) error

JSON decodes the response body as JSON into v.

func (*Response) String

func (r *Response) String() (string, error)

String reads the response body and returns it as a string.

func (*Response) XML

func (r *Response) XML(v any) error

XML decodes the response body as XML into v.

Directories

Path Synopsis
Package balancer provides client-side load balancing as HTTP middleware.
Package balancer provides client-side load balancing as HTTP middleware.
Package circuitbreaker provides a per-host circuit breaker as HTTP middleware.
Package circuitbreaker provides a per-host circuit breaker as HTTP middleware.
examples
basic-client command
Basic HTTP client with retry, timeout, and structured logging.
Basic HTTP client with retry, timeout, and structured logging.
form-request command
Demonstrates form-encoded requests for OAuth token endpoints and similar APIs.
Demonstrates form-encoded requests for OAuth token endpoints and similar APIs.
load-balancing command
Demonstrates load balancing across multiple backend endpoints with circuit breaking and health-checked failover.
Demonstrates load balancing across multiple backend endpoints with circuit breaking and health-checked failover.
request-id-propagation command
Demonstrates request ID propagation between server and client.
Demonstrates request ID propagation between server and client.
server-basic command
Basic HTTP server with routing, middleware, health checks, and graceful shutdown.
Basic HTTP server with routing, middleware, health checks, and graceful shutdown.
server-protected command
Production server with CORS, rate limiting, body size limits, and timeouts.
Production server with CORS, rate limiting, body size limits, and timeouts.
internal
requestid
Package requestid provides a shared context key for request IDs, allowing both client and server packages to access request IDs without circular imports.
Package requestid provides a shared context key for request IDs, allowing both client and server packages to access request IDs without circular imports.
Package middleware provides client-side HTTP middleware for use with httpx.Client or any http.RoundTripper-based transport chain.
Package middleware provides client-side HTTP middleware for use with httpx.Client or any http.RoundTripper-based transport chain.
Package retry provides configurable HTTP request retry as client middleware.
Package retry provides configurable HTTP request retry as client middleware.
Package server provides a production-ready HTTP server with graceful shutdown, middleware composition, routing, and JSON response helpers.
Package server provides a production-ready HTTP server with graceful shutdown, middleware composition, routing, and JSON response helpers.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL