httpx

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Oct 15, 2026 License: MIT Imports: 35 Imported by: 0

README

httpx

HTTP standards and resilience patterns as composable http.RoundTripper layers for Go's standard http.Client.

Keep your client, requests and responses. Add one transport or combine layers around your existing transport; it keeps control of connections, proxies and TLS. Wrapping an existing client also preserves its cookie jar, redirect policy and timeout.

Requires Go 1.26 or later. APIs may change between minor releases while at v0.

go get github.com/ashtonian/pkg/httpx

Common HTTP patterns

Choose the behaviors your client needs and enable their layers individually.

Pattern What httpx handles
Retries and backoff Transient transport errors and HTTP 429/502/503/504; bounded exponential backoff, jitter and Retry-After.
Request replay rules Idempotent methods, an idempotency key or explicit opt-in, plus a replayable body. Write safety depends on the server's contract; an opt-in does not deduplicate requests.
Circuit breaking Fail fast for a repeatedly failing origin, then probe for recovery; independent state per scheme/host/port.
Slow-request hedging Start an overlapping attempt after a delay and cancel losers. GET by default; the first HTTP response wins, including an error status.
Rate and concurrency limits Per-origin rate/burst budgets and optional waiting; cap in-flight requests through the response body's lifetime.
Timeouts and cancellation Preserve client/context deadlines through retry and admission waits; optional base transport with dial/TLS/header timeouts.
Conditional and fresh reads ETag / Last-Modified revalidation, 304 reuse, Cache-Control, Vary, corrected age and bounded storage.
Cache partitioning Keys include the effective URL, Authorization and Cookie; configure additional headers explicitly, including API-key headers.
Stale fallback and background refresh stale-if-error and stale-while-revalidate when permitted; keep useful cached reads available during upstream failures or refreshes.
Compression negotiation Lazy gzip/deflate decoding, optional Brotli and configurable accepted encodings; preserve encoded partial responses. Go already handles basic gzip.
Origin-bound credentials Add bearer, basic or API-key credentials only when the request URL and effective host match the provider's bound origin.
Streaming and response bounds Ordinary client.Do bodies for streams/downloads; opt-in bounded reads and JSON/XML decoding, with optional MessagePack/protobuf.
Operational visibility Redacted request/response logging, retry/cache events and metrics hooks.

Failure handling and failover

After retries are exhausted, callers receive the final HTTP response or Go error. Check StatusCode: a 4xx/5xx response is not automatically a Go error. Use errors.Is / errors.As for underlying errors and local refusals such as ErrCircuitOpen, ErrRateLimited and ErrLimiterTimeout. Close response bodies.

Failover to another host or region is application-owned. Retries and hedges repeat the same target through your transport; httpx does not select a backup endpoint. Circuit breaking limits calls to a failing origin; stale-cache fallback can keep permitted reads available. See failure behavior and failover boundaries.

Quick Start

package main

import (
	"context"
	"errors"
	"fmt"
	"io"
	"log/slog"
	"net/http"
	"os"
	"time"

	"github.com/ashtonian/pkg/httpx"
)

func main() {
	if err := run(context.Background(), os.Getenv("API_URL"), os.Stdout); err != nil {
		slog.Error("request", "error", err)
		os.Exit(1)
	}
}

func run(ctx context.Context, url string, out io.Writer) (err error) {
	if url == "" {
		url = "https://httpbin.org/etag/httpx-demo"
	}
	// Layers are listed from innermost to outermost.
	transport, err := httpx.NewChain(
		httpx.WithCompressionT(),
		httpx.WithRetryT(httpx.WithMaxAttempts(3),
			httpx.WithBackoff(100*time.Millisecond, time.Second, 2)),
	).Build(http.DefaultTransport.(*http.Transport).Clone())
	if err != nil {
		return err
	}
	cache, err := httpx.NewCacheTransport(transport, nil) // Bounded private cache.
	if err != nil {
		return err
	}
	client := &http.Client{Transport: cache, Timeout: 10 * time.Second}
	defer client.CloseIdleConnections()
	defer func() { err = errors.Join(err, cache.Close()) }()

	for range 2 { // The server's cache headers decide: fresh hit or revalidation.
		req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
		if err != nil {
			return err
		}
		resp, err := client.Do(req)
		if err != nil {
			return err
		}
		body, readErr := httpx.ReadAtMost(resp.Body, 1<<20)
		if err := errors.Join(readErr, resp.Body.Close()); err != nil {
			return err
		}
		if resp.StatusCode != http.StatusOK {
			return fmt.Errorf("HTTP %d", resp.StatusCode)
		}
		if _, err := fmt.Fprintf(out, "HTTP %d; %s\n%s\n", resp.StatusCode, resp.Header.Get("Cache-Status"), body); err != nil {
			return err
		}
	}
	return nil
}

Run go run ./examples/quickstart from this module. It fetches httpbin's ETag endpoint twice; set API_URL to use another complete URL. Cache headers decide whether the second GET is a fresh hit, a conditional revalidation or a new fetch. The integration tests exercise retries, gzip, ETags and 304 reuse against a local HTTP server.

Documentation

Need Read
Choose which behaviors to add and when Use cases
Wrap an existing client, order layers, or use a custom transport Composition
Use ETags, freshness, stale responses and cache partition keys Caching
Negotiate compression, preserve ranges, or add a codec Compression and codecs
Tune retry/backoff, circuit breakers and request budgets Resilience
Handle statuses, stream bodies, or use optional typed helpers Requests and responses
Bind credentials, redact logs and export metrics Security and observability
Look up every constructor, option, default and error Reference
Run tests, benchmarks or release checks Development

NewClient / NewStandard and typed Get[T] / Do[T] helpers are optional. Optional modules: Brotli, MessagePack, protobuf. The Gin example demonstrates a complete API and client.

License

MIT.

Documentation

Overview

Package httpx implements composable HTTP behavior as http.RoundTripper decorators: conditional caching, compression negotiation, and safe retry with backoff. Use them with an ordinary http.Client, standard requests and responses, and an existing transport's TLS, proxy and pool settings.

Transports first

NewRetryTransport, NewCompressionTransport and NewCacheTransport each wrap one http.RoundTripper. NewChain combines layers into another RoundTripper; the first layer listed is innermost. Assign the result to http.Client.Transport and use http.NewRequestWithContext and client.Do. Request contexts, cookies, redirects, status codes and streaming bodies keep their standard net/http interfaces. Reuse clients and transports.

Put compression inside retry and cache outside retry. This lets fresh cache hits skip the network, while revalidation uses the retry policy and the cache stores decoded responses. If used, auth runs before cache lookup; rate limits, concurrency limits and breakers normally run per attempt.

HTTP caching

CacheTransport is an opt-in private HTTP cache. It follows freshness, Cache-Control, Vary and identity boundaries, revalidates with ETag or Last-Modified, and merges a 304 response with the stored representation. MemoryCache bounds retained entries and bytes; CacheStore permits a custom store. Stale reuse is opt-in through origin directives or options.

Identity includes Authorization, Cookie, effective authority and configured identity headers. Configure all additional identity headers, including API-key headers; use separate stores for identities hidden in a transport, such as mTLS. Caller-conditional, ranged, HEAD and body-bearing GET requests bypass cache.

Compression and retry

CompressionTransport negotiates gzip and deflate, with optional Brotli, and decodes supported complete response bodies lazily. Read errors remain body-read errors; partial representations keep their encoded bytes. Go's default transport already handles basic gzip; this layer supplies configurable accepted encodings and lazy decoding as a reusable decorator.

RetryTransport defaults to three total attempts with exponential backoff, jitter and Retry-After handling, subject to the request's deadline. Retry requires an idempotent method, an Idempotency-Key, or explicit WithRetryNonIdempotent permission, plus a replayable body. The server must implement deduplication for an idempotency key to make writes safe. Unknown streams are recorded as sent, within a bound, rather than buffered before the transport can honor cancellation.

Optional policies and helpers

Circuit breakers, rate and concurrency limits, hedging, origin-bound auth, redacted logging and metrics are independent transports too. Their order determines whether they observe logical calls or individual attempts. TransportStack inspects layers; built-in layers expose Unwrap.

NewClient and NewStandard are optional builders returning *http.Client. Both default to a tuned base transport and 30-second timeout; NewStandard also enables compression and retry. Other policies must be enabled. Get, Post, Put, Patch and Do optionally decode bounded 2xx values by Content-Type. JSON and XML are built in; MessagePack and protobuf use separate modules. WithMaxResponseBytes defaults to 10 MiB after decompression. ReadAtMost bounds complete reads without typed decoding.

Status, bodies and shutdown

