Configuration Reference¶
All relay client configuration is applied through functional option functions passed to relay.New (or (*Client).With to derive a new client from an existing one). Each function returns a relay.Option value. Options are applied left-to-right; later options win when they touch the same field.
This reference lists every With* option function exported by the relay package (the core module - not ext/* extensions), in alphabetical order. A short note on scope: some names below look like they might be per-request helpers - they are not. Per-request concerns (a one-off Authorization header, a request-scoped timeout, path/query params) are set via methods on *relay.Request (e.g. req.WithBearerToken(...), req.WithBasicAuth(...), req.WithAPIKey(...), req.WithTimeout(...)), returned by client.Get/client.Post/etc. - not via relay.With* options passed to relay.New. This file only covers client-construction options.
WithAdaptiveTimeout¶
Enables adaptive per-request timeouts computed from a percentile of recently observed response latencies, instead of a single fixed WithTimeout value. Automatically forces timing instrumentation on (as if WithTiming were also passed), since adaptive timeout needs latency samples to function.
relay.AdaptiveTimeoutConfig fields:
| Field | Type | Description | Default if zero |
|---|---|---|---|
Percentile | float64 | Latency percentile used as the base (e.g. 0.95 for p95). | 0.95 |
Multiplier | float64 | Scales the percentile latency to get the timeout (e.g. 2.0 = 2× p95). | 2.0 |
WindowSize | int | Number of recent latency observations kept. | 100 |
MinTimeout | time.Duration | Floor on the computed timeout. | 100ms |
MaxTimeout | time.Duration | Ceiling on the computed timeout. | 30s |
InitialTimeout | time.Duration | Used until enough observations accumulate. | 5s |
Default: Disabled; every request uses the fixed WithTimeout value.
client := relay.New(relay.WithAdaptiveTimeout(relay.AdaptiveTimeoutConfig{
Percentile: 0.95,
Multiplier: 2.0,
}))
WithAutoIdempotencyKey¶
Automatically injects an X-Idempotency-Key UUID v4 header on every request, regardless of HTTP method. The same key is reused across all retry attempts for a given request. Use WithAutoIdempotencyOnSafeRetries instead if you only want this for methods that are actually idempotent.
Default: Disabled.
WithAutoIdempotencyOnSafeRetries¶
Automatically injects an X-Idempotency-Key UUID header on requests whose method is idempotent per RFC 9110 §9.2.2: GET, HEAD, PUT, DELETE, OPTIONS, and TRACE. POST and PATCH are skipped since they are not guaranteed idempotent - use WithAutoIdempotencyKey instead if you want the header on every method unconditionally. The key is generated once per original request and reused on all of its retry attempts.
Default: Disabled.
Note: The key is injected as soon as the request is sent, regardless of whether it is ever retried -
WithRetrydoes not need to be configured for the header to appear.
client := relay.New(
relay.WithAutoIdempotencyOnSafeRetries(),
relay.WithRetry(&relay.RetryConfig{MaxAttempts: 3}),
)
WithAutoNormaliseURL¶
Controls whether WithBaseURL automatically appends a trailing slash to the base URL when missing. Enabled by default for API convenience; disable it if you need WithBaseURL to store the URL exactly as passed, with no modification.
Default: true.
client := relay.New(
relay.WithAutoNormaliseURL(false),
relay.WithBaseURL("https://api.example.com/v1"),
)
WithBaseURL¶
Sets the base URL prepended to every request path that does not already start with http:// or https://. The URL is pre-parsed once at construction time for performance. If WithAutoNormaliseURL is left at its default (true), a trailing slash is added automatically when missing.
Default: Empty string; request paths must be absolute URLs.
WithBeforeRedirectHook¶
func WithBeforeRedirectHook(fn relay.BeforeRedirectHookFunc) relay.Option
type BeforeRedirectHookFunc func(req *http.Request, via []*http.Request) error
Appends a hook invoked before each redirect is followed. via is the chain of requests followed so far. Returning a non-nil error stops the redirect chain; the error propagates as the Client.Execute return value. Multiple hooks may be registered (each call to WithBeforeRedirectHook appends one); they run in registration order.
Default: No hooks; all redirects within WithMaxRedirects are followed automatically.
client := relay.New(relay.WithBeforeRedirectHook(
func(req *http.Request, via []*http.Request) error {
log.Printf("redirecting to %s (%d hops so far)", req.URL, len(via))
return nil
},
))
WithBeforeRetryHook¶
func WithBeforeRetryHook(fn relay.BeforeRetryHookFunc) relay.Option
type BeforeRetryHookFunc func(ctx context.Context, attempt int, req *relay.Request, httpResp *http.Response, err error)
Appends a hook invoked before each retry sleep. attempt is 1-based (first retry = 1). httpResp and err reflect the result that triggered the retry; either may be nil depending on whether the trigger was an HTTP status code or a transport error. Multiple hooks may be registered and run in registration order.
Default: No hooks.
client := relay.New(relay.WithBeforeRetryHook(
func(ctx context.Context, attempt int, req *relay.Request, resp *http.Response, err error) {
log.Printf("retry #%d for %s: %v", attempt, req.URL(), err)
},
))
WithCache¶
Attaches a CacheStore backend used to cache HTTP responses honoring standard cache-control semantics. Pass nil to disable caching. Use WithInMemoryCache for the built-in LRU option, or implement the CacheStore interface yourself for Redis, disk, or other backends (see ext/cache/twolevel and ext/redis for examples).
Default: nil; caching disabled.
WithCertificatePinning¶
Rejects TLS connections whose certificate chain does not contain any certificate matching one of the provided SHA-256 pins. Pins must be base64-encoded SHA-256 digests of the certificate's SPKI, optionally prefixed with "sha256/". An invalid pin configuration disables pinning and logs a warning via the configured Logger at construction time rather than failing relay.New.
Default: No pins; standard system trust store validation only.
client := relay.New(relay.WithCertificatePinning([]string{
"sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=",
}))
WithCertWatcher¶
Attaches a pre-constructed *CertWatcher - there is no standalone exported constructor for one; obtain it from an existing client built with WithDynamicTLSCert via client.Config().CertWatcher, then share that same watcher with other clients. Unlike WithDynamicTLSCert, callers are responsible for starting and stopping the watcher's lifecycle themselves - Client.Shutdown does not stop a watcher attached this way, since it may be shared across multiple clients.
Default: nil; no dynamic client certificate.
primary := relay.New(relay.WithDynamicTLSCert("/certs/client.crt", "/certs/client.key", 5*time.Minute))
secondary := relay.New(relay.WithCertWatcher(primary.Config().CertWatcher))
WithCircuitBreaker¶
Replaces the circuit breaker configuration, which trips to Open after consecutive failures and stops sending requests to protect a struggling downstream, probing periodically in HalfOpen state to detect recovery. Zero/unset fields on a non-nil cbc are defaulted individually (you don't have to specify all four).
relay.CircuitBreakerConfig fields:
| Field | Type | Description | Default if zero |
|---|---|---|---|
MaxFailures | int | Consecutive failures in Closed that trip the breaker to Open. | 5 |
ResetTimeout | time.Duration | How long the breaker stays Open before probing again. | 30s (via this option) / 60s (client-wide default) |
HalfOpenRequests | int | Max probe requests allowed while HalfOpen. | 1 (via this option) / 3 (client-wide default) |
SuccessThreshold | int | Consecutive successes in HalfOpen required to close the circuit. | 1 (via this option) / 2 (client-wide default) |
OnStateChange | func(from, to relay.CircuitBreakerState) | Optional callback on every state transition. | nil |
Default: A circuit breaker is always active unless disabled with WithDisableCircuitBreaker - the client-wide default is 5 failures, 60s reset, 3 half-open probes, 2-success threshold.
client := relay.New(relay.WithCircuitBreaker(&relay.CircuitBreakerConfig{
MaxFailures: 5,
ResetTimeout: 30 * time.Second,
SuccessThreshold: 2,
}))
WithClientCert¶
Configures a static TLS client certificate for mutual TLS (mTLS), loaded once from disk at construction time. A load error is silently ignored, leaving the TLS config unchanged - use WithDynamicTLSCert instead if hot-reloading or hard-failure-on-error is required.
Default: No client certificate presented.
WithClientCertPEM¶
Same as WithClientCert, but loads the certificate/key from PEM-encoded byte slices rather than files - useful when certificates come from an environment variable or a secret manager (Vault, AWS Secrets Manager) rather than disk.
Default: No client certificate presented.
WithCompression¶
Enables transparent response decompression. The client advertises the chosen algorithm(s) via Accept-Encoding and automatically decompresses responses whose Content-Encoding matches. relay.CompressionAuto (recommended) advertises zstd, br, gzip, deflate and decompresses whichever the server picks; CompressionZstd, CompressionBrotli, CompressionGzip restrict to one algorithm.
Default: Not called by default, but response decompression for the standard Accept-Encoding negotiated by Go's transport is otherwise on unless WithDisableCompression is set - use this option for zstd/brotli support beyond what the stdlib transport handles natively.
WithConnectionPool¶
Tunes the underlying transport's connection pool. maxIdle is the maximum total idle (keep-alive) connections across all hosts; maxIdlePerHost per individual host; maxPerHost caps total connections (active + idle) per host - 0 means unlimited.
Default: maxIdle=100, maxIdlePerHost=20, maxPerHost=50.
WithCookieJar¶
Sets the cookie jar used by the client for automatic cookie handling across requests. Pass nil to disable cookies entirely.
Default: A standard RFC 6265 jar (cookiejar.New(nil)) is attached automatically.
WithCredentialProvider¶
func WithCredentialProvider(p relay.CredentialProvider) relay.Option
type CredentialProvider interface {
Credentials(ctx context.Context) (relay.Credentials, error)
}
Sets a CredentialProvider called before each request attempt (including retries) to supply fresh credentials - useful for short-lived tokens (OAuth, Vault) without rebuilding the client. Runs before WithSigner. The built-in relay.RotatingTokenProvider caches a bearer token and refreshes it only when within a configurable threshold of expiry.
Default: nil; no credentials applied automatically.
provider := relay.NewRotatingTokenProvider(fetchToken, 30*time.Second)
client := relay.New(relay.WithCredentialProvider(provider))
WithCustomDialer¶
Replaces the default net.Dialer. When set, WithDialTimeout/WithDialKeepAlive are ignored in favour of the dialer's own settings.
Default: A net.Dialer built from WithDialTimeout/WithDialKeepAlive.
WithDefaultAccept¶
Sets the value sent in the Accept header for any request that doesn't already have one explicitly set. Pass "" to disable the default (Go's HTTP client then sends none, and servers commonly treat that as */*).
Default: ""; no default Accept header is added.
WithDefaultCookieJar¶
Creates and attaches a fresh standard RFC 6265 cookie jar. Rarely needed explicitly since relay.New already attaches one by default - useful mainly to reset back to a jar after an earlier option (or .With(WithCookieJar(nil))) cleared it.
Default: Already the default; see WithCookieJar.
WithDefaultHeaders¶
Merges the given headers into every outgoing request. Per-request headers (set via Request.WithHeader) always take precedence over these defaults. Values are sanitised to strip CR/LF characters. Calling this multiple times merges into the existing map rather than replacing it.
Default: No default headers.
WithDialKeepAlive¶
Sets the interval between TCP keep-alive probes sent on active connections. Ignored if WithCustomDialer is set.
Default: 30 seconds.
WithDialTimeout¶
Sets the maximum time allowed for a TCP connection to be established. Ignored if WithCustomDialer is set.
Default: 30 seconds.
WithDigestAuth¶
Enables HTTP Digest Authentication (RFC 7616). The client automatically handles the 401 challenge/response cycle: it sends the initial unauthenticated request, receives the challenge, then retries with the computed digest.
Default: Disabled.
WithDisableCircuitBreaker¶
Removes the circuit breaker entirely so all requests are attempted regardless of upstream failure rates. Without this, a client-wide circuit breaker is always active (see WithCircuitBreaker's default).
Default: Not disabled; a circuit breaker with conservative defaults is always on unless this option is used.
WithDisableCompression¶
Disables automatic Accept-Encoding negotiation and transparent response decompression at the transport level.
Default: Compression negotiation is on.
WithDisableRedirectTracking¶
Skips populating Response.RedirectCount and Response.RedirectChain(), avoiding a per-request context allocation for callers who don't read either. WithMaxRedirects enforcement and WithBeforeRedirectHook hooks are unaffected - only the count/chain bookkeeping is skipped.
Default: Redirect tracking is on; RedirectCount/RedirectChain() are populated automatically.
WithDisableRetry¶
Disables all retry behaviour so only a single attempt is ever made (equivalent to WithRetry(&relay.RetryConfig{MaxAttempts: 1})).
Default: Not disabled; a client-wide default retry policy (3 attempts, exponential backoff) is active unless this option or WithRetry overrides it.
WithDisableTiming¶
Deprecated: timing is off by default now (see WithTiming); this option is a no-op kept only for source compatibility with older code.
WithDNSCache¶
Enables client-side DNS caching so each unique hostname is resolved at most once per ttl interval, reducing lookup latency on keep-alive-heavy workloads. The cache is per-client. Entries are evicted lazily on next access after expiry. Concurrent misses for the same host are coalesced into a single real DNS query.
Default: Disabled; every dial uses the system resolver.
Tip: A TTL of 30s-5min works well for most services, depending on how often upstream IPs rotate.
WithDNSOverride¶
Maps hostnames to fixed IP addresses, bypassing DNS resolution for those hosts. Useful for service discovery, split-horizon DNS, and integration testing without modifying /etc/hosts. Calling this multiple times merges into the existing map.
Default: No overrides.
WithDynamicTLSCert¶
Enables hot-reloading of the TLS client certificate: the files are re-read every interval, so short-lived certificates (ACME, Vault PKI) get picked up without restarting the client or dropping existing connections. interval must be positive - construction fails silently (TLS config left unchanged) if the initial load fails, or the resulting CertWatcher is simply not created for interval <= 0. The watcher is exclusively owned by the client that created it and is stopped automatically by Client.Shutdown.
Default: Static TLS client certificate (or none); no hot-reload.
client := relay.New(relay.WithDynamicTLSCert(
"/etc/certs/client.crt", "/etc/certs/client.key", 5*time.Minute,
))
WithErrorDecoder¶
Called whenever the HTTP response status code is ≥ 400, after all WithOnAfterResponse hooks. Receives the status code and the full, already-buffered body. Returning a non-nil error releases the response and returns that error from Client.Execute instead; returning nil preserves the default behaviour where HTTP error codes are not automatically treated as Go errors.
Default: nil; 4xx/5xx responses are returned normally, not as errors.
var ErrNotFound = errors.New("not found")
client := relay.New(relay.WithErrorDecoder(func(status int, body []byte) error {
if status == http.StatusNotFound {
return ErrNotFound
}
return nil
}))
WithExpectContinueTimeout¶
Sets the maximum time to wait for a server's first response headers after fully writing the request headers, when the request has an Expect: 100-continue header. Zero disables the timeout.
Default: 0 (disabled).
WithExtension¶
func WithExtension(ext relay.Extension) relay.Option
type Extension interface {
Name() string
Apply(cfg *relay.Config) error
}
Registers an Extension - a single seam bundling transport middleware, hooks, and construction-time config validation, instead of hand-wiring WithTransportMiddleware plus assorted hook options. Multiple extensions may be registered; Apply runs in registration order, once per extension, after all With* options have been applied. An Apply error is logged via the configured Logger, not fatal.
Default: No extensions registered.
WithHARRecording¶
Attaches a HARRecorder that captures every request/response pair in HAR 1.2 format. Call HARRecorder.Export/ExportHAR to serialise. The recorded body snapshot is capped at 10 MB per request/response for memory safety; the actual request and response bodies sent/received over the wire are never truncated by this option.
Default: nil; no HAR recording.
WithHARRecorder¶
Alias for WithHARRecording.
WithHealthCheck¶
Enables a background goroutine that proactively probes url with a plain GET while the circuit breaker is Open, resetting it to Closed on a successful probe instead of waiting for ResetTimeout to elapse naturally. expectedStatus == 0 accepts any 2xx. Has no effect when the circuit breaker is disabled. The probe goroutine stops automatically on Client.Shutdown.
Default: Disabled. interval <= 0 defaults to 1 minute; timeout <= 0 defaults to 10 seconds.
client := relay.New(relay.WithHealthCheck(
"https://api.example.com/health", 30*time.Second, 5*time.Second, http.StatusOK,
))
WithHedging¶
Enables request hedging: if the first attempt hasn't completed within after, a duplicate request is sent; the first response to arrive wins and the other is cancelled. Defaults to 2 concurrent attempts total - use WithHedgingN to change that. Trades a small amount of extra backend load for reduced tail latency.
Default: Disabled (0, no hedging).
Warning: Only hedge idempotent requests (GET, HEAD, PUT, DELETE, OPTIONS, TRACE). Hedging POST/PATCH can cause duplicate side effects unless the endpoint is itself idempotency-safe (see
WithAutoIdempotencyOnSafeRetries).
WithHedgingN¶
Like WithHedging, but with up to maxAttempts concurrent duplicate requests (including the original) instead of the default of 2. after is the delay between launching each successive attempt.
Default: Disabled.
WithHTTP2PushHandler¶
func WithHTTP2PushHandler(handler relay.PushPromiseHandler) relay.Option
type PushPromiseHandler func(pushedURL string, pushedResp *http.Response)
Registers a handler for HTTP/2 server push promises.
Current limitation: the version of
golang.org/x/net/http2this module depends on disables server push at the transport level (SETTINGS_ENABLE_PUSH=0) and no longer exposes a push-interception API. This option is stored for forward compatibility but the handler is never actually invoked at runtime today.
Default: nil.
WithIdleConnTimeout¶
Sets how long an idle keep-alive connection remains open before being evicted from the pool.
Default: 90 seconds.
WithInMemoryCache¶
Creates and attaches the built-in in-memory LRU CacheStore with the given maximum entry count. Shorthand for WithCache(relay.NewInMemoryCacheStore(maxEntries)).
Default: No cache.
WithLoadBalancer¶
Distributes requests across multiple backend URLs. When set, WithBaseURL is ignored and a backend is selected per request according to Strategy.
relay.LoadBalancerConfig fields:
| Field | Type | Description |
|---|---|---|
Backends | []string | Base URLs to balance across. Must not be empty. |
Strategy | relay.LoadBalancerStrategy | relay.RoundRobin (default) or relay.Random. |
Default: Disabled.
client := relay.New(relay.WithLoadBalancer(relay.LoadBalancerConfig{
Backends: []string{"https://api-1.example.com", "https://api-2.example.com"},
Strategy: relay.RoundRobin,
}))
WithLogger¶
Sets the structured logger used for internal relay events (retries, circuit-breaker transitions, rate-limit waits, extension errors). Use relay.SlogAdapter(slog.Default()) to integrate with log/slog, or implement relay.Logger yourself.
Default: A no-op logger; nothing is logged.
WithMaxConcurrentRequests¶
Enables bulkhead isolation: at most n requests may be in flight simultaneously. Additional requests block until a slot frees up or the request's context is cancelled, in which case relay.ErrBulkheadFull (wrapping the context error) is returned. Pair with WithPriorityQueue to dequeue higher-priority requests first once a slot opens.
Default: 0; no concurrency limit.
WithMaxRedirects¶
Sets the maximum number of redirects followed automatically. Set to 0 to disable redirect following entirely (the 3xx response is returned to the caller instead).
Default: 10.
WithMaxResponseBodyBytes¶
Limits how many bytes of a response body Client.Execute buffers. Responses exceeding the limit are truncated; check Response.IsTruncated(). Set to 0 for no limit. Can be overridden per request.
Default: 10 MB.
WithOnAfterResponse¶
Appends a hook called after a successful response is received (after all retries, before returning to the caller). A hook returning a non-nil error propagates as the Client.Execute return value. Runs before WithErrorDecoder.
Default: No hooks.
client := relay.New(relay.WithOnAfterResponse(func(ctx context.Context, resp *relay.Response) error {
log.Printf("response: %d", resp.StatusCode)
return nil
}))
WithOnBeforeRequest¶
Appends a hook called before each request attempt, including retries. A hook returning a non-nil error cancels the request immediately.
Default: No hooks.
client := relay.New(relay.WithOnBeforeRequest(func(ctx context.Context, req *relay.Request) error {
log.Printf("sending %s %s", req.Method(), req.URL())
return nil
}))
WithOnErrorHook¶
func WithOnErrorHook(fn relay.OnErrorHookFunc) relay.Option
type OnErrorHookFunc func(ctx context.Context, req *relay.Request, err error)
Appends a hook invoked whenever Client.Execute returns a non-nil error, after all internal error handling (retries exhausted, etc.). Intended for logging/metrics; the return value is discarded.
Default: No hooks.
client := relay.New(relay.WithOnErrorHook(
func(ctx context.Context, req *relay.Request, err error) {
log.Printf("request to %s failed: %v", req.URL(), err)
},
))
WithOnRetry¶
Registers a callback invoked before each retry sleep, set directly on the active RetryConfig (creating a default one first if none is set yet). attempt is 1-based. Prefer WithBeforeRetryHook if you need the originating *relay.Request too.
Default: No callback.
client := relay.New(relay.WithOnRetry(func(attempt int, resp *http.Response, err error) {
log.Printf("retry attempt %d: %v", attempt, err)
}))
WithOnStateChange¶
Registers a callback invoked on every circuit breaker state transition (Closed/Open/HalfOpen), set directly on the active CircuitBreakerConfig (creating a default one first if none is set yet).
Default: No callback.
client := relay.New(relay.WithOnStateChange(func(from, to relay.CircuitBreakerState) {
log.Printf("circuit breaker: %v -> %v", from, to)
}))
WithPriorityQueue¶
Enables priority-aware dequeuing when the bulkhead (WithMaxConcurrentRequests) is at capacity: higher-priority requests are dequeued before lower-priority ones once a slot frees up, with FIFO order within the same priority. Only has an effect when WithMaxConcurrentRequests is also set.
Default: Disabled; waiting requests are served FIFO regardless of priority.
WithProxy¶
Sets the proxy URL for all requests. Pass "" (or omit the option) to inherit the proxy from the HTTP_PROXY/HTTPS_PROXY environment variables instead.
Default: ""; proxy sourced from environment variables.
WithRateLimit¶
Enables a client-side token-bucket rate limiter. rps is the sustained requests-per-second rate; burst is the maximum tokens that can accumulate above the sustained rate (how many requests can fire immediately before throttling kicks in). Requests block in Wait until a token is available or the request's context is done.
Default: Disabled.
WithRequestCoalescing¶
Enables deduplication of concurrent identical GET/HEAD requests: only one real HTTP call is made per unique key (method + URL + a fixed set of identity headers); all waiting callers receive independent copies of the response. The shared call runs under a detached context, so no individual caller's Request.WithTimeout bounds it - only the client-level WithTimeout does.
Default: Disabled.
WithRequestCompression¶
Enables transparent compression of outgoing request bodies whose serialised size exceeds minBytes (pass <= 0 to use the default of 1024 bytes). Content-Encoding is set automatically. CompressionAuto/CompressionZstd compress with zstd; CompressionBrotli/CompressionGzip use the named algorithm explicitly.
Default: Disabled; request bodies are never compressed.
WithRequestDeduplication¶
Enables singleflight-based deduplication for GET/HEAD requests, collapsing concurrent requests to the same URL into a single real HTTP call. Similar to WithRequestCoalescing but implemented via golang.org/x/sync/singleflight on a simpler key (method + full URL only). Can be overridden per request with Request.WithDeduplication. The shared call runs under a detached context, same caveat as WithRequestCoalescing.
Default: Disabled.
WithRequestLogger¶
Adds transport-level middleware that logs every request/response cycle via logger. Requests are logged at Debug; responses at Debug for 2xx/3xx and Warn for 4xx/5xx, including status code and latency in milliseconds. Passing nil is a safe no-op.
Default: Not added.
WithResponseDecoder¶
Replaces the default encoding/json/encoding/xml decoders used by Response.Decode and relay.ExecuteAs/relay.DecodeAs. contentType receives the response's Content-Type header so the function can pick the right format (e.g. Protocol Buffers).
Default: nil; built-in JSON/XML decoding based on Content-Type.
client := relay.New(relay.WithResponseDecoder(func(ct string, body []byte, v any) error {
if strings.Contains(ct, "protobuf") {
return proto.Unmarshal(body, v.(proto.Message))
}
return json.Unmarshal(body, v)
}))
WithResponseHeaderTimeout¶
Sets the deadline to read response headers after the request body has been fully sent. 0 disables the timeout.
Default: 0 (disabled).
WithResponseValidator¶
Applies a SchemaValidator after each successful (2xx) response body is decoded. If validation fails, Client.Execute returns a relay.ValidationError. The built-in JSON Schema validator is one implementation of SchemaValidator.
Default: nil; no response validation.
WithRetry¶
Replaces the entire retry configuration. Pass nil to restore the package defaults.
relay.RetryConfig fields:
| Field | Type | Description | Package default |
|---|---|---|---|
MaxAttempts | int | Total tries including the first. 1 disables retries. | 3 |
InitialInterval | time.Duration | Base delay before the first retry. | 100ms |
MaxInterval | time.Duration | Caps the computed backoff regardless of attempt count. | 30s |
Multiplier | float64 | Growth factor per attempt (2.0 = classic exponential backoff). | 2.0 |
RandomFactor | float64 | ± jitter proportional to the computed interval (0 disables). | 0.5 |
RetryableStatus | []int | HTTP status codes that trigger a retry. | [429, 500, 502, 503, 504] |
RetryIf | func(resp *http.Response, err error) bool | Optional predicate; returning false suppresses a retry the built-in logic would otherwise perform. | nil |
OnRetry | func(attempt int, resp *http.Response, err error) | Optional callback before each retry sleep. | nil |
Default: The table's "Package default" column; retries are always active unless WithDisableRetry is used or MaxAttempts: 1 is set explicitly.
client := relay.New(relay.WithRetry(&relay.RetryConfig{
MaxAttempts: 4,
InitialInterval: 250 * time.Millisecond,
MaxInterval: 8 * time.Second,
Multiplier: 2.0,
RandomFactor: 0.3,
}))
WithRetryBudget¶
Sets a sliding-window retry budget that caps the fraction of requests that may be retried within the window, preventing retry storms when a downstream service degrades.
relay.RetryBudget fields:
| Field | Type | Description |
|---|---|---|
Ratio | float64 | Max fraction of requests that can be retried (e.g. 0.1 = 10%). |
Window | time.Duration | Sliding window duration. |
MinRetry | int | Minimum number of retries always allowed regardless of ratio (default 10 if unset). |
Default: nil; no budget enforced.
client := relay.New(relay.WithRetryBudget(&relay.RetryBudget{
Ratio: 0.1,
Window: 10 * time.Second,
}))
WithRetryIf¶
Sets a custom retry predicate on the active RetryConfig (creating a default one first if none is set yet). Evaluated when the built-in logic would retry; returning false suppresses that retry.
Default: No custom predicate; the built-in status/error classification decides.
client := relay.New(relay.WithRetryIf(func(resp *http.Response, err error) bool {
if resp != nil {
return resp.StatusCode == http.StatusServiceUnavailable
}
return true
}))
WithRootCA¶
Adds a PEM-encoded CA certificate to the client's TLS trust store, for private PKI environments where the server certificate is signed by an internal CA. Multiple calls append additional CAs without replacing earlier ones.
Default: The system certificate pool only.
WithSigner¶
func WithSigner(s relay.RequestSigner) relay.Option
type RequestSigner interface {
Sign(req *http.Request) error
}
Sets a RequestSigner invoked for every outgoing request (including retries), after default headers, the idempotency key, and WithCredentialProvider have been applied. The signer receives the fully-built *http.Request and may add/modify headers or compute a body digest. Use relay.RequestSignerFunc to adapt a plain function. Built-in implementation: ext/sigv4 (AWS SigV4). For your own HMAC/JWS/OAuth 1.0a scheme, implement RequestSigner directly.
Default: nil; no signing.
client := relay.New(relay.WithSigner(relay.RequestSignerFunc(func(r *http.Request) error {
r.Header.Set("Authorization", "Bearer "+apiKey)
return nil
})))
WithRequestSigner¶
Alias for WithSigner.
WithSRVDiscovery¶
Sets an SRVResolver on the client. Before each request, the resolver performs (or serves from cache) a DNS SRV lookup and the request's Host is replaced with the resolved host:port target. Build the resolver with relay.NewSRVResolver(service, proto, name, scheme, opts...), where opts are relay.SRVOption values - WithSRVTTL(d) to cache lookups, WithSRVBalancer(b) to pick relay.SRVRoundRobin (default), relay.SRVRandom, or relay.SRVPriority. Note: WithSRVTTL/WithSRVBalancer configure the resolver itself (SRVOption), not the client (relay.Option) - they're passed to NewSRVResolver, not to relay.New.
Default: No SRV-based discovery.
resolver := relay.NewSRVResolver("http", "tcp", "myservice.example.com", "https",
relay.WithSRVTTL(30*time.Second),
relay.WithSRVBalancer(relay.SRVRoundRobin),
)
client := relay.New(relay.WithSRVDiscovery(resolver))
WithTimeout¶
Sets the end-to-end deadline for a complete request/response cycle, including all retry attempts. Use Request.WithTimeout for per-request overrides.
Default: 30 seconds.
WithTiming¶
Enables per-request timing instrumentation (httptrace). When enabled, Response.Timing is populated with a DNS/TCP/TLS/TTFB/content-transfer breakdown at the cost of roughly 10 additional allocations per Client.Execute call. Automatically enabled by WithAdaptiveTimeout, since adaptive timeout needs latency samples to function.
Default: Off; Response.Timing fields stay zero unless timing is enabled directly or via WithAdaptiveTimeout.
WithTLSConfig¶
Replaces the default *tls.Config used by the transport. Use this for mTLS beyond what WithClientCert/WithClientCertPEM cover, custom root CAs beyond WithRootCA, or minimum TLS version enforcement.
Default: A config enforcing TLS 1.2 as the minimum version.
WithTLSHandshakeTimeout¶
Sets the deadline for completing the TLS handshake.
Default: 10 seconds.
WithTransportAdapter¶
Registers a custom http.RoundTripper for requests whose URL scheme matches scheme (e.g. "h3", "grpc"). Requests with that scheme are dispatched to rt instead of the default transport. "http"/"https" cannot be overridden this way - use WithTransportMiddleware for those. Used internally by ext/http3 to route h3:// requests over QUIC.
Default: Only http/https are handled, by the default transport.
WithTransportMiddleware¶
Appends one or more http.RoundTripper middleware functions. Middleware is applied outermost-last: the last appended middleware is the first to intercept a request. This is the general-purpose way to add cross-cutting transport concerns (logging, tracing, header injection).
Default: No middleware.
type roundTripFunc func(*http.Request) (*http.Response, error)
func (f roundTripFunc) RoundTrip(r *http.Request) (*http.Response, error) { return f(r) }
client := relay.New(relay.WithTransportMiddleware(
func(next http.RoundTripper) http.RoundTripper {
return roundTripFunc(func(req *http.Request) (*http.Response, error) {
req.Header.Set("X-Correlation-ID", generateCorrelationID())
return next.RoundTrip(req)
})
},
))
WithURLNormalisation¶
Controls how WithBaseURL is resolved against request paths. relay.NormalisationAuto (default) intelligently detects API-shaped base URLs (e.g. /v1, /odata, /graphql) and preserves their path via safe string concatenation, while using zero-allocation RFC 3986 resolution for host-only base URLs. relay.NormalisationRFC3986 forces strict RFC 3986 resolution everywhere (fastest, but a base URL with a path component like /v1 gets replaced rather than extended by an absolute request path). relay.NormalisationAPI forces safe string normalisation everywhere.
Default: relay.NormalisationAuto.
WithUnixSocket¶
Routes all requests through a Unix domain socket at socketPath, regardless of the host in the request URL (e.g. talking to the Docker daemon at /var/run/docker.sock). WithBaseURL still controls the HTTP Host header and path; only the network transport layer changes. HTTP/2 is disabled for Unix socket connections. No-op on js/wasm builds.
Default: TCP.
client := relay.New(
relay.WithBaseURL("http://localhost"),
relay.WithUnixSocket("/var/run/docker.sock"),
)
WithWebSocketDialTimeout¶
Sets the handshake timeout used by Client.ExecuteWebSocket. Zero falls back to the client's WithTimeout value.
Default: 0 (falls back to WithTimeout).
Per-request authentication (not client options)¶
A static API key, Basic Auth, or Bearer token attached to every request from a single client is most often set once via WithDefaultHeaders, WithSigner, or WithCredentialProvider above. But relay also has dedicated per-request builder methods on *relay.Request for the common cases, set individually on whichever requests need them:
req := client.Get("/orders").
WithBearerToken(token). // Authorization: Bearer <token>
WithBasicAuth(user, pass). // Authorization: Basic <base64>
WithAPIKey("X-API-Key", key) // arbitrary header-based key
These are methods on *relay.Request, not relay.Option functions - they cannot be passed to relay.New.
Complete Configuration Example¶
The following example shows a production-grade client combining multiple options:
package main
import (
"context"
"crypto/tls"
"log"
"net/http"
"time"
"github.com/jhonsferg/relay"
)
func main() {
client := relay.New(
relay.WithBaseURL("https://api.example.com"),
relay.WithDefaultHeaders(map[string]string{
"Authorization": "Bearer prod-token-value",
}),
relay.WithTimeout(15*time.Second),
relay.WithRetry(&relay.RetryConfig{
MaxAttempts: 4,
InitialInterval: 250 * time.Millisecond,
MaxInterval: 8 * time.Second,
Multiplier: 2.0,
RandomFactor: 0.5,
}),
relay.WithAutoIdempotencyOnSafeRetries(),
relay.WithCircuitBreaker(&relay.CircuitBreakerConfig{
MaxFailures: 10,
ResetTimeout: 1 * time.Minute,
SuccessThreshold: 3,
}),
relay.WithRateLimit(500, 50),
relay.WithMaxConcurrentRequests(100),
relay.WithDNSCache(30*time.Second),
relay.WithTLSConfig(&tls.Config{
MinVersion: tls.VersionTLS12,
}),
relay.WithBeforeRetryHook(func(ctx context.Context, attempt int, req *relay.Request, resp *http.Response, err error) {
log.Printf("[retry] attempt %d for %s: %v", attempt, req.URL(), err)
}),
relay.WithOnErrorHook(func(ctx context.Context, req *relay.Request, err error) {
log.Printf("[error] %s -> %v", req.URL(), err)
}),
)
defer client.Shutdown(context.Background())
resp, err := client.Execute(client.Get("/health"))
if err != nil {
log.Fatal(err)
}
defer relay.PutResponse(resp)
log.Println("health check:", resp.StatusCode)
}