Documentation
¶
Index ¶
- func DefaultRetryPolicy(resp *http.Response, err error, retryStatusCodes []int) (shouldRetry bool)
- func NewGamma(opts ...Option) *http.Client
- func NewTransport(opts ...Option) http.RoundTripper
- func WithOverrides(req *http.Request, opts ...OverrideOption) *http.Request
- type AdaptiveOption
- type AdaptiveRules
- type BackoffFunc
- type BackoffStrategy
- type Middleware
- type Option
- type OverrideOption
- type Overrides
- type RetryConfig
- type RetryOption
- type RetryPolicy
- type RoundTripperFunc
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func DefaultRetryPolicy ¶
DefaultRetryPolicy retries on any network error or when the response status code matches one of the configured retryable codes.
func NewGamma ¶
NewGamma builds a standard *http.Client with the gamma middleware chain installed as its transport. This is the recommended entry point for most callers — you get a fully configured client in one call and keep using the familiar http.Client API.
Middlewares are applied in the order given: the first Use becomes the outermost wrapper (see Chain for the exact semantics). WithBase and WithClientTimeout may appear anywhere in the option list.
client := gamma.NewGamma(
gamma.WithClientTimeout(30 * time.Second),
gamma.Use(gamma.Retry(
gamma.RetryMaxAttempts(3),
gamma.RetryOn(429, 503, 504),
)),
)
resp, err := client.Get("https://api.example.com/data")
func NewTransport ¶
func NewTransport(opts ...Option) http.RoundTripper
NewTransport builds a composable http.RoundTripper from the given options. Use it when you need to attach the gamma middleware chain to an *http.Client you manage yourself — for example, because the client is shared with other code, or because you want to pin its Timeout, Jar, or redirect policy independently of gamma.
The WithClientTimeout option is ignored here (a bare RoundTripper has no Timeout field); set http.Client.Timeout on your client directly instead.
rt := gamma.NewTransport(
gamma.Use(gamma.Retry()),
gamma.Use(gamma.Timeout(5 * time.Second)),
)
client := &http.Client{Transport: rt, Timeout: 30 * time.Second}
func WithOverrides ¶
func WithOverrides(req *http.Request, opts ...OverrideOption) *http.Request
WithOverrides attaches per-request configuration to the request via its context. Middlewares that support overrides (for example, the retry middleware) read these values through [getOverrides] and merge them on top of their own defaults.
req, _ := http.NewRequest("POST", "https://api.example.com/pay", body)
req = gamma.WithOverrides(req,
gamma.OverrideRetries(1),
gamma.OverridePerAttemptTimeout(15*time.Second),
)
resp, err := client.Do(req)
Types ¶
type AdaptiveOption ¶
type AdaptiveOption func(*AdaptiveRules)
AdaptiveOption configures an AdaptiveRules set via AdaptiveBackoff.
func AdaptiveDefault ¶
func AdaptiveDefault(b BackoffStrategy) AdaptiveOption
AdaptiveDefault overrides the fallback backoff used when no specific rule matches.
func AdaptiveOnRateLimit ¶
func AdaptiveOnRateLimit(b BackoffStrategy) AdaptiveOption
AdaptiveOnRateLimit overrides the backoff used for 429 responses.
backoff := gamma.AdaptiveBackoff(
gamma.AdaptiveOnRateLimit(gamma.ConstantBackoff(5 * time.Second)),
)
type AdaptiveRules ¶
type AdaptiveRules struct {
// OnRateLimit is used when the server responds with 429 Too Many Requests.
OnRateLimit BackoffStrategy
// OnServerError is used for 5xx responses.
OnServerError BackoffStrategy
// OnConnReset is used when the response is nil (network-level failure).
OnConnReset BackoffStrategy
// Default is the fallback for any other retryable error.
Default BackoffStrategy
}
AdaptiveRules maps error categories to dedicated BackoffStrategy implementations. Use with AdaptiveBackoff to apply different retry policies depending on the type of failure.
type BackoffFunc ¶
BackoffFunc is an adapter that lets ordinary functions satisfy BackoffStrategy.
custom := gamma.BackoffFunc(func(attempt int, resp *http.Response) time.Duration {
return time.Duration(attempt+1) * 500 * time.Millisecond
})
type BackoffStrategy ¶
BackoffStrategy determines the delay between retry attempts. Implementations receive the current attempt number (zero-indexed) and the HTTP response (which may be nil for network-level errors).
strategy := gamma.ExponentialBackoff(time.Second, 2.0) delay := strategy.Delay(3, resp) // 1s * 2^3 = 8s
func AdaptiveBackoff ¶
func AdaptiveBackoff(opts ...AdaptiveOption) BackoffStrategy
AdaptiveBackoff returns a composite strategy that selects a backoff policy based on the type of failure. By default it uses:
- 429 rate-limit: exponential backoff (2s base, 3x factor)
- 5xx server error: exponential backoff with jitter (1s base, 2x factor)
- nil response (network error): constant 100ms
- everything else: exponential backoff (1s base, 2x factor)
Pass AdaptiveOption values to override individual rules.
// use defaults
backoff := gamma.AdaptiveBackoff()
// override rate-limit strategy
backoff = gamma.AdaptiveBackoff(
gamma.AdaptiveOnRateLimit(gamma.ExponentialJitterBackoff(3*time.Second, 2.0)),
)
func ConstantBackoff ¶
func ConstantBackoff(d time.Duration) BackoffStrategy
ConstantBackoff returns a strategy that always waits the same duration, regardless of the attempt number. If the response contains a Retry-After header, that value takes precedence.
// always wait 200ms between retries backoff := gamma.ConstantBackoff(200 * time.Millisecond)
func ExponentialBackoff ¶
func ExponentialBackoff(base time.Duration, factor float64) BackoffStrategy
ExponentialBackoff returns a strategy that waits base * factor^attempt. If the response contains a Retry-After header, that value takes precedence.
// 1s, 2s, 4s, 8s, … backoff := gamma.ExponentialBackoff(time.Second, 2.0) // 500ms, 1.5s, 4.5s, 13.5s, … backoff = gamma.ExponentialBackoff(500*time.Millisecond, 3.0)
func ExponentialJitterBackoff ¶
func ExponentialJitterBackoff(base time.Duration, factor float64) BackoffStrategy
ExponentialJitterBackoff is like ExponentialBackoff but adds random jitter to avoid thundering-herd problems. The delay is uniformly distributed in [half, full] where full = base * factor^attempt. If the response contains a Retry-After header, that value takes precedence.
// jittered delays centred around 1s, 2s, 4s, … backoff := gamma.ExponentialJitterBackoff(time.Second, 2.0)
type Middleware ¶
type Middleware func(http.RoundTripper) http.RoundTripper
Middleware wraps an http.RoundTripper and returns a new one with added behaviour. It is the fundamental building block of gamma — every feature (retry, timeout, circuit breaker, rate limit, observability hooks) is expressed as a Middleware so the pieces compose cleanly.
Because a Middleware is just a function, users can freely write their own and drop them into the chain alongside the built-ins.
logging := func(next http.RoundTripper) http.RoundTripper {
return gamma.RoundTripperFunc(func(req *http.Request) (*http.Response, error) {
log.Printf("→ %s %s", req.Method, req.URL)
return next.RoundTrip(req)
})
}
client := gamma.NewGamma(gamma.Use(logging))
func Chain ¶
func Chain(middlewares ...Middleware) Middleware
Chain composes middlewares into a single Middleware. The first argument is the outermost wrapper: it runs first on the way in and last on the way out.
Given Chain(a, b, c), a request flows a → b → c → base transport, and the response flows back c → b → a. This matches the conventional "onion" model used by most HTTP middleware libraries.
chain := gamma.Chain(logging, rateLimit, retry)
rt := chain(http.DefaultTransport)
client := &http.Client{Transport: rt}
func Retry ¶
func Retry(opts ...RetryOption) Middleware
Retry returns a middleware that has a baked in default configuration for retry. This is the simplest way to get started with retry.
client := gamma.NewGamma(
gamma.Use(gamma.Retry()),
)
resp, err := client.Get("https://api.example.com/data")
It also supports custom configuration via RetryOption functions.
client := gamma.NewGamma(
gamma.Use(gamma.Retry(
gamma.RetryMaxAttempts(3),
gamma.RetryOn(429, 503, 504),
gamma.RetryWithBackoff(gamma.ExponentialBackoff(time.Second, 2.0)),
)),
)
resp, err := client.Get("https://api.example.com/data")
func Timeout ¶
func Timeout(d time.Duration) Middleware
Timeout returns a middleware that enforces a deadline on the request context. Its meaning depends on where you place it in the chain:
Outside (before) Retry — acts as an overall timeout across every attempt plus the backoff waits between them. Once d elapses the context is cancelled and the retry loop gives up.
Inside (after) Retry — acts as a per-attempt timeout, because the retry middleware re-enters the rest of the chain on every iteration and each iteration gets a fresh Timeout context.
If you only need a per-attempt deadline, prefer RetryPerAttemptTimeout — it lives inside the retry config and is both simpler and more discoverable. Reach for this middleware when you want an overall cap or want the deadline expressed as a composable pipeline stage.
// overall: 30s hard cap across all retries + backoff
client := gamma.NewGamma(
gamma.Use(gamma.Timeout(30 * time.Second)),
gamma.Use(gamma.Retry()),
)
// per-attempt: each individual attempt capped at 5s
client = gamma.NewGamma(
gamma.Use(gamma.Retry()),
gamma.Use(gamma.Timeout(5 * time.Second)),
)
type Option ¶
type Option func(*config)
Option customises the transport or client produced by NewTransport and NewGamma. Options follow the functional-options pattern: each one is a small function that mutates the internal [config].
func Use ¶
func Use(m Middleware) Option
Use appends a Middleware to the chain. Order matters: the first Use() call becomes the outermost wrapper, so it runs first on the request and last on the response.
client := gamma.NewGamma(
gamma.Use(gamma.Timeout(30 * time.Second)), // outermost
gamma.Use(gamma.Retry()),
gamma.Use(gamma.CircuitBreaker(5, 30*time.Second)),
)
func WithBase ¶
func WithBase(rt http.RoundTripper) Option
WithBase overrides the underlying http.RoundTripper. Defaults to http.DefaultTransport. Use this when you need to customise the base transport (for example, to set proxy settings, TLS config, or connection pool limits) while still layering gamma middlewares on top.
base := &http.Transport{MaxIdleConnsPerHost: 100}
client := gamma.NewGamma(
gamma.WithBase(base),
gamma.Use(gamma.Retry()),
)
func WithClientTimeout ¶
WithClientTimeout sets the http.Client.Timeout on the client returned by NewGamma. This is the hard ceiling the standard library enforces on the entire request (including redirects, connect, and read). It has no effect when using NewTransport.
Prefer Timeout middleware when you want the deadline to participate in the middleware pipeline (for example, applying per-attempt timeouts or composing with retry).
client := gamma.NewGamma(
gamma.WithClientTimeout(10 * time.Second),
gamma.Use(gamma.Retry()),
)
type OverrideOption ¶
type OverrideOption func(*Overrides)
OverrideOption mutates an Overrides value. Pass one or more to WithOverrides to attach per-request settings to an http.Request.
func OverrideBackoff ¶
func OverrideBackoff(b BackoffStrategy) OverrideOption
OverrideBackoff sets the BackoffStrategy for this request, overriding the one configured on the retry middleware.
req = gamma.WithOverrides(req,
gamma.OverrideBackoff(gamma.ConstantBackoff(5 * time.Second)),
)
func OverridePerAttemptTimeout ¶
func OverridePerAttemptTimeout(d time.Duration) OverrideOption
OverridePerAttemptTimeout sets the per-attempt deadline for this request, overriding the value configured on the retry middleware.
// this endpoint's backend is known to be slow
req = gamma.WithOverrides(req,
gamma.OverridePerAttemptTimeout(15 * time.Second),
)
func OverrideRetries ¶
func OverrideRetries(n int) OverrideOption
OverrideRetries sets the maximum number of attempts for this request, overriding whatever was configured on the retry middleware.
// this specific request should not be retried req = gamma.WithOverrides(req, gamma.OverrideRetries(1))
type Overrides ¶
type Overrides struct {
// MaxAttempts, when non-nil, replaces [RetryConfig.MaxAttempts] for this
// request only.
MaxAttempts *int
// RetryStatusCodes, when non-nil, replaces [RetryConfig.RetryStatusCodes]
// for this request only.
RetryStatusCodes []int
// Backoff, when non-nil, replaces [RetryConfig.Backoff] for this request
// only.
Backoff BackoffStrategy
// PerAttemptTimeout, when non-nil, replaces
// [RetryConfig.PerAttemptTimeout] for this request only.
PerAttemptTimeout *time.Duration
}
Overrides holds per-request configuration that takes precedence over the defaults baked into the middleware chain. Fields use pointers where the zero value is a meaningful setting (for example, 0 retries), so that an "unset" override can be distinguished from "explicitly set to zero".
Callers don't usually construct an Overrides directly — they use WithOverrides together with the Override* option constructors.
type RetryConfig ¶
type RetryConfig struct {
MaxAttempts int
Policy RetryPolicy
RetryStatusCodes []int
Backoff BackoffStrategy
PerAttemptTimeout time.Duration
}
RetryConfig holds all tuneable knobs for the retry middleware. Every field has a sensible default (see [defaultRetryConfig]), so callers only need to override what they care about via RetryOption functions.
cfg := &gamma.RetryConfig{
MaxAttempts: 4,
RetryStatusCodes: []int{429, 502, 503},
Backoff: gamma.ExponentialBackoff(500*time.Millisecond, 2.0),
Policy: gamma.DefaultRetryPolicy,
}
type RetryOption ¶
type RetryOption func(*RetryConfig)
RetryOption is a functional option that mutates a RetryConfig. Pass one or more RetryOption values to Retry to customize behaviour.
gamma.Retry(
gamma.RetryMaxAttempts(5),
gamma.RetryOn(429, 503),
)
func RetryMaxAttempts ¶
func RetryMaxAttempts(n int) RetryOption
RetryMaxAttempts sets the total number of attempts (initial + retries). For example, RetryMaxAttempts(3) means one initial request plus two retries. The default is 2.
client := gamma.NewGamma(
gamma.Use(gamma.Retry(
gamma.RetryMaxAttempts(5),
)),
)
func RetryOn ¶
func RetryOn(codes ...int) RetryOption
RetryOn replaces the default set of retryable HTTP status codes. Only responses whose status code appears in codes will be retried (in addition to network-level errors, which are always retried by the default policy).
client := gamma.NewGamma(
gamma.Use(gamma.Retry(
gamma.RetryOn(429, 502, 503, 504),
)),
)
func RetryPerAttemptTimeout ¶
func RetryPerAttemptTimeout(d time.Duration) RetryOption
RetryPerAttemptTimeout sets a per-attempt deadline. Each individual round-trip is cancelled if it exceeds this duration, and the next retry fires. A zero value (the default) means no per-attempt timeout — only the overall request context governs cancellation.
client := gamma.NewGamma(
gamma.Use(gamma.Retry(
gamma.RetryPerAttemptTimeout(2*time.Second),
)),
)
func RetryWithBackoff ¶
func RetryWithBackoff(b BackoffStrategy) RetryOption
RetryWithBackoff overrides the delay strategy used between retry attempts. The default is ExponentialBackoff with a 1 s base and a factor of 2.0.
client := gamma.NewGamma(
gamma.Use(gamma.Retry(
gamma.RetryWithBackoff(gamma.ExponentialBackoff(200*time.Millisecond, 3.0)),
)),
)
func RetryWithPolicy ¶
func RetryWithPolicy(p RetryPolicy) RetryOption
RetryWithPolicy overrides the function that decides whether a failed request should be retried. The default is DefaultRetryPolicy, which retries on network errors and any status code listed in RetryStatusCodes.
idempotentOnly := func(resp *http.Response, err error, codes []int) bool {
if resp != nil && resp.Request.Method == http.MethodPost {
return false
}
return gamma.DefaultRetryPolicy(resp, err, codes)
}
client := gamma.NewGamma(
gamma.Use(gamma.Retry(
gamma.RetryWithPolicy(idempotentOnly),
)),
)
type RetryPolicy ¶
RetryPolicy decides whether a failed request should be retried based on the response, error, and the set of retryable status codes.
type RoundTripperFunc ¶
RoundTripperFunc adapts an ordinary function into an http.RoundTripper, mirroring the http.HandlerFunc pattern on the server side. It is exported so that callers writing their own middlewares can return a [RoundTripper] without defining a new struct type.
logging := func(next http.RoundTripper) http.RoundTripper {
return gamma.RoundTripperFunc(func(req *http.Request) (*http.Response, error) {
log.Printf("→ %s %s", req.Method, req.URL)
return next.RoundTrip(req)
})
}
func (RoundTripperFunc) RoundTrip ¶
RoundTrip calls f(req) and satisfies http.RoundTripper.