A nil error is not an HTTP success check. Inspect the response status and close each response body, including one returned with a typed decode error. Raw client.Do bodies remain streaming and need application-owned bounds. EOF, read error or Close releases limiter slots and hedge winner contexts. Call CacheTransport.Close to stop background refreshes before closing idle transport connections with http.Client.CloseIdleConnections.

Optional modules

Blank-import codec/msgpack, codec/proto or compress/brotli to register MessagePack, protobuf or Brotli. Named imports expose implementations. Each optional dependency is a separate Go module; the core depends only on golang.org/x/time and github.com/sony/gobreaker/v2.

Index

Constants

View Source
const (
	DefaultMaxCacheBodyBytes        int64 = 1 << 20
	DefaultMaxRetention                   = 24 * time.Hour
	DefaultRevalidateTimeout              = 30 * time.Second
	DefaultMaxConcurrentRevalidates       = 8
)

Cache defaults.

View Source
const (
	DefaultFailureThreshold = 5
	DefaultSuccessThreshold = 2
	DefaultOpenTimeout      = 30 * time.Second
)

Circuit breaker defaults.

View Source
const (
	DefaultMemoryCacheMaxEntries       = 1024
	DefaultMemoryCacheMaxBytes   int64 = 64 << 20
)

MemoryCache defaults.

View Source
const (
	EncodingBrotli   = "br"
	EncodingGzip     = "gzip"
	EncodingDeflate  = "deflate"
	EncodingZstd     = "zstd"
	EncodingIdentity = "identity"
)

Encoding constants for compression.

View Source
const (
	ContentTypeJSON        = "application/json"
	ContentTypeXML         = "application/xml"
	ContentTypeMsgpack     = "application/msgpack"
	ContentTypeProtobuf    = "application/protobuf"
	ContentTypeFormURL     = "application/x-www-form-urlencoded"
	ContentTypeOctetStream = "application/octet-stream"
)

ContentType constants for codecs.

View Source
const DefaultLatencyWindow = 1024

DefaultLatencyWindow is how many recent latencies a LatencyHook keeps.

View Source
const DefaultMaxOrigins = 1024

DefaultMaxOrigins bounds how many origins CircuitTransport and RateLimitTransport keep state for.

View Source
const DefaultMaxReplayBytes int64 = 1 << 20

DefaultMaxReplayBytes is the largest request body RetryTransport records, as the first attempt sends it, so it can re-send it. Requests that set GetBody are replayed without recording; a larger body without GetBody, or one the first attempt did not read to its end, is not retried.

View Source
const DefaultMaxResponseBytes int64 = 10 << 20

DefaultMaxResponseBytes bounds how much of a response body the decoding helpers read.

View Source
const DefaultTimeout = 30 * time.Second

DefaultTimeout is the overall request timeout applied by NewClient when the caller passes no WithTimeout option. Set WithTimeout(0) to disable it (e.g., for long-poll endpoints); base transport timeouts still apply.

View Source
const IdempotencyKeyHeader = "Idempotency-Key"

IdempotencyKeyHeader is the request header that permits RetryTransport to replay a non-idempotent request. The server must implement deduplication for that key; setting it alone does not make a write safe.

View Source
const Redacted = "[REDACTED]"

Redacted replaces the value of every sensitive header and query parameter in the snapshots a LoggingTransport hands to its hook.

Variables

View Source
var (
	// ErrCircuitOpen is returned when the circuit breaker is open.
	ErrCircuitOpen = errors.New("httpx: circuit breaker is open")

	// ErrLimiterTimeout is returned when acquiring a slot times out.
	ErrLimiterTimeout = errors.New("httpx: limiter acquire timeout")

	// ErrResponseTooLarge is returned by the decoding helpers when a response
	// body exceeds WithMaxResponseBytes.
	ErrResponseTooLarge = errors.New("httpx: response body too large to decode")

	// ErrRateLimited is returned when the rate limit is exceeded.
	ErrRateLimited = errors.New("httpx: rate limit exceeded")

	// ErrAuthRefresh is returned when token refresh fails.
	ErrAuthRefresh = errors.New("httpx: auth token refresh failed")

	// ErrNoCodec is returned when no codec is registered for the response content type.
	ErrNoCodec = errors.New("httpx: no codec registered for content type")

	// ErrInvalidConfig is wrapped by every construction-time validation
	// failure returned from NewClient, NewStandard, NewBaseTransport, the
	// New*Transport constructors and Chain.Build.
	ErrInvalidConfig = errors.New("httpx: invalid configuration")
)

Sentinel errors for transport failures.

Functions

func DecodeResponse

func DecodeResponse(resp *http.Response, codec Codec, v any, opts ...DecodeOption) error

DecodeResponse reads resp.Body up to WithMaxResponseBytes (default DefaultMaxResponseBytes) and decodes it into v. Afterwards resp.Body holds the bytes read. A larger body fails with ErrResponseTooLarge and resp.Body still reads the whole body; the caller must close it. Unlike the typed request helpers, DecodeResponse does not check the status, request method or Content-Type; the caller selects the body and codec.

func Delete

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

Delete sends a DELETE request. The response body is not decoded.

func Do

func Do[T any](client *http.Client, req *http.Request, opts ...DecodeOption) (T, *http.Response, error)

Do executes a request and decodes the response body into T.

This is the lowest-level generic helper. Use it when you need full control over the request (custom headers, custom codec via EncodeRequest, etc.).

If the request has no Accept header, one is set from registered codecs. Only 2xx responses are decoded; non-2xx responses return zero T with the response body intact and nil error. The body is read up to WithMaxResponseBytes: a larger one fails with ErrResponseTooLarge and the returned response still reads the whole body, which the caller must close. After decoding (or a decode error) resp.Body holds the bytes read. When T is a pointer type the codec decodes into a newly allocated pointee. HEAD, 204 and 205 responses return zero T without decoding, even when their headers include a Content-Type.

req, _ := http.NewRequest("POST", url, nil)
httpx.EncodeRequest(req, msgpack.Codec, body)
user, resp, err := httpx.Do[User](client, req)

func EncodeRequest

func EncodeRequest(req *http.Request, codec Codec, v any) error

EncodeRequest encodes v into req's body, replacing any body req had: it closes the old one and sets Body, GetBody and ContentLength together, so a retry or a 307/308 redirect replays the new payload.

func Get

func Get[T any](ctx context.Context, client *http.Client, url string, opts ...DecodeOption) (T, *http.Response, error)

Get sends a GET request and decodes the response body into T.

The codec is auto-detected from the response Content-Type using the global codec registry. Only 2xx responses are decoded; non-2xx responses return zero T with the response body intact and nil error. A body larger than WithMaxResponseBytes (default DefaultMaxResponseBytes) fails with ErrResponseTooLarge.

All transport decorators on the client (retry, auth, cache, compression, etc.) apply transparently.

user, resp, err := httpx.Get[User](ctx, client, "https://api.example.com/users/1")

func HasCodec

func HasCodec(contentType string) bool

HasCodec reports whether a codec is registered for the content type.

func HasCompressor

func HasCompressor(encoding string) bool

HasCompressor reports whether a compressor is registered for the encoding.

func MustNewStandard

func MustNewStandard(opts ...Option) *http.Client

MustNewStandard is NewStandard for statically known options: it panics when they are invalid, like regexp.MustCompile. Use it only for zero-config defaults built from literal options; call NewStandard and handle the error whenever an option value comes from runtime configuration.

func NewBaseTransport

func NewBaseTransport(opts ...BaseOption) (*http.Transport, error)

NewBaseTransport creates a tuned *http.Transport with production defaults.

Default configuration:

  • MaxIdleConns: 100
  • MaxIdleConnsPerHost: 10
  • IdleConnTimeout: 90s
  • TLSHandshakeTimeout: 10s
  • ResponseHeaderTimeout: 30s (no per-request bound otherwise — a server that accepts the connection but never sends headers would hang the call)
  • ForceAttemptHTTP2: true
  • DisableCompression: true (compression is handled by CompressionTransport)

Negative counts, durations or buffer sizes and a nil dialer are rejected with an error wrapping ErrInvalidConfig.

func NewClient

func NewClient(opts ...Option) (*http.Client, error)

NewClient creates a new http.Client with the configured transport stack.

