Table of contents
Part of nurago, a collection of independent Go packages for backend services.
import "github.com/tecnickcom/nurago/pkg/httpretrier"
Package httpretrier provides configurable retry execution for outbound HTTP requests.
It wraps an HTTPClient with retry orchestration driven by a pluggable
RetryIfFn. HTTPRetrier.Do executes a request up to a bounded number of
attempts, applying delay growth and jitter between retries. The retry decision
function receives both *http.Response and error, enabling policy decisions
based on transport failures and/or HTTP status codes.
Retries are applied per request rather than per client, so an already configured client (instrumented, authenticated, or both) keeps its behavior and is wrapped instead of replaced.
Built-in Retry Policies
Predefined helpers are provided for common semantics:
RetryIfForReadRequestsfor idempotent reads (e.g. GET)RetryIfForWriteRequestsfor state-changing writes (e.g. POST/PUT/PATCH)RetryIfFnByHTTPMethodto select one of the above from method name
The default policy retries only when err != nil.
Backoff Behavior
Delay progression is configurable via:
- total attempts cap (
WithAttempts) - initial delay (
WithDelay) - multiplicative delay factor (
WithDelayFactor) - random jitter ceiling (
WithJitter) - maximum delay ceiling (
WithMaxDelay) - jitter strategy (
WithJitterStrategy)
This produces bounded exponential-style backoff with randomization, helping
reduce synchronized retry storms. Optionally, WithRespectRetryAfter makes the
retrier wait at least the server-provided Retry-After delay, and WithOnRetry
exposes each scheduled retry for logging or metrics.
Request Body Replay
When a request has a body and retries are needed, the retrier relies on
Request.GetBody to recreate the body stream for subsequent attempts. If the
request has a body that cannot be recreated (GetBody missing or failing),
retries cannot continue and Do returns an error. Bodyless requests (e.g. a GET
with no body) retry without restriction.
When To Use
- Calls to an upstream API fail intermittently with 429 or 5xx responses.
- Read and write requests need different retry policies, because replaying a write may duplicate a side effect.
- The server sends Retry-After and you want it respected, bounded by a cap you choose.
Example
// A server that fails twice before succeeding.
attempts := 0
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
attempts++
if attempts < 3 {
w.WriteHeader(http.StatusServiceUnavailable)
return
}
w.WriteHeader(http.StatusOK)
}))
defer srv.Close()
retrier, err := httpretrier.New(
srv.Client(),
httpretrier.WithAttempts(4),
httpretrier.WithDelay(time.Millisecond),
httpretrier.WithJitter(time.Microsecond), // kept tiny so the example runs quickly
httpretrier.WithRetryIfFn(httpretrier.RetryIfForReadRequests),
)
if err != nil {
fmt.Println(err)
return
}
req, err := http.NewRequestWithContext(context.TODO(), http.MethodGet, srv.URL, nil)
if err != nil {
fmt.Println(err)
return
}
resp, err := retrier.Do(req)
if err != nil {
fmt.Println(err)
return
}
defer func() { _ = resp.Body.Close() }()
fmt.Println(resp.StatusCode, "after", attempts, "attempts")
// Output:
// 200 after 3 attempts
Full source is in example_httpretrier_test.go. More runnable examples are on pkg.go.dev.
Dependencies
This package reaches no external module: it uses only the Go standard library.