Documentation
¶
Overview ¶
Package httpx provides a resilient outbound-HTTP toolkit: transient-error classification, generic retry with jittered exponential backoff, Retry-After parsing, HTTP status mapping, secret redaction, body draining, and a configurable redirect allowlist.
Index ¶
- Constants
- Variables
- func CheckHTTPStatus(resp *http.Response) error
- func Close(c *http.Client)
- func DefaultRedirectPolicy(req *http.Request, via []*http.Request) error
- func DockerGitHubRedirectPolicy(req *http.Request, via []*http.Request) error
- func Drain(body io.ReadCloser)
- func DrainClose(rc io.ReadCloser)
- func IsPermanent(err error) bool
- func IsTransient(err error) bool
- func JitteredBackoff(backoff time.Duration) time.Duration
- func LimitedBody(resp *http.Response, limit int64) io.ReadCloser
- func NewClient(timeout time.Duration) *http.Client
- func ParseRetryAfter(h string) time.Duration
- func ParseRetryAfterResponse(resp *http.Response) time.Duration
- func Permanent(err error) error
- func RedactSecret(err error, secret string) error
- func RedactTransportError(err error, prefix, secret string) error
- func RedirectPolicyFunc(opts ...RedirectOption) func(*http.Request, []*http.Request) error
- func Retry(ctx context.Context, client *http.Client, reqURL string, opts ...Option) ([]byte, error)
- func RetryOnRateLimit(ctx context.Context, maxAttempts int, maxWait time.Duration, ...) error
- func RetryWithBackoff[T any](ctx context.Context, maxRetries int, baseDelay time.Duration, label string, ...) (T, error)
- func SafeDouble(d time.Duration) time.Duration
- func SleepCtx(ctx context.Context, d time.Duration) error
- type AuthError
- type Backoff
- type CheckRetry
- type ExpBackoffOption
- type ExponentialBackoff
- type HTTPStatusError
- type OnRetry
- type Option
- type PermanentError
- type PrepareRetry
- type RTOption
- func WithBackoff(b Backoff) RTOption
- func WithCheckRetry(cr CheckRetry) RTOption
- func WithMaxRetries(n int) RTOption
- func WithOnRetry(fn OnRetry) RTOption
- func WithPrepareRetry(fn PrepareRetry) RTOption
- func WithRTBaseDelay(d time.Duration) RTOption
- func WithRTMaxElapsedTime(d time.Duration) RTOption
- func WithRetryNonIdempotent(enable bool) RTOption
- type RateLimitError
- type RedirectOption
- type RetryRoundTripper
- type StatusError
- type Transient
Examples ¶
Constants ¶
const ( // DefaultBaseDelay is the production base for exponential-backoff retry. DefaultBaseDelay = time.Second // DefaultMaxAttempts caps Retry at three tries. DefaultMaxAttempts = 3 // DefaultMaxBodyBytes caps response bodies at 10 MB. DefaultMaxBodyBytes int64 = 10 << 20 // RetryAfterCap is the maximum Retry-After honor duration. RetryAfterCap = 60 * time.Second )
const BackoffStop time.Duration = -1
BackoffStop signals that no more retries should be made.
Variables ¶
var ErrRateLimited = errors.New("rate limited")
ErrRateLimited is a sentinel callers use with errors.Is to detect 429 responses.
var ErrServerError = errors.New("server error")
ErrServerError is a sentinel for upstream 5xx responses.
var RedirectPolicy = DockerGitHubRedirectPolicy
RedirectPolicy is a legacy alias for DockerGitHubRedirectPolicy, kept for backward compatibility. New code should use DefaultRedirectPolicy (same-host only, used by NewClient) or DockerGitHubRedirectPolicy explicitly.
Deprecated: Use DefaultRedirectPolicy or DockerGitHubRedirectPolicy directly.
Functions ¶
func CheckHTTPStatus ¶
CheckHTTPStatus maps HTTP error status codes to typed errors. Returns nil for 2xx/3xx. 401/403 → *AuthError, 429 → *RateLimitError, others ≥400 → *HTTPStatusError.
func DefaultRedirectPolicy ¶
DefaultRedirectPolicy is the default redirect policy: it denies cross-host redirects, allowing only redirects to the same host as the original request. For custom allowlists, use RedirectPolicyFunc.
func DockerGitHubRedirectPolicy ¶
DockerGitHubRedirectPolicy is an OPTIONAL example redirect policy allowing docker.com and github.com hosts. Use it by assigning to Client.CheckRedirect or pass RedirectOption values to RedirectPolicyFunc for other allowlists.
func Drain ¶
func Drain(body io.ReadCloser)
Drain reads and discards up to 64 KB of a response body to enable HTTP connection reuse.
func DrainClose ¶
func DrainClose(rc io.ReadCloser)
DrainClose reads remaining bytes (up to drainLimit) from rc before closing it.
func IsPermanent ¶
IsPermanent reports whether err (or any wrapped error) is a *PermanentError.
func IsTransient ¶
IsTransient returns true for errors likely caused by temporary server or network issues worth retrying. Auth, rate-limit, permanent, and context errors are never transient.
func JitteredBackoff ¶
JitteredBackoff returns a duration in [backoff/2, backoff] using the "equal jitter" strategy (per AWS Builders' Library). Full jitter and decorrelated jitter are intentionally not provided — equal jitter is the recommended default for HTTP retry as it avoids thundering herd while maintaining a minimum backoff floor.
func LimitedBody ¶
func LimitedBody(resp *http.Response, limit int64) io.ReadCloser
LimitedBody wraps resp.Body with an io.LimitReader capped at limit bytes, preserving the original Close method.
func NewClient ¶
NewClient returns an *http.Client with the given timeout and the DefaultRedirectPolicy (same-host only). For custom redirect allowlists, configure CheckRedirect with RedirectPolicyFunc or assign DockerGitHubRedirectPolicy.
func ParseRetryAfter ¶
ParseRetryAfter parses a Retry-After header value (delta-seconds or HTTP-date). Returns zero for missing/malformed values. Caps at RetryAfterCap for safety (prevents unbounded waits in retry loops). For raw uncapped values, use ParseRetryAfterResponse.
func ParseRetryAfterResponse ¶
ParseRetryAfterResponse parses the Retry-After header from an *http.Response. Returns zero if absent or unparseable. Does NOT cap — preserves the raw duration so callers (e.g., CheckHTTPStatus) can make their own decisions. For capped values suitable for retry loops, use ParseRetryAfter.
func Permanent ¶
Permanent wraps err to indicate it should never be retried. Mirrors cenkalti/backoff.Permanent().
func RedactSecret ¶
RedactSecret replaces occurrences of secret in err's message with "REDACTED".
func RedactTransportError ¶
RedactTransportError unwraps *url.Error and redacts the secret from the error message. Returns nil for nil input.
func RedirectPolicyFunc ¶
RedirectPolicyFunc returns a CheckRedirect function configured with the given options. With no options, all redirects are refused.
func Retry ¶
Retry performs an HTTP GET with bounded exponential-backoff retry on 429 and 5xx responses. 4xx (non-429) and transport errors are returned immediately. Honors Retry-After (capped at RetryAfterCap).
Example ¶
package main
import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"time"
"github.com/cplieger/httpx"
)
func main() {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
fmt.Fprint(w, "hello")
}))
defer srv.Close()
body, err := httpx.Retry(context.Background(), srv.Client(), srv.URL,
httpx.WithMaxAttempts(3),
httpx.WithBaseDelay(100*time.Millisecond),
)
if err != nil {
fmt.Println("error:", err)
return
}
fmt.Println(string(body))
}
Output: hello
func RetryOnRateLimit ¶
func RetryOnRateLimit(ctx context.Context, maxAttempts int, maxWait time.Duration, fn func(ctx context.Context) error) error
RetryOnRateLimit retries fn up to maxAttempts times when it returns a *RateLimitError. Non-rate-limit errors are returned immediately. The context is passed to fn on each attempt.
func RetryWithBackoff ¶
func RetryWithBackoff[T any](ctx context.Context, maxRetries int, baseDelay time.Duration, label string, fn func(ctx context.Context) (T, error)) (T, error)
RetryWithBackoff retries fn up to maxRetries times with jittered exponential backoff. Non-transient errors are returned immediately. Logging uses slog.Default() and cannot be overridden per-call; control output via slog.SetDefault().
Example ¶
package main
import (
"context"
"fmt"
"time"
"github.com/cplieger/httpx"
)
func main() {
attempts := 0
result, err := httpx.RetryWithBackoff(context.Background(), 3, time.Millisecond, "example", func(_ context.Context) (string, error) {
attempts++
if attempts < 2 {
return "", &httpx.HTTPStatusError{Code: 503}
}
return "done", nil
})
if err != nil {
fmt.Println("error:", err)
return
}
fmt.Println(result)
}
Output: done
func SafeDouble ¶
SafeDouble doubles a duration, guarding against int64 overflow.
Types ¶
type AuthError ¶
type AuthError struct{ Msg string }
AuthError indicates invalid or expired credentials.
type Backoff ¶
type Backoff interface {
// NextBackOff returns the next wait duration, or BackoffStop to stop.
NextBackOff() time.Duration
// Reset restores the strategy to its initial state.
Reset()
}
Backoff is a pluggable backoff strategy. NextBackOff returns the duration to wait before the next retry. Return BackoffStop to signal no more retries. Mirrors cenkalti/backoff.BackOff.
type CheckRetry ¶
CheckRetry is the signature for a retry policy function. It receives the context, the response (may be nil on transport error), and the error (nil on successful response). It returns whether to retry and an optional error to short-circuit with. Mirrors hashicorp/go-retryablehttp CheckRetry.
type ExpBackoffOption ¶
type ExpBackoffOption func(*expBackoffCfg)
ExpBackoffOption configures an ExponentialBackoff.
func WithInitialInterval ¶
func WithInitialInterval(d time.Duration) ExpBackoffOption
WithInitialInterval sets the first backoff duration. Default: DefaultBaseDelay.
func WithMaxElapsedTime ¶
func WithMaxElapsedTime(d time.Duration) ExpBackoffOption
WithMaxElapsedTime caps total retry time for the backoff. Zero means no cap.
type ExponentialBackoff ¶
type ExponentialBackoff struct {
// contains filtered or unexported fields
}
ExponentialBackoff implements Backoff with jittered exponential backoff. This is the default strategy used throughout httpx.
func NewExponentialBackoff ¶
func NewExponentialBackoff(opts ...ExpBackoffOption) *ExponentialBackoff
NewExponentialBackoff creates an ExponentialBackoff with functional options.
func (*ExponentialBackoff) NextBackOff ¶
func (b *ExponentialBackoff) NextBackOff() time.Duration
NextBackOff returns the next jittered backoff duration, or BackoffStop if MaxElapsedTime has been exceeded.
func (*ExponentialBackoff) Reset ¶
func (b *ExponentialBackoff) Reset()
Reset restores the backoff to its initial state.
type HTTPStatusError ¶
type HTTPStatusError struct {
Code int
}
HTTPStatusError represents a non-2xx HTTP response not covered by AuthError or RateLimitError. Implements the Transient interface for 502/503/504.
func (*HTTPStatusError) Error ¶
func (e *HTTPStatusError) Error() string
func (*HTTPStatusError) IsClientError ¶
func (e *HTTPStatusError) IsClientError() bool
IsClientError reports whether the status code is 4xx.
func (*HTTPStatusError) IsServerError ¶
func (e *HTTPStatusError) IsServerError() bool
IsServerError reports whether the status code is 5xx.
func (*HTTPStatusError) IsTransient ¶
func (e *HTTPStatusError) IsTransient() bool
IsTransient reports whether the status code is a retryable server failure (502/503/504).
type OnRetry ¶
OnRetry is called before each retry attempt (not the initial request). attempt is 1-indexed (first retry = 1). resp may be nil on transport errors.
type Option ¶
type Option func(*retryCfg)
Option configures a Retry call.
func WithBaseDelay ¶
WithBaseDelay sets the initial backoff delay. Default: DefaultBaseDelay (1s).
func WithHeaders ¶
WithHeaders sets a function that is called to set headers on each request.
func WithLogger ¶
WithLogger sets the logger for retry diagnostics. Default: slog.Default().
func WithMaxAttempts ¶
WithMaxAttempts sets the maximum number of attempts (including the first). Default: DefaultMaxAttempts (3).
func WithMaxBodyBytes ¶
WithMaxBodyBytes sets the maximum response body size to read. Default: DefaultMaxBodyBytes (10 MB).
type PermanentError ¶
type PermanentError struct {
Err error
}
PermanentError wraps an error to signal that it should NOT be retried, regardless of other retry policies. Mirrors cenkalti/backoff.PermanentError. Use Permanent(err) to wrap.
func (*PermanentError) Error ¶
func (e *PermanentError) Error() string
func (*PermanentError) Is ¶
func (e *PermanentError) Is(target error) bool
Is allows errors.Is matching against other PermanentErrors.
func (*PermanentError) Unwrap ¶
func (e *PermanentError) Unwrap() error
type PrepareRetry ¶
PrepareRetry is called before each retry to allow request mutation (e.g., re-signing with a fresh token). Mirrors go-retryablehttp PrepareRetry.
type RTOption ¶
type RTOption func(*rtCfg)
RTOption configures a RetryRoundTripper via NewRetryRoundTripper.
func WithBackoff ¶
WithBackoff sets a custom backoff strategy. When set, BaseDelay is ignored.
func WithCheckRetry ¶
func WithCheckRetry(cr CheckRetry) RTOption
WithCheckRetry sets a custom retry policy. If nil, the default policy retries on transient transport errors and 429/5xx responses.
func WithMaxRetries ¶
WithMaxRetries sets the maximum number of retries (not counting the initial request). Default: 2 (3 total attempts).
func WithOnRetry ¶
WithOnRetry sets a hook called before each retry attempt for observability.
func WithPrepareRetry ¶
func WithPrepareRetry(fn PrepareRetry) RTOption
WithPrepareRetry sets a hook called before each retry to mutate the request (e.g., refresh auth tokens).
func WithRTBaseDelay ¶
WithRTBaseDelay sets the initial backoff delay for the round-tripper (used when no custom Backoff is provided). Default: DefaultBaseDelay (1s).
func WithRTMaxElapsedTime ¶
WithRTMaxElapsedTime caps total time spent retrying. Zero means no cap.
func WithRetryNonIdempotent ¶
WithRetryNonIdempotent enables retry of non-idempotent methods (POST, PUT, PATCH, DELETE) when the request has a GetBody function for body replay.
type RateLimitError ¶
RateLimitError indicates a rate limit was exceeded. RetryAfter, when non-zero, is the hint from the upstream's Retry-After header.
func (*RateLimitError) Error ¶
func (e *RateLimitError) Error() string
type RedirectOption ¶
type RedirectOption func(*redirectCfg)
RedirectOption configures a redirect policy created by RedirectPolicyFunc.
func WithAllowedHosts ¶
func WithAllowedHosts(hosts ...string) RedirectOption
WithAllowedHosts sets the exact hostnames allowed as redirect targets.
func WithAllowedSuffixes ¶
func WithAllowedSuffixes(suffixes ...string) RedirectOption
WithAllowedSuffixes sets the domain suffixes allowed (e.g. ".docker.com").
func WithMaxHops ¶
func WithMaxHops(n int) RedirectOption
WithMaxHops sets the maximum number of redirect hops. Default: 5.
type RetryRoundTripper ¶
type RetryRoundTripper struct {
// contains filtered or unexported fields
}
RetryRoundTripper implements http.RoundTripper with automatic retry.
By default, only idempotent methods (GET, HEAD, OPTIONS, TRACE) are retried. Use WithRetryNonIdempotent to also retry POST/PUT/PATCH/DELETE when the request has a GetBody function for body replay (mirrors go-retryablehttp).
Mirrors hashicorp/go-retryablehttp RoundTripper but operates directly on stdlib *http.Request without requiring a custom request type.
func NewRetryRoundTripper ¶
func NewRetryRoundTripper(next http.RoundTripper, opts ...RTOption) *RetryRoundTripper
NewRetryRoundTripper creates a RetryRoundTripper wrapping next with the given options. If next is nil, http.DefaultTransport is used.
func (*RetryRoundTripper) RoundTrip ¶
RoundTrip implements http.RoundTripper. It retries eligible requests on transient failures with jittered exponential backoff, honoring Retry-After. Per the http.RoundTripper contract, the caller's request is never mutated.
func (*RetryRoundTripper) StandardClient ¶
func (rt *RetryRoundTripper) StandardClient() *http.Client
StandardClient returns an *http.Client using this RetryRoundTripper as its Transport. Mirrors hashicorp/go-retryablehttp StandardClient().
type StatusError ¶
StatusError represents a non-2xx response with URL context. Used by Retry. Supports errors.Is matching against ErrRateLimited and ErrServerError.
func (*StatusError) Error ¶
func (e *StatusError) Error() string
func (*StatusError) Is ¶
func (e *StatusError) Is(target error) bool
Is reports whether this StatusError matches ErrRateLimited or ErrServerError.