relay.

API reference

Package relay — the exported surface. Signatures are stable across the v1 line; additions are documented in the changelog.

Constructor

func New

func New(opts ...Option) *Client

Creates a client with the given options applied in order. With no options it behaves like a plain http.Client backed by a clone of http.DefaultTransport: a single attempt, no per-attempt timeout, and no breaker. The returned client is safe for concurrent use.

Client

type Client

type Client struct {
	// unexported fields
}

Client is a resilient HTTP client. Construct it with New and reuse it; do not copy a Client after first use.

func (*Client) Do

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

Sends an HTTP request and returns the response, applying the breaker, retry, and timeout layers. The provided context governs the entire operation; if it is cancelled, Do returns its error. The request's own context is replaced by a derived one, so build requests with http.NewRequest rather than http.NewRequestWithContext.

func (*Client) Get

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

Convenience wrapper that builds a GET request and calls Do.

func (*Client) Post

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

Convenience wrapper that builds a POST request with the given content type and body. When body is a *bytes.Reader, *bytes.Buffer, or *strings.Reader, the request is retryable; otherwise relay sends it exactly once.

func (*Client) State

func (c *Client) State() BreakerState

Returns the current breaker state. Useful for health endpoints and metrics. Returns StateClosed when the breaker is disabled.

Options

type Option

type Option func(*config)

An Option configures a Client. Use the With* helpers below rather than constructing options directly.

OptionSignature
WithTimeoutfunc(d time.Duration) Option
WithRetryfunc(p RetryPolicy) Option
WithBackofffunc(b Backoff) Option
WithCircuitBreakerfunc(c BreakerConfig) Option
WithTransportfunc(rt http.RoundTripper) Option

Retries

type RetryPolicy

type RetryPolicy struct {
	MaxAttempts int
	RetryOn     func(resp *http.Response, err error) bool
}

See Configuration for field semantics.

Built-in classifiers

func RetryIdempotent(resp *http.Response, err error) bool
func RetryNever(resp *http.Response, err error) bool

Backoff

type Backoff

type Backoff interface {
	// Delay returns the wait before the given attempt number, where
	// attempt is the 1-indexed retry (1 = before the second try).
	Delay(attempt int) time.Duration
}

type ExponentialBackoff

type ExponentialBackoff struct {
	Base   time.Duration
	Max    time.Duration
	Jitter JitterFunc // optional; defaults to FullJitter
}

Implements Backoff with min(Max, Base × 2^(attempt-1)), reduced by Jitter. The package also exports ConstantBackoff for a fixed delay.

Jitter

type JitterFunc func(d time.Duration) time.Duration

var (
	FullJitter  JitterFunc // uniform in [0, d]
	EqualJitter JitterFunc // d/2 + uniform in [0, d/2]
	NoJitter    JitterFunc // returns d unchanged
)

Circuit breaker

type BreakerConfig

type BreakerConfig struct {
	FailureThreshold int
	SuccessThreshold int
	OpenTimeout      time.Duration
}

type BreakerState

type BreakerState int

const (
	StateClosed BreakerState = iota
	StateOpen
	StateHalfOpen
)

func (s BreakerState) String() string

Errors

var (
	// ErrCircuitOpen is returned when the breaker is open and a call is
	// rejected without contacting the upstream.
	ErrCircuitOpen = errors.New("relay: circuit breaker open")

	// ErrExhausted is returned when all retry attempts have been used and
	// the last attempt still failed. It wraps the underlying error.
	ErrExhausted = errors.New("relay: retry attempts exhausted")
)

Both errors support errors.Is. ErrExhausted wraps the final cause, so errors.Unwrap yields the last transport or response error encountered.

resp, err := client.Get(ctx, url)
switch {
case errors.Is(err, relay.ErrCircuitOpen):
	// upstream is being shed; serve a fallback
case errors.Is(err, relay.ErrExhausted):
	// retried and still failed
case err != nil:
	// context cancelled or non-retryable error
}

For end-to-end usage, see Examples.