The stack order is fixed (see NewClient's build order below), independent of the order options are passed. Every enabled layer validates its configuration; the first invalid layer's error is returned, wrapping ErrInvalidConfig.

Example:

client, err := httpx.NewClient(
    httpx.WithBaseTransport(httpx.WithHTTP2()),
    httpx.WithCompression(),
    httpx.WithRetry(httpx.WithMaxAttempts(3)),
    httpx.WithCircuitBreaker(),
)

func NewStandard

func NewStandard(opts ...Option) (*http.Client, error)

NewStandard returns an *http.Client with compression, safe retries and a bounded timeout for ordinary outbound API calls. Pass additional [Option]s to override or extend the defaults for a specific call site (e.g. circuit breaker around a flaky upstream, hedging for a latency-sensitive endpoint).

Default configuration:

  • Base transport with HTTP/2 + tuned pool defaults (MaxIdleConns 100 / MaxIdleConnsPerHost 10 / IdleConnTimeout 90s / TLSHandshakeTimeout 10s / ResponseHeaderTimeout 30s)
  • Compression (gzip and zlib deflate; optional registered encodings)
  • Retry with exponential backoff (3 attempts; transient failures) for idempotent requests and requests carrying Idempotency-Key only — a POST or PATCH is sent once unless the caller opts in
  • 30s overall request timeout

No HTTP cache is wired by default. Callers that want one opt in with WithCache, which partitions stored responses by Authorization, Cookie and any WithCacheIdentityHeaders. Configure all additional identity headers; identities hidden in a transport, such as mTLS, need separate stores.

Callers that need cancellation pass a context.Context with a deadline; the 30s ceiling is the upper bound, not the floor.

Codec note: httpx ships JSON + XML codecs natively. MsgPack is opt-in via the separate codec/msgpack module; protobuf uses codec/proto. Import only the formats your application needs.

NewStandard returns an error wrapping ErrInvalidConfig when an override is invalid; the defaults alone always validate.

func Patch

func Patch[T any](ctx context.Context, client *http.Client, url string, body any, opts ...DecodeOption) (T, *http.Response, error)

Patch sends a PATCH request with an encoded body and decodes the response into T.

func Post

func Post[T any](ctx context.Context, client *http.Client, url string, body any, opts ...DecodeOption) (T, *http.Response, error)

Post sends a POST request with an encoded body and decodes the response into T.

The body is encoded using the JSON codec by default. Pass nil for no body. If body implements io.Reader, it is used directly without encoding.

created, resp, err := httpx.Post[User](ctx, client, url, CreateUserReq{Name: "Alice"})

func Put

func Put[T any](ctx context.Context, client *http.Client, url string, body any, opts ...DecodeOption) (T, *http.Response, error)

Put sends a PUT request with an encoded body and decodes the response into T.

updated, resp, err := httpx.Put[User](ctx, client, url, UpdateUserReq{Name: "Bob"})

func ReadAtMost

func ReadAtMost(r io.Reader, limit int64) ([]byte, error)

ReadAtMost reads r to its end when it holds at most limit bytes. A larger input fails with ErrResponseTooLarge rather than returning a prefix, so a truncated representation is never mistaken for a whole one. limit must be at least 1.

func RegisterCodec

func RegisterCodec(c Codec)

RegisterCodec registers a codec for its content type, replacing any earlier registration. Call it from init() in optional modules (e.g. codec/msgpack). It panics on a nil codec.

func RegisterCompressor

func RegisterCompressor(encoding string, factory func() Compressor)

RegisterCompressor registers a compressor factory for an encoding, replacing any earlier registration. Call it from init() in optional modules (e.g. compress/brotli). It panics on a nil factory.

func RegisteredEncodings

func RegisteredEncodings() []string

RegisteredEncodings returns the registered encoding names, sorted.

func TransportStack

func TransportStack(client *http.Client) []http.RoundTripper

TransportStack returns the transport stack for inspection.

Types

type APIKeyProvider

type APIKeyProvider struct {
	Key    string
	Header string // Default: "X-API-Key"
}

APIKeyProvider adds an API key header.

func NewAPIKeyProvider

func NewAPIKeyProvider(key string) *APIKeyProvider

NewAPIKeyProvider creates a new API key provider.

func NewAPIKeyProviderWithHeader

func NewAPIKeyProviderWithHeader(key, header string) *APIKeyProvider

NewAPIKeyProviderWithHeader creates an API key provider with a custom header.

func (*APIKeyProvider) AddAuth

func (p *APIKeyProvider) AddAuth(req *http.Request) error

AddAuth implements AuthProvider.

type AdaptiveHedgingTransport

type AdaptiveHedgingTransport struct {
	*HedgingTransport
	// contains filtered or unexported fields
}

AdaptiveHedgingTransport adjusts hedge delay based on observed latencies.

func NewAdaptiveHedgingTransport

func NewAdaptiveHedgingTransport(base http.RoundTripper, percentile float64, opts ...HedgingOption) (*AdaptiveHedgingTransport, error)

NewAdaptiveHedgingTransport creates a hedging transport that adapts delay based on latency.

percentile selects the observed latency used as the hedge delay and must be within (0, 1].

func (*AdaptiveHedgingTransport) RoundTrip

func (t *AdaptiveHedgingTransport) RoundTrip(req *http.Request) (*http.Response, error)

RoundTrip implements http.RoundTripper with adaptive delay.

type AuthBinding

type AuthBinding struct {
	URL      string
	Provider AuthProvider
}

AuthBinding binds a provider to the origin of URL (its scheme, host and port; any path is ignored).

type AuthProvider

type AuthProvider interface {
	AddAuth(req *http.Request) error
}

AuthProvider adds authentication to requests.

type AuthTransport

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

AuthTransport adds credentials to requests whose origin has a bound provider. The connection destination and effective Host authority must both match the binding before that provider runs, including on redirect hops followed by http.Client. Caller-supplied headers are passed through; this transport does not remove credentials already present on a request.

func NewAuthTransport

func NewAuthTransport(base http.RoundTripper, bindings ...AuthBinding) (*AuthTransport, error)

NewAuthTransport creates a transport that applies each binding's provider only to requests for that binding's origin. At least one binding is required; origins must be distinct.

func (*AuthTransport) CloseIdleConnections

func (t *AuthTransport) CloseIdleConnections()

CloseIdleConnections forwards to the wrapped transport.

func (*AuthTransport) RoundTrip

func (t *AuthTransport) RoundTrip(req *http.Request) (*http.Response, error)

RoundTrip implements http.RoundTripper.

func (*AuthTransport) Unwrap

func (t *AuthTransport) Unwrap() http.RoundTripper

Unwrap returns the wrapped transport.

type BaseOption

type BaseOption func(*http.Transport)

BaseOption configures a base transport.

func WithDialer

func WithDialer(d *net.Dialer) BaseOption

WithDialer sets a custom dialer for establishing connections.

func WithDisableHTTP2

func WithDisableHTTP2() BaseOption

WithDisableHTTP2 disables HTTP/2, forcing HTTP/1.1.

func WithDisableKeepAlives

func WithDisableKeepAlives() BaseOption

WithDisableKeepAlives disables HTTP keep-alives. This forces a new connection for each request.

func WithExpectContinueTimeout

func WithExpectContinueTimeout(d time.Duration) BaseOption

WithExpectContinueTimeout sets how long the transport waits for a server's first response after sending request headers when the request had an "Expect: 100-continue" header (default: 1s).

func WithHTTP2

func WithHTTP2() BaseOption

WithHTTP2 enables HTTP/2 (this is the default).

func WithIdleConnTimeout

func WithIdleConnTimeout(d time.Duration) BaseOption

WithIdleConnTimeout sets how long idle connections remain open.

func WithMaxConnsPerHost

func WithMaxConnsPerHost(n int) BaseOption

WithMaxConnsPerHost limits the total number of connections per host. This includes dialing, active, and idle connections.

func WithMaxIdleConns

func WithMaxIdleConns(n int) BaseOption

WithMaxIdleConns sets the maximum number of idle connections across all hosts.

func WithMaxIdleConnsPerHost

func WithMaxIdleConnsPerHost(n int) BaseOption

WithMaxIdleConnsPerHost sets the maximum number of idle connections per host.

func WithReadBufferSize

func WithReadBufferSize(size int) BaseOption

WithReadBufferSize sets the read buffer size for connections.

func WithResponseHeaderTimeout

func WithResponseHeaderTimeout(d time.Duration) BaseOption

WithResponseHeaderTimeout sets the timeout for reading response headers.

func WithTLSConfig

func WithTLSConfig(cfg *tls.Config) BaseOption

WithTLSConfig sets a custom TLS configuration.

func WithTLSHandshakeTimeout

func WithTLSHandshakeTimeout(d time.Duration) BaseOption

WithTLSHandshakeTimeout sets the TLS handshake timeout.

func WithWriteBufferSize

func WithWriteBufferSize(size int) BaseOption

WithWriteBufferSize sets the write buffer size for connections.

type BasicAuthProvider

type BasicAuthProvider struct {
	Username string
	Password string
}

BasicAuthProvider adds HTTP Basic authentication.

func NewBasicAuthProvider

func NewBasicAuthProvider(username, password string) *BasicAuthProvider

NewBasicAuthProvider creates a new basic auth provider.

func (*BasicAuthProvider) AddAuth

func (p *BasicAuthProvider) AddAuth(req *http.Request) error

AddAuth implements AuthProvider.

type BearerTokenProvider

type BearerTokenProvider struct {
	RefreshFunc func() (string, error)
	// contains filtered or unexported fields
}

BearerTokenProvider adds a Bearer token to requests.

func NewBearerTokenProvider

func NewBearerTokenProvider(token string) *BearerTokenProvider

NewBearerTokenProvider creates a new bearer token provider.

func NewRefreshableBearerTokenProvider

func NewRefreshableBearerTokenProvider(token string, refresh func() (string, error)) *BearerTokenProvider

NewRefreshableBearerTokenProvider creates a bearer token provider with a refresh callback. The application must call Refresh to invoke it, or SetToken to replace the token directly. Requests and HTTP 401 responses do not trigger either operation automatically.

func (*BearerTokenProvider) AddAuth

func (p *BearerTokenProvider) AddAuth(req *http.Request) error

AddAuth implements AuthProvider.

func (*BearerTokenProvider) Refresh

func (p *BearerTokenProvider) Refresh() error

Refresh calls the refresh function and updates the token.

func (*BearerTokenProvider) SetToken

func (p *BearerTokenProvider) SetToken(token string)

SetToken updates the token.

type BufferPool

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

BufferPool is a sync.Pool-based buffer pool for reducing allocations.

func DefaultBufferPool

func DefaultBufferPool() *BufferPool

DefaultBufferPool returns the package's shared default BufferPool. The returned pool is the singleton — callers MUST treat it as read-only. Consumers that need to mutate (drain, replace) should construct their own via NewBufferPool.

func NewBufferPool

func NewBufferPool(size int) (*BufferPool, error)

NewBufferPool creates a buffer pool whose buffers start with size bytes of capacity; size must be >= 0.

func (*BufferPool) Get

func (p *BufferPool) Get() *bytes.Buffer

Get retrieves a buffer from the pool. The buffer is reset and ready for use.

func (*BufferPool) Put

func (p *BufferPool) Put(b *bytes.Buffer)

Put returns a buffer to the pool. Buffers that have grown significantly larger than the initial size are discarded to avoid holding excessive memory.

func (*BufferPool) Size

func (p *BufferPool) Size() int

Size returns the initial buffer size for this pool.

type CacheOption

type CacheOption func(*CacheTransport)

CacheOption configures a CacheTransport.

func WithCacheHook

func WithCacheHook(hook MetricsHook) CacheOption

WithCacheHook sets the metrics hook for hits and misses.

func WithCacheIdentityHeaders

func WithCacheIdentityHeaders(names ...string) CacheOption

WithCacheIdentityHeaders adds request headers that identify the caller (for example X-API-Key) to the partition key, alongside Authorization and Cookie. Other identity headers are not detected automatically.

func WithMaxCacheBodyBytes

func WithMaxCacheBodyBytes(n int64) CacheOption

WithMaxCacheBodyBytes caps the size of a stored body; larger responses pass through unstored. Default DefaultMaxCacheBodyBytes.

func WithMaxRetention

func WithMaxRetention(d time.Duration) CacheOption

WithMaxRetention caps how long any response is kept, including responses kept only for revalidation. Default DefaultMaxRetention.

func WithRevalidation

func WithRevalidation(timeout time.Duration, concurrency int) CacheOption

WithRevalidation sets the timeout and the concurrency cap for background (stale-while-revalidate) revalidations. Defaults DefaultRevalidateTimeout and DefaultMaxConcurrentRevalidates.

func WithStaleIfError

func WithStaleIfError(d time.Duration) CacheOption

WithStaleIfError lets the cache serve a stale response for up to d when revalidation returns an inner transport error (including local admission refusal) or 5xx, unless the response says must-revalidate or no-cache. Caller cancellation and expired caller deadlines remain errors. A response's own stale-if-error directive also grants permission.

func WithStaleWhileRevalidate

func WithStaleWhileRevalidate(d time.Duration) CacheOption

WithStaleWhileRevalidate lets the cache serve a stale response for up to d while it revalidates in the background, unless the response says must-revalidate or no-cache. A response's own stale-while-revalidate directive also grants permission.

type CacheStore

type CacheStore interface {
	Get(key string) (*CachedResponse, bool)
	Set(key string, value *CachedResponse, ttl time.Duration)
	Delete(key string)
}

CacheStore is the storage behind CacheTransport. Implementations must be safe for concurrent use and must treat stored values as immutable. ttl > 0 bounds how long an entry may be returned; ttl <= 0 means no expiry.

type CacheTransport

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

CacheTransport is a private HTTP cache (RFC 9111) for GET requests.

Stored responses are keyed by the effective request target — the URL with the request's Host as its authority — and partitioned by caller identity — the Authorization and Cookie headers plus any WithCacheIdentityHeaders. Callers must configure every additional identity header (including API-key headers) and use separate stores for identities hidden in a transport. These keys do not establish application authorization or tenant boundaries. Within a partition the cache honours Vary, corrected Age, the request directives no-store, no-cache, max-age, min-fresh, max-stale and only-if-cached, and the response directives no-store, no-cache, must-revalidate, max-age and Expires. A response without explicit freshness is stored only when it has a validator, and is revalidated on every use. Range, HEAD, caller-conditional and body-bearing requests bypass the cache; unsafe methods with a response below 400 invalidate the target URI within that request's partition, and a fetch or revalidation still in flight when they do never stores what it read.

func NewCacheTransport

func NewCacheTransport(base http.RoundTripper, store CacheStore, opts ...CacheOption) (*CacheTransport, error)

NewCacheTransport creates a cache transport over store; a nil store uses a default NewMemoryCache.

func (*CacheTransport) Close

func (t *CacheTransport) Close() error

Close stops background revalidations and waits for them to finish.

func (*CacheTransport) CloseIdleConnections

func (t *CacheTransport) CloseIdleConnections()

CloseIdleConnections forwards to the wrapped transport.

func (*CacheTransport) RoundTrip

func (t *CacheTransport) RoundTrip(req *http.Request) (*http.Response, error)

RoundTrip implements http.RoundTripper.

func (*CacheTransport) Unwrap

func (t *CacheTransport) Unwrap() http.RoundTripper

Unwrap returns the wrapped transport.

type CachedResponse

type CachedResponse struct {
	StatusCode   int
	Header       http.Header
	Body         []byte
	RequestTime  time.Time   // when the request that produced it was sent
	ResponseTime time.Time   // when it was received
	VaryHeaders  http.Header // request header values named by the response's Vary
}

CachedResponse is a response held by CacheTransport. Freshness is derived from Header (Cache-Control, Expires, Date, Age) and the request and response times (RFC 9111 §4.2).

type Chain

type Chain []TransportFunc

Chain composes RoundTripper decorators around a base transport. Build returns an http.RoundTripper for use with an ordinary http.Client.

Example:

transport, err := httpx.NewChain(
    httpx.WithCompressionT(),
    httpx.WithCircuitBreakerT(),
    httpx.WithRetryT(httpx.WithMaxAttempts(3)),
).Build(nil)

func APIClientChain

func APIClientChain() Chain

APIClientChain returns a chain for REST API clients: retry inside the cache, so a hit is never retried and a miss is.

func DefaultChain

func DefaultChain() Chain

DefaultChain returns compression, a circuit breaker and retry, in the NewClient order: the breaker is inside retry, so it counts every attempt.

func NewChain

func NewChain(funcs ...TransportFunc) Chain

NewChain creates a new transport chain. Transports are applied in order: first added is innermost (closest to base). Order is semantics, not style: a layer sees what the layers outside it send and counts what the layers inside it return. A circuit breaker inside retry counts every attempt, outside it only logical outcomes; a limiter or rate limit inside retry and hedging budgets every network attempt, outside them one logical call; a cache outside the breaker and the budgets serves hits without them, inside them not at all while the breaker is open; and a cache outside the auth layer would partition before the credential is set. NewClient's order (compression, circuit breaker, limiter, rate limit, retry, hedging, cache, auth, metrics, logging) is the one to start from.

func ResilientChain

func ResilientChain() Chain

ResilientChain returns a chain optimized for reliability, in NewClient's order: every attempt passes the breaker and holds a limiter slot, and the cache answers — stale if need be — even while the breaker is open.

func (Chain) Append

func (c Chain) Append(funcs ...TransportFunc) Chain

Append adds transport functions to the chain. Returns a new chain without modifying the original.

func (Chain) Build

func (c Chain) Build(base http.RoundTripper) (http.RoundTripper, error)

Build applies all transport functions to the base transport. If base is nil, a default NewBaseTransport is used.

func (Chain) Client

func (c Chain) Client(base http.RoundTripper, timeout time.Duration) (*http.Client, error)

Client creates an http.Client with the transport chain. If base is nil, a default NewBaseTransport is used. A negative timeout is rejected; zero disables the client timeout.

func (Chain) Prepend

func (c Chain) Prepend(funcs ...TransportFunc) Chain

Prepend adds transport functions to the beginning of the chain. Returns a new chain without modifying the original.

type ChainAuthProvider

type ChainAuthProvider struct {
	Providers []AuthProvider
}

ChainAuthProvider chains multiple auth providers.

func NewChainAuthProvider

func NewChainAuthProvider(providers ...AuthProvider) *ChainAuthProvider

NewChainAuthProvider creates a new chain of auth providers.

func (*ChainAuthProvider) AddAuth

func (p *ChainAuthProvider) AddAuth(req *http.Request) error

AddAuth implements AuthProvider by calling all providers in order.

type CircuitOption

type CircuitOption func(*CircuitTransport)

CircuitOption configures a CircuitTransport.

func WithFailureThreshold

func WithFailureThreshold(n int) CircuitOption

WithFailureThreshold sets how many consecutive failures open an origin's circuit. n must be within [1, math.MaxUint32].

func WithIsFailure

func WithIsFailure(fn func(*http.Response, error) bool) CircuitOption

WithIsFailure sets how a response or transport error is classified.

func WithMaxBreakers

func WithMaxBreakers(n int) CircuitOption

WithMaxBreakers caps how many origins keep a breaker. The least recently used origin beyond the cap loses its breaker and starts again closed; a request already admitted by the dropped breaker still completes, so the cap bounds the origins retained, not the requests in flight.

func WithOnStateChange

func WithOnStateChange(fn func(origin Origin, from, to State)) CircuitOption

WithOnStateChange sets a callback for an origin's state transitions, including Reset and eviction of a breaker that was not closed (both report a transition to closed). It runs synchronously, serialized with breaker state changes, so it must be quick and must not call back into the transport.

func WithOpenTimeout

func WithOpenTimeout(d time.Duration) CircuitOption

WithOpenTimeout sets how long a circuit stays open before admitting probes.

func WithSuccessThreshold

func WithSuccessThreshold(n int) CircuitOption

WithSuccessThreshold sets how many probes a half-open circuit admits and how many consecutive successes close it. n must be within [1, math.MaxUint32].

type CircuitTransport

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

CircuitTransport fails fast when an origin (scheme, host and port) is unhealthy. Each origin has its own breaker: consecutive failures open it, after the open timeout it admits up to the success threshold of probe requests, and that many consecutive successes close it again; any probe failure reopens it. A request cancelled by its caller counts as neither success nor failure, and an outcome that completes after its breaker changed state is ignored. A rejected request fails with ErrCircuitOpen, both while open and beyond the probe count while half-open. At most DefaultMaxOrigins breakers are kept (WithMaxBreakers); the least recently used origin is dropped beyond that and starts closed on its next request.

func NewCircuitTransport

func NewCircuitTransport(base http.RoundTripper, opts ...CircuitOption) (*CircuitTransport, error)

NewCircuitTransport creates a per-origin circuit breaker transport. Defaults: DefaultFailureThreshold, DefaultSuccessThreshold, DefaultOpenTimeout, DefaultMaxOrigins breakers; any error, nil response or 5xx from the wrapped transport is a failure, except caller cancellation. Deadline errors count as failures. Put local admission layers outside the breaker to keep their refusals out of these outcomes.

func (*CircuitTransport) CloseIdleConnections

func (t *CircuitTransport) CloseIdleConnections()

CloseIdleConnections forwards to the wrapped transport.

func (*CircuitTransport) Reset

func (t *CircuitTransport) Reset(origin Origin)

Reset gives origin a fresh, closed breaker.

func (*CircuitTransport) RoundTrip

func (t *CircuitTransport) RoundTrip(req *http.Request) (*http.Response, error)

RoundTrip implements http.RoundTripper.

func (*CircuitTransport) State

func (t *CircuitTransport) State(origin Origin) State

State returns origin's circuit state; an origin with no breaker is closed.

func (*CircuitTransport) Unwrap

func (t *CircuitTransport) Unwrap() http.RoundTripper

Unwrap returns the wrapped transport.

type Codec

type Codec interface {
	ContentType() string
	Encode(v any) ([]byte, error)
	Decode(data []byte, v any) error
}

Codec handles encoding/decoding for a content type.

var (
	JSON Codec = jsonCodec{}
	XML  Codec = xmlCodec{}
)

Built-in codec singletons (stdlib only).

func GetCodec

func GetCodec(contentType string) (Codec, bool)

GetCodec returns the registered codec for a content type, if any.

func RegisteredCodecs

func RegisteredCodecs() []Codec

RegisteredCodecs returns the registered codecs sorted by content type, so the Accept header built from them is the same on every request.

type CompressionOption

type CompressionOption func(*CompressionTransport)

CompressionOption configures a CompressionTransport.

func WithBrotliLevel

func WithBrotliLevel(level int) CompressionOption

WithBrotliLevel sets the brotli compression level (0-11). Requires brotli to be registered via import _ "github.com/ashtonian/pkg/httpx/compress/brotli"

func WithCompressionPool

func WithCompressionPool(pool *BufferPool) CompressionOption

WithCompressionPool sets the buffer pool.

func WithCompressor

func WithCompressor(encoding string, c Compressor) CompressionOption

WithCompressor registers a custom compressor for an encoding. This can also be used to override built-in compressors.

func WithDeflateLevel

func WithDeflateLevel(level int) CompressionOption

WithDeflateLevel sets the deflate compression level: flate.HuffmanOnly (-2), flate.DefaultCompression (-1), or 0 through 9.

func WithEncodings

func WithEncodings(encodings ...string) CompressionOption

WithEncodings sets the advertised encoding list without quality weights. Each encoding needs a registered compressor or an earlier WithCompressor option; an unavailable encoding makes construction fail.

func WithGzipLevel

func WithGzipLevel(level int) CompressionOption

WithGzipLevel sets the gzip compression level: gzip.HuffmanOnly (-2), gzip.DefaultCompression (-1), or 0 through 9.

type CompressionTransport

type CompressionTransport struct {
	Pool      *BufferPool
	Encodings []string // Priority order for Accept-Encoding
	// contains filtered or unexported fields
}

CompressionTransport negotiates response content encodings and lazily decompresses supported complete responses. It does not compress outgoing request bodies; callers can use Compress explicitly for that purpose.

func NewCompressionTransport

func NewCompressionTransport(base http.RoundTripper, opts ...CompressionOption) (*CompressionTransport, error)

NewCompressionTransport creates a new compression transport.

By default, it advertises registered brotli, gzip and deflate encodings, in that order without quality weights. The server chooses the encoding. Select other registered encodings with WithEncodings or add an implementation with WithCompressor. The transport automatically decompresses complete response bodies. Partial representations retain their encoding and byte offsets; Range and HEAD requests do not receive an automatic Accept-Encoding header.

To enable brotli support, import the compress/brotli submodule:

import _ "github.com/ashtonian/pkg/httpx/compress/brotli"

func (*CompressionTransport) CloseIdleConnections

func (t *CompressionTransport) CloseIdleConnections()

CloseIdleConnections forwards to the wrapped transport.

func (*CompressionTransport) Compress

func (t *CompressionTransport) Compress(data []byte, encoding string) ([]byte, error)

Compress compresses data using the specified encoding.

func (*CompressionTransport) RoundTrip

func (t *CompressionTransport) RoundTrip(req *http.Request) (*http.Response, error)

RoundTrip implements http.RoundTripper.

func (*CompressionTransport) Unwrap

Unwrap returns the wrapped transport.

type Compressor

type Compressor interface {
	// Compress compresses data, using the provided buffer for output.
	// Returns the compressed bytes (may be a slice of buf's backing array).
	Compress(data []byte, buf *bytes.Buffer) ([]byte, error)

	// Decompress wraps a reader with decompression.
	Decompress(r io.Reader) (io.Reader, error)
}

Compressor handles compression and decompression for a specific encoding. Implement this interface to add custom compression algorithms.

func GetCompressor

func GetCompressor(encoding string) (Compressor, bool)

GetCompressor returns a new compressor instance for the encoding, if registered.

type CountingHook

type CountingHook struct {
	Requests    atomic.Int64
	Responses   atomic.Int64
	Errors      atomic.Int64
	Retries     atomic.Int64
	CacheHits   atomic.Int64
	CacheMisses atomic.Int64
}

CountingHook is a simple MetricsHook that counts events. All fields are safe for concurrent use.

func (*CountingHook) OnCacheHit

func (h *CountingHook) OnCacheHit(_ *http.Request, _ bool)

func (*CountingHook) OnCacheMiss

func (h *CountingHook) OnCacheMiss(_ *http.Request)

func (*CountingHook) OnRequest

func (h *CountingHook) OnRequest(*http.Request)

func (*CountingHook) OnResponse

func (h *CountingHook) OnResponse(_ *http.Request, _ *http.Response, _ time.Duration, err error)

func (*CountingHook) OnRetry

func (h *CountingHook) OnRetry(_ *http.Request, _ int, _ error)

type DecodeOption

type DecodeOption func(*decodeConfig)

DecodeOption configures the decoding helpers (Get, Post, Put, Patch, Do and DecodeResponse).

func WithMaxResponseBytes

func WithMaxResponseBytes(n int64) DecodeOption

WithMaxResponseBytes caps the response body a helper reads before decoding. A larger body fails with ErrResponseTooLarge instead of being decoded from a prefix. Default DefaultMaxResponseBytes.

type FuncAuthProvider

type FuncAuthProvider func(*http.Request) error

FuncAuthProvider wraps a function as an AuthProvider.

func (FuncAuthProvider) AddAuth

func (f FuncAuthProvider) AddAuth(req *http.Request) error

AddAuth implements AuthProvider.

type HedgingOption

type HedgingOption func(*HedgingTransport)

HedgingOption configures a HedgingTransport.

func WithHedgeIf

func WithHedgeIf(fn func(*http.Request) bool) HedgingOption

WithHedgeIf sets a function to determine whether to hedge a request.

func WithMaxHedgedRequests

func WithMaxHedgedRequests(n int) HedgingOption

WithMaxHedgedRequests sets the maximum number of concurrent requests.

type HedgingTransport

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

HedgingTransport implements request hedging for tail latency reduction.

func NewHedgingTransport

func NewHedgingTransport(base http.RoundTripper, delay time.Duration, opts ...HedgingOption) (*HedgingTransport, error)

NewHedgingTransport creates a new hedging transport.

delay is the time to wait before sending a hedge request. By default, up to 2 concurrent requests are allowed.

func (*HedgingTransport) CloseIdleConnections

func (t *HedgingTransport) CloseIdleConnections()

CloseIdleConnections forwards to the wrapped transport.

func (*HedgingTransport) RoundTrip

func (t *HedgingTransport) RoundTrip(req *http.Request) (*http.Response, error)

RoundTrip implements http.RoundTripper.

Each attempt runs under its own cancellable context. The first attempt to return a response wins: every other attempt is cancelled and its response, if one still arrives, is drained and closed. The winner's context stays alive until the caller finishes with its body, so a delayed or streaming body stays readable.

func (*HedgingTransport) Unwrap

func (t *HedgingTransport) Unwrap() http.RoundTripper

Unwrap returns the wrapped transport.

type LatencyHook

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

LatencyHook records request latencies: running aggregates (count, sum, min, max) over every response since creation or Reset, and the most recent window of samples. Memory is bounded by the window. All methods are safe for concurrent use.

func NewLatencyHook

func NewLatencyHook(window int) (*LatencyHook, error)

NewLatencyHook creates a LatencyHook keeping the last window samples.

func (*LatencyHook) Avg

func (h *LatencyHook) Avg() time.Duration

Avg returns the mean of all recorded latencies.

func (*LatencyHook) Count

func (h *LatencyHook) Count() int64

Count returns how many latencies were recorded.

func (*LatencyHook) Max

func (h *LatencyHook) Max() time.Duration

Max returns the largest recorded latency.

func (*LatencyHook) Min

func (h *LatencyHook) Min() time.Duration

Min returns the smallest recorded latency.

func (*LatencyHook) OnCacheHit

func (h *LatencyHook) OnCacheHit(*http.Request, bool)

func (*LatencyHook) OnCacheMiss

func (h *LatencyHook) OnCacheMiss(*http.Request)

func (*LatencyHook) OnRequest

func (h *LatencyHook) OnRequest(*http.Request)

func (*LatencyHook) OnResponse

func (h *LatencyHook) OnResponse(_ *http.Request, _ *http.Response, duration time.Duration, _ error)

func (*LatencyHook) OnRetry

func (h *LatencyHook) OnRetry(*http.Request, int, error)

func (*LatencyHook) Recent

func (h *LatencyHook) Recent() []time.Duration

Recent returns the samples in the window, oldest first.

func (*LatencyHook) Reset

func (h *LatencyHook) Reset()

Reset clears the aggregates and the window.

func (*LatencyHook) Sum

func (h *LatencyHook) Sum() time.Duration

Sum returns the total of all recorded latencies.

type LimiterOption

type LimiterOption func(*LimiterTransport)

LimiterOption configures a LimiterTransport.

func WithAcquireTimeout

func WithAcquireTimeout(d time.Duration) LimiterOption

WithAcquireTimeout sets the timeout for acquiring a slot. Default is 0 (wait until a slot is available or the request context ends).

func WithOnBlocked

func WithOnBlocked(fn func()) LimiterOption

WithOnBlocked sets a callback for when a request blocks waiting for a slot.

type LimiterTransport

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

LimiterTransport limits concurrent in-flight requests. A request holds its slot from before it is sent until its response body is read to the end or closed; callers must close response bodies to free their slot.

func NewLimiterTransport

func NewLimiterTransport(base http.RoundTripper, maxConcurrent int, opts ...LimiterOption) (*LimiterTransport, error)

NewLimiterTransport creates a new concurrency limiter transport.

func (*LimiterTransport) Available

func (t *LimiterTransport) Available() int

Available returns the number of available slots.

func (*LimiterTransport) Capacity

func (t *LimiterTransport) Capacity() int

Capacity returns the maximum number of concurrent requests.

func (*LimiterTransport) CloseIdleConnections

func (t *LimiterTransport) CloseIdleConnections()

CloseIdleConnections forwards to the wrapped transport.

func (*LimiterTransport) InFlight

func (t *LimiterTransport) InFlight() int

InFlight returns the number of in-flight requests.

func (*LimiterTransport) RoundTrip

func (t *LimiterTransport) RoundTrip(req *http.Request) (*http.Response, error)

RoundTrip implements http.RoundTripper.

func (*LimiterTransport) Unwrap

func (t *LimiterTransport) Unwrap() http.RoundTripper

Unwrap returns the wrapped transport.

type LogEntry

type LogEntry struct {
	Type        string            `json:"type"` // "request" or "response"
	Method      string            `json:"method,omitempty"`
	URL         string            `json:"url,omitempty"`
	StatusCode  int               `json:"status_code,omitempty"`
	Duration    time.Duration     `json:"duration,omitempty"`
	Headers     map[string]string `json:"headers,omitempty"`
	ContentType string            `json:"content_type,omitempty"`
	Error       string            `json:"error,omitempty"`
}

LogEntry represents a structured log entry.

type LoggingHook

type LoggingHook interface {
	LogRequest(req *http.Request)
	LogResponse(req *http.Request, resp *http.Response, duration time.Duration, err error)
}

LoggingHook receives logging events from a LoggingTransport. Both methods receive redacted snapshots without bodies (see LoggingTransport); resp is nil and err non-nil when the request failed.

type LoggingOption

type LoggingOption func(*LoggingTransport)

LoggingOption configures a LoggingTransport.

func WithSensitiveHeaders

func WithSensitiveHeaders(names ...string) LoggingOption

WithSensitiveHeaders redacts the named headers in addition to the defaults. Names are matched case-insensitively.

func WithSensitiveQueryParams

func WithSensitiveQueryParams(names ...string) LoggingOption

WithSensitiveQueryParams redacts the named query parameters in addition to the defaults. Names are matched case-insensitively.

type LoggingTransport

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

LoggingTransport reports each request and response to a LoggingHook.

The hook never sees the live request or response: it receives detached snapshots built from an allowlist of fields — method, URL, protocol, header, host, length and transfer coding, and the request context — with no body, no redirect response and no TLS state. Sensitive header values and credential query parameters are replaced with Redacted; URL userinfo and fragments are removed, from the request URL and from URL-valued headers (Referer, Location, Content-Location) alike; and an error is reported with every such URL in its text redacted. The hook's error is detached from the original cause, which only the caller receives. Redaction is always on; WithSensitiveHeaders and WithSensitiveQueryParams extend the defaults.

The guarantee covers what this transport hands its LoggingHook. Hooks of other transports — MetricsHook, RetryHook, the cache's hook — receive the live request, because they act on it rather than record it; do not log from them.

func NewLoggingTransport

func NewLoggingTransport(base http.RoundTripper, hook LoggingHook, opts ...LoggingOption) (*LoggingTransport, error)

NewLoggingTransport creates a new logging transport. A nil hook logs nothing.

func (*LoggingTransport) CloseIdleConnections

func (t *LoggingTransport) CloseIdleConnections()

CloseIdleConnections forwards to the wrapped transport.

func (*LoggingTransport) RoundTrip

func (t *LoggingTransport) RoundTrip(req *http.Request) (*http.Response, error)

RoundTrip implements http.RoundTripper.

func (*LoggingTransport) Unwrap

func (t *LoggingTransport) Unwrap() http.RoundTripper

Unwrap returns the wrapped transport.

type MemoryCache

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

MemoryCache is a bounded in-memory CacheStore: it keeps at most maxEntries responses and maxBytes of stored bodies and headers, evicting the least recently used entry first, and drops entries whose TTL has passed.

func NewMemoryCache

func NewMemoryCache(opts ...MemoryCacheOption) (*MemoryCache, error)

NewMemoryCache creates a bounded in-memory cache. Defaults: DefaultMemoryCacheMaxEntries entries, DefaultMemoryCacheMaxBytes bytes.

func (*MemoryCache) Bytes

func (c *MemoryCache) Bytes() int64

Bytes returns the accounted size of stored responses.

func (*MemoryCache) Delete

func (c *MemoryCache) Delete(key string)

Delete removes key.

func (*MemoryCache) Get

func (c *MemoryCache) Get(key string) (*CachedResponse, bool)

Get returns the stored response for key, if it has not expired.

func (*MemoryCache) Len

func (c *MemoryCache) Len() int

Len returns the number of stored responses.

func (*MemoryCache) Set

func (c *MemoryCache) Set(key string, value *CachedResponse, ttl time.Duration)

Set stores value under key for ttl; ttl <= 0 means no expiry (the entry still leaves on eviction). Stored values must not be modified afterwards.

type MemoryCacheOption

type MemoryCacheOption func(*MemoryCache)

MemoryCacheOption configures a MemoryCache.

func WithMaxBytes

func WithMaxBytes(n int64) MemoryCacheOption

WithMaxBytes caps the total size of stored bodies and headers. A single response larger than the cap is not stored.

func WithMaxEntries

func WithMaxEntries(n int) MemoryCacheOption

WithMaxEntries caps the number of stored responses.

type MetricsHook

type MetricsHook interface {
	OnRequest(req *http.Request)
	OnResponse(req *http.Request, resp *http.Response, duration time.Duration, err error)
	OnRetry(req *http.Request, attempt int, err error)
	OnCacheHit(req *http.Request, fresh bool)
	OnCacheMiss(req *http.Request)
}

MetricsHook receives transport events for observability.

type MetricsTransport

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

MetricsTransport reports each request and its response or error to a MetricsHook.

func NewMetricsTransport

func NewMetricsTransport(base http.RoundTripper, hook MetricsHook) (*MetricsTransport, error)

NewMetricsTransport creates a new metrics transport.

func (*MetricsTransport) CloseIdleConnections

func (t *MetricsTransport) CloseIdleConnections()

CloseIdleConnections forwards to the wrapped transport.

func (*MetricsTransport) RoundTrip

func (t *MetricsTransport) RoundTrip(req *http.Request) (*http.Response, error)

RoundTrip implements http.RoundTripper.

func (*MetricsTransport) Unwrap

func (t *MetricsTransport) Unwrap() http.RoundTripper

Unwrap returns the wrapped transport.

type MultiHook

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

MultiHook fans every event out to several MetricsHooks, including OnRetryCause for those that implement RetryCauseHook.

func NewMultiHook

func NewMultiHook(hooks ...MetricsHook) *MultiHook

NewMultiHook combines hooks; nil hooks are skipped.

func (*MultiHook) OnCacheHit

func (h *MultiHook) OnCacheHit(req *http.Request, fresh bool)

func (*MultiHook) OnCacheMiss

func (h *MultiHook) OnCacheMiss(req *http.Request)

func (*MultiHook) OnRequest

func (h *MultiHook) OnRequest(req *http.Request)

func (*MultiHook) OnResponse

func (h *MultiHook) OnResponse(req *http.Request, resp *http.Response, duration time.Duration, err error)

func (*MultiHook) OnRetry

func (h *MultiHook) OnRetry(req *http.Request, attempt int, err error)

func (*MultiHook) OnRetryCause

func (h *MultiHook) OnRetryCause(req *http.Request, attempt int, cause RetryCause, err error)

OnRetryCause forwards to the hooks that implement RetryCauseHook.

type Option

type Option func(*clientConfig)

Option configures a client.

func WithAuth

func WithAuth(originURL string, provider AuthProvider) Option

WithAuth binds provider to the origin (scheme, host and port) of originURL; any path is ignored. Credentials are added only to requests for that origin — never to another host, port or scheme, including redirect hops the client follows. Repeat WithAuth to bind further origins.

func WithBaseTransport

func WithBaseTransport(opts ...BaseOption) Option

WithBaseTransport configures the base http.Transport.

func WithCache

func WithCache(store CacheStore, opts ...CacheOption) Option

WithCache enables the RFC 9111 private cache (see CacheTransport). A nil store uses a default NewMemoryCache.

func WithCircuitBreaker

func WithCircuitBreaker(opts ...CircuitOption) Option

WithCircuitBreaker enables circuit breaker.

func WithCompression

func WithCompression(opts ...CompressionOption) Option

WithCompression enables response compression negotiation and lazy decoding. It does not compress request bodies.

func WithHedging

func WithHedging(delay time.Duration, opts ...HedgingOption) Option

WithHedging enables request hedging.

func WithJar

func WithJar(jar http.CookieJar) Option

WithJar sets the cookie jar.

func WithLimiter

func WithLimiter(maxConcurrent int, opts ...LimiterOption) Option

WithLimiter enables concurrency limiting.

func WithLogging

func WithLogging(hook LoggingHook, opts ...LoggingOption) Option

WithLogging enables request/response logging.

func WithMetrics

func WithMetrics(hook MetricsHook) Option

WithMetrics enables metrics collection.

func WithRateLimit

func WithRateLimit(rps float64, burst int, opts ...RateLimitOption) Option

WithRateLimit enables rate limiting.

func WithRetry

func WithRetry(opts ...RetryOption) Option

WithRetry enables retry with exponential backoff.

func WithTimeout

func WithTimeout(d time.Duration) Option

WithTimeout sets the overall client timeout (applied to the whole request including body read). Pass 0 to disable; default is DefaultTimeout.

func WithTransport

func WithTransport(t http.RoundTripper) Option

WithTransport sets a custom base transport. The configured base transport options (WithBaseTransport) are ignored when this is set.

type Origin

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

Origin is the scheme, host and port of a URL (RFC 6454): the boundary a credential is bound to. https://api.example.com and http://api.example.com are different origins, as are two ports on one host. The zero Origin matches no request.

func OriginOf

func OriginOf(u *url.URL) Origin

OriginOf returns the origin of u, or the zero Origin when u is nil, not http(s), or has no host.

func ParseOrigin

func ParseOrigin(rawURL string) (Origin, error)

ParseOrigin returns the origin of an absolute http or https URL. Any path, query or fragment is ignored; userinfo is rejected so a credential cannot hide in a binding. Errors never repeat the input, which may hold a credential: a rejected URL is described by its scheme and host at most.

func (Origin) IsZero

func (o Origin) IsZero() bool

IsZero reports whether o is the zero Origin.

func (Origin) String

func (o Origin) String() string

String returns the origin as scheme://host:port.

type QueryParamAuthProvider

type QueryParamAuthProvider struct {
	Key   string // Query parameter name
	Value string // Query parameter value
}

QueryParamAuthProvider adds authentication as a query parameter.

func NewQueryParamAuthProvider

func NewQueryParamAuthProvider(key, value string) *QueryParamAuthProvider

NewQueryParamAuthProvider creates a new query parameter auth provider.

func (*QueryParamAuthProvider) AddAuth

func (p *QueryParamAuthProvider) AddAuth(req *http.Request) error

AddAuth implements AuthProvider.

type RateLimitOption

type RateLimitOption func(*RateLimitTransport)

RateLimitOption configures a RateLimitTransport.

func WithMaxBuckets

func WithMaxBuckets(n int) RateLimitOption

WithMaxBuckets caps how many origins keep a token bucket.

func WithWait

func WithWait() RateLimitOption

WithWait makes a request over the limit wait for a token instead of failing. The wait honours the request context; a wait that would outlast the context's deadline fails with ErrRateLimited at once.

type RateLimitTransport

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

RateLimitTransport throttles requests with one token bucket per origin (scheme, host and port): each origin may send rps requests per second with bursts up to burst. At most DefaultMaxOrigins buckets are kept (WithMaxBuckets); the least recently used origin is dropped beyond that and starts with a full bucket on its next request. A request over the limit fails with ErrRateLimited, or waits for a token under WithWait.

func NewRateLimitTransport

func NewRateLimitTransport(base http.RoundTripper, rps float64, burst int, opts ...RateLimitOption) (*RateLimitTransport, error)

NewRateLimitTransport creates a per-origin rate limiting transport.

func (*RateLimitTransport) CloseIdleConnections

func (t *RateLimitTransport) CloseIdleConnections()

CloseIdleConnections forwards to the wrapped transport.

func (*RateLimitTransport) RoundTrip

func (t *RateLimitTransport) RoundTrip(req *http.Request) (*http.Response, error)

RoundTrip implements http.RoundTripper.

func (*RateLimitTransport) Unwrap

func (t *RateLimitTransport) Unwrap() http.RoundTripper

Unwrap returns the wrapped transport.

type RetryCause

type RetryCause string

RetryCause groups retries into status, network, timeout or unknown causes. The status category combines HTTP 429, 502, 503 and 504; it does not distinguish rate-limit responses from server failures.

const (
	// RetryCauseStatus — server returned a retryable HTTP status
	// (502/503/504/429).
	RetryCauseStatus RetryCause = "status"
	// RetryCauseNetwork — network-level error (connection reset,
	// connection refused, broken pipe, EOF).
	RetryCauseNetwork RetryCause = "network"
	// RetryCauseTimeout — net.Error.Timeout() was true.
	RetryCauseTimeout RetryCause = "timeout"
	// RetryCauseUnknown — retryIf returned true but the cause didn't
	// match any of the above. Caller's custom retry condition.
	RetryCauseUnknown RetryCause = "unknown"
)

RetryCause values.

type RetryCauseHook

type RetryCauseHook interface {
	OnRetryCause(req *http.Request, attempt int, cause RetryCause, err error)
}

RetryCauseHook is an OPTIONAL extension to MetricsHook. RetryTransport type-asserts the hook and calls OnRetryCause in addition to OnRetry when the assertion succeeds. Implementations that don't care about per-cause labelling don't need to implement it.

Splitting the cause from the legacy OnRetry signature avoids breaking every existing MetricsHook implementation while still letting modern observability stacks emit labelled counters.

type RetryOption

type RetryOption func(*RetryTransport)

RetryOption configures a RetryTransport.

func WithBackoff

func WithBackoff(initial, max time.Duration, multiplier float64) RetryOption

WithBackoff sets the backoff parameters. max also bounds a server's Retry-After: a response asking for a longer pause is returned to the caller instead of retried.

func WithJitter

func WithJitter(jitter float64) RetryOption

WithJitter sets the jitter factor (0.0-1.0).

func WithMaxAttempts

func WithMaxAttempts(n int) RetryOption

WithMaxAttempts sets the maximum number of attempts.

func WithMaxReplayBytes

func WithMaxReplayBytes(n int64) RetryOption

WithMaxReplayBytes caps how much of a request body without GetBody is recorded, as the first attempt sends it, for replay. A larger body is sent once.

func WithRetryHook

func WithRetryHook(hook MetricsHook) RetryOption

WithRetryHook sets the metrics hook for retry events.

func WithRetryIf

func WithRetryIf(fn func(*http.Response, error) bool) RetryOption

WithRetryIf sets a custom retry condition.

func WithRetryNonIdempotent

func WithRetryNonIdempotent() RetryOption

WithRetryNonIdempotent also replays non-idempotent requests (POST, PATCH, ...) that carry no Idempotency-Key. Use it only when the server deduplicates repeated requests; otherwise an ambiguous failure can execute the operation more than once.

func WithRetryOn

func WithRetryOn(codes ...int) RetryOption

WithRetryOn sets the status codes that trigger a retry.

type RetryTransport

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

RetryTransport implements exponential backoff with jitter.

A request is eligible for replay when its method is idempotent (RFC 9110 §9.2.2: GET, HEAD, OPTIONS, TRACE, PUT, DELETE), it carries an Idempotency-Key header, or the transport was built with WithRetryNonIdempotent. The application must ensure the server makes replay safe; this transport cannot deduplicate writes. Retried attempts clone the request metadata, but still consume and close request bodies.

func NewRetryTransport

func NewRetryTransport(base http.RoundTripper, opts ...RetryOption) (*RetryTransport, error)

NewRetryTransport creates a new retry transport.

Defaults: 3 attempts, 100ms initial wait, 10s max wait, 2x multiplier, 10% jitter, 1 MiB recording bound for bodies without GetBody. Retries on 502, 503, 504, 429, connection reset/refused/aborted, broken pipe, EOF and network timeouts; caller cancellation and deadline errors are excluded.

func (*RetryTransport) CloseIdleConnections

func (t *RetryTransport) CloseIdleConnections()

CloseIdleConnections forwards to the wrapped transport.

func (*RetryTransport) RoundTrip

func (t *RetryTransport) RoundTrip(req *http.Request) (*http.Response, error)

RoundTrip implements http.RoundTripper.

func (*RetryTransport) Unwrap

func (t *RetryTransport) Unwrap() http.RoundTripper

Unwrap returns the wrapped transport.

type State

type State int

State represents the circuit breaker state.

const (
	// StateClosed is the normal operating state.
	StateClosed State = iota
	// StateOpen is when the circuit is tripped and failing fast.
	StateOpen
	// StateHalfOpen is when the circuit is testing if the backend is healthy.
	StateHalfOpen
)

func (State) String

func (s State) String() string

String returns the string representation of the state.

type StructuredLoggingHook

type StructuredLoggingHook struct {
	LogFunc func(entry LogEntry)
}

StructuredLoggingHook is a LoggingHook that captures structured data.

func (*StructuredLoggingHook) LogRequest

func (h *StructuredLoggingHook) LogRequest(req *http.Request)

LogRequest implements LoggingHook.

func (*StructuredLoggingHook) LogResponse

func (h *StructuredLoggingHook) LogResponse(req *http.Request, resp *http.Response, duration time.Duration, err error)

LogResponse implements LoggingHook.

type TransportFunc

type TransportFunc func(http.RoundTripper) (http.RoundTripper, error)

TransportFunc wraps a transport, returning an error when its configuration is invalid. This is the building block for the Chain API.

func WithAuthT

func WithAuthT(originURL string, provider AuthProvider) TransportFunc

WithAuthT returns a TransportFunc that binds provider to the origin of originURL (see WithAuth).

func WithCacheT

func WithCacheT(store CacheStore, opts ...CacheOption) TransportFunc

WithCacheT returns a TransportFunc that adds caching.

func WithCircuitBreakerT

func WithCircuitBreakerT(opts ...CircuitOption) TransportFunc

WithCircuitBreakerT returns a TransportFunc that adds circuit breaker.

func WithCompressionT

func WithCompressionT(opts ...CompressionOption) TransportFunc

WithCompressionT returns a TransportFunc that adds compression.

func WithHedgingT

func WithHedgingT(delay time.Duration, opts ...HedgingOption) TransportFunc

WithHedgingT returns a TransportFunc that adds request hedging.

func WithLimiterT

func WithLimiterT(maxConcurrent int, opts ...LimiterOption) TransportFunc

WithLimiterT returns a TransportFunc that adds concurrency limiting.

func WithLoggingT

func WithLoggingT(hook LoggingHook, opts ...LoggingOption) TransportFunc

WithLoggingT returns a TransportFunc that adds logging.

func WithMetricsT

func WithMetricsT(hook MetricsHook) TransportFunc

WithMetricsT returns a TransportFunc that adds metrics.

func WithRateLimitT

func WithRateLimitT(rps float64, burst int, opts ...RateLimitOption) TransportFunc

WithRateLimitT returns a TransportFunc that adds rate limiting.

func WithRetryT

func WithRetryT(opts ...RetryOption) TransportFunc

WithRetryT returns a TransportFunc that adds retry with backoff.

Directories

Path Synopsis
codec
msgpack module
compress
brotli module
examples
quickstart command
Command quickstart is the README Quick Start: compose HTTP compression, retry/backoff, and a private validator-aware cache around an ordinary http.Client.
Command quickstart is the README Quick Start: compose HTTP compression, retry/backoff, and a private validator-aware cache around an ordinary http.Client.
internal
testutil
Package testutil provides testing utilities for the httpx package.
Package testutil provides testing utilities for the httpx package.

Jump to

Keyboard shortcuts

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