httpclient

package
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: May 26, 2026 License: MIT Imports: 14 Imported by: 0

Documentation

Overview

Package httpclient wraps net/http with retry, circuit breaker, and tracing so consumers stop reimplementing the same outbound-call boilerplate.

c := httpclient.New(
    httpclient.WithBaseURL("https://api.example.com"),
    httpclient.WithTimeout(5 * time.Second),
    httpclient.WithRetries(3),
    httpclient.WithBreaker(httpclient.NewBreaker()),
)
res, err := c.Do(ctx, req)

Index

Constants

This section is empty.

Variables

View Source
var ErrBreakerOpen = errors.New("httpclient: circuit breaker open")

ErrBreakerOpen is returned by the client when the circuit is open.

Functions

func FxModule added in v0.3.0

func FxModule(name string, opts ...Option) fx.Option

FxModule registers a named *Client in the fx graph so a service can hold several clients (one per upstream) and inject the right one by name.

Usage:

app.Run(
    app.WithName("billing"),
    httpclient.FxModule("stripe",
        httpclient.WithBaseURL("https://api.stripe.com"),
        httpclient.WithRetries(3),
        httpclient.WithBreakerPerHost(5, 30*time.Second)),
    httpclient.FxModule("slack",
        httpclient.WithBaseURL("https://slack.com/api")),
    internal.FxModule(),
)

// Inject by name:
type StripeDeps struct {
    fx.In
    Client *httpclient.Client `name:"stripe"`
}

The module name doubles as the fx group key so duplicate registrations panic at boot.

func IsRetriable

func IsRetriable(err error) bool

IsRetriable reports whether an error is one the client retried on.

func NewObservabilityTransport added in v0.3.0

func NewObservabilityTransport(base http.RoundTripper, log logger.Logger, tr tracer.Tracer) http.RoundTripper

NewObservabilityTransport wraps base so every outbound request gets an OTel span (propagated via W3C tracecontext headers) and a single structured log line on completion with method / host / status / duration. Pass it to WithTransport, or rely on FxModule to wire it automatically when logger + tracer are available in the fx graph.

Types

type Breaker

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

Breaker is a small failure-rate-based circuit breaker.

func NewBreaker

func NewBreaker(opts ...BreakerOption) *Breaker

NewBreaker constructs a breaker. Defaults: threshold 5, cooldown 30s.

func (*Breaker) Allow

func (b *Breaker) Allow() (allow bool, state State)

Allow reports whether a request should be attempted. Call before sending.

func (*Breaker) RecordFailure

func (b *Breaker) RecordFailure()

RecordFailure increments the consecutive-failure counter and trips the breaker once it reaches the threshold.

func (*Breaker) RecordSuccess

func (b *Breaker) RecordSuccess()

RecordSuccess closes the breaker.

func (*Breaker) SetClock

func (b *Breaker) SetClock(now func() time.Time)

SetClock overrides the time source. Test-only.

func (*Breaker) State

func (b *Breaker) State() State

State returns the current state.

type BreakerOption

type BreakerOption func(*Breaker)

func WithCooldown

func WithCooldown(d time.Duration) BreakerOption

func WithThreshold

func WithThreshold(n int) BreakerOption

type Client

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

Client is an http.Client wrapper with retry + breaker + per-attempt timeout.

func New

func New(opts ...Option) *Client

New constructs a Client with the supplied options.

func (*Client) Do

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

Do sends the request with the configured retry + breaker semantics. The request body is buffered so retries can replay it.

type Option

type Option func(*Client)

func WithBackoff

func WithBackoff(opts ...retry.Option) Option

WithBackoff overrides the retry policy options. Defaults to exponential backoff starting at 100ms.

func WithBaseURL

func WithBaseURL(url string) Option

WithBaseURL prepends a base URL to relative request paths.

func WithBreaker

func WithBreaker(b *Breaker) Option

WithBreaker installs a circuit breaker. When the breaker is open, Do returns ErrBreakerOpen immediately without contacting the server.

func WithBreakerPerHost added in v0.3.0

func WithBreakerPerHost(threshold int, cooldown time.Duration) Option

WithBreakerPerHost installs a manager that builds a separate *Breaker per upstream host. One bad downstream stops poisoning unrelated calls. Mutually exclusive with WithBreaker; the per-host manager wins if both are supplied.

func WithHTTPClient

func WithHTTPClient(c *http.Client) Option

WithHTTPClient overrides the underlying http.Client. Useful when callers want shared connection pooling. Resets timeout to the supplied client's.

func WithHeaders

func WithHeaders(fn func() http.Header) Option

WithHeaders installs a header factory invoked per attempt. Headers from the inbound request are preserved; factory headers are added on top (typical use: Authorization, X-Tenant-ID).

func WithRetries

func WithRetries(n int) Option

WithRetries sets the total number of attempts (1 = no retry, default 1).

func WithRetryAll added in v0.3.0

func WithRetryAll() Option

WithRetryAll opts every HTTP method into the retry policy. By default retries are gated to idempotent methods (GET, HEAD, PUT, DELETE, OPTIONS) per RFC 9110 §9.2.2 — replaying POST/PATCH can double-charge / double-create. Use this for endpoints you know are idempotent on the server side despite the verb.

func WithTimeout

func WithTimeout(d time.Duration) Option

WithTimeout sets the per-attempt timeout. The http.Client's own timeout is left alone; this wraps the request context.

func WithTransport

func WithTransport(rt http.RoundTripper) Option

WithTransport overrides the RoundTripper. Combine with NewTracingTransport from go/kit/transport/rest for OTEL spans.

type PerHostBreaker added in v0.3.0

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

PerHostBreaker keeps a separate *Breaker per upstream host so one flaky downstream doesn't poison unrelated calls. Breakers are created lazily on first request to a given host.

func NewPerHostBreaker added in v0.3.0

func NewPerHostBreaker(threshold int, cooldown time.Duration) *PerHostBreaker

NewPerHostBreaker returns a manager that builds per-host breakers using the supplied threshold + cooldown.

func (*PerHostBreaker) For added in v0.3.0

func (p *PerHostBreaker) For(host string) *Breaker

For returns the breaker for host, creating one on demand.

func (*PerHostBreaker) Snapshot added in v0.3.0

func (p *PerHostBreaker) Snapshot() map[string]State

Snapshot returns the current per-host breaker state. Useful for /healthz handlers that want to expose breaker status.

type State

type State int

State is one of:

  • Closed: requests flow normally
  • Open: trip threshold reached, requests fail fast until cooldown
  • HalfOpen: probe state — one request is allowed to test recovery
const (
	StateClosed State = iota
	StateOpen
	StateHalfOpen
)

func (State) String

func (s State) String() string

Jump to

Keyboard shortcuts

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