The stellabill-backend project implements a shared, resilient HTTP client in internal/httpclient to standardize outbound network communications. This wrapper prevents cascading failures, guards against retry storms, and ensures safe idempotency when making external API calls.
- Bounded Timeouts: Enforces individual request timeouts to prevent hanging connections or partial read deadlocks (
RequestTimeout). - Jittered Exponential Backoff: Uses an exponential backoff strategy with up to 20% random jitter to prevent "thundering herd" retry storms.
- Circuit Breaker Pattern: Global circuit breakers (partitioned by target
host) fast-fail requests if the upstream service experiences a high failure rate. - Idempotency Guard: Strictly prevents duplicate side effects by refusing to retry non-idempotent methods (
POST,PATCH) unless anIdempotency-Keyheader is present. - Rate-Limit Respect: Automatically parses and respects the
Retry-Afterheader for429 Too Many Requestsand503 Service Unavailableresponses.
All resilient HTTP calls automatically track operational health through Prometheus metrics defined in internal/httpclient/metrics.go.
| Metric Name | Type | Labels | Description |
|---|---|---|---|
http_client_retries_total |
Counter | host, method |
Tracks the number of retry attempts triggered. |
http_client_failures_total |
Counter | host, method, reason |
Tracks ultimate request failures. Reasons include timeout, non_2xx, error, and max_retries_reached. |
http_client_circuit_state |
Gauge | host |
Tracks the live circuit breaker state: 0 (Closed), 1 (Open), 2 (Half-Open). |
Understanding the client's internal routing behavior is critical for safe integrations.
The client will seamlessly retry a request when:
- A transient network error occurs (e.g., DNS failure, connection reset).
- A request times out (respecting
RequestTimeout). - The upstream server returns a
5xxstatus code (e.g.,500,502,504). - The upstream server returns a
429 Too Many Requestsor503 Service Unavailable(overriding the backoff with theRetry-Afterheader if provided).
Idempotency Constraint: Retries are only executed if the HTTP method is intrinsically idempotent (GET, PUT, DELETE), OR if the caller explicitly provided an Idempotency-Key HTTP header.
The client will instantly fail and return an error without hitting the network when:
- The Circuit is Open: If the downstream
hosthas breached themaxFailuresthreshold recently, the circuit breaker opens and returnsErrCircuitOpenimmediately. - Non-Idempotent Constraint: If a
POSTorPATCHrequest fails on the first attempt and lacks anIdempotency-Keyheader, the client aborts to avoid duplicate side effects (unlessRetryNonIdempotentconfiguration is forcibly set to true). - Max Retries Reached: Once
MaxRetriesattempts have been exhausted, the final response is returned to the caller.
// Initialize the client with the remote host and zap logger
logger := security.ProductionLogger()
client := httpclient.NewClient("api.external-service.com", logger)
// Prepare an idempotent POST request
req, _ := http.NewRequest(http.MethodPost, "https://api.external-service.com/v1/resource", body)
req.Header.Set("Idempotency-Key", "unique-event-id-123")
// Execute resiliently
resp, err := client.Do(req)
if err != nil {
// Handle terminal network failure
}
defer resp.Body.Close()
// Handle response (check resp.StatusCode, etc.)