-
Release v1.0.0 Stable
released this
2026-05-11 19:51:01 -06:00 | 1 commits to main since this releasev1.0.0
code.nochebuena.dev/go/httpclientOverview
httpclientv1.0.0 commits the HTTP client API as stable. All v0.9.0 roadmap items are
resolved. The module wrapsnet/httpwith automatic retry, circuit breaking, request-ID
propagation, and generic typed JSON helpers for both outbound requests and responses.What Changed Since v0.9.0
Retry-on-429 with Retry-After
The retry loop now handles HTTP 429 Too Many Requests. Previously only network errors and
>= 500responses triggered a retry; 429 was returned immediately to the caller.When the server responds with 429 and includes a
Retry-Afterheader (integer seconds),
that duration is used as the delay before the next attempt. Without the header, the
configuredBackOffDelayapplies as usual.// Server responds: 429 Too Many Requests + Retry-After: 5 // Client waits 5 seconds, then retries automatically.DoJSONRequest[Req, Resp any]
New free generic function complementing
DoJSON. Use it for POST/PUT/PATCH calls where
the request also carries a JSON body:// Before (manual boilerplate every time): data, _ := json.Marshal(myReq) req, _ := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(data)) req.Header.Set("Content-Type", "application/json") resp, err := httpclient.DoJSON[MyResponse](ctx, client, req) // After: resp, err := httpclient.DoJSONRequest[MyRequest, MyResponse](ctx, client, "POST", url, myReq)Use
DoJSONfor GET/DELETE (no request body); useDoJSONRequestfor POST/PUT/PATCH.Dependency bumps
logzandxerrorspromoted to v1.0.0.Roadmap items resolved
Item Resolution Retry-on-429 with Retry-After✅ Done — respects header when present, falls back to BackOffDelay DoJSONRequest[Req, Resp]✅ Done — serialises body, sets Content-Type, delegates to DoJSON Circuit breaker state metrics ❌ No — exposing gobreaker.Statecouples the Client interface to a third-party type; state transitions are already logged at Warn levelDoStream❌ No — streaming is a fundamentally different API contract; use client.Dodirectly for streaming responsesProduction feedback on defaults ✅ Validated as-is — 30s/5s/3 retries/1s delay/10 CB threshold/1m CB timeout confirmed in production Full API (stable)
Client—Do(req *http.Request) (*http.Response, error).Config—Name,Timeout,DialTimeout,MaxRetries,RetryDelay,CBThreshold,CBTimeout; env-tag support.New(logger logz.Logger, cfg Config) Client— constructor with circuit breaker + retry.NewWithDefaults(logger logz.Logger) Client— convenience constructor with production defaults.DefaultConfig() Config— returns the default configuration values.DoJSON[T any](ctx, client, req) (*T, error)— executes a request and decodes the JSON response intoT. Use for GET/DELETE.DoJSONRequest[Req, Resp any](ctx, client, method, url, body) (*Resp, error)— serialisesbodyas JSON, constructs the request, and decodes the response. Use for POST/PUT/PATCH.MapStatusToError(code int, msg string) error— maps HTTP status codes to xerrors:404→ErrNotFound,400→ErrInvalidInput,401→ErrUnauthorized,403→ErrPermissionDenied,409→ErrAlreadyExists,429→ErrUnavailable, others →ErrInternal.Migration from v0.9.0
No breaking changes.
DoJSONRequestis a new addition; existing code usingDoJSONis unaffected. The retry-on-429 behavior is a change inDo— callers that previously handled 429 manually after the call should review whether that handling is still needed.go get code.nochebuena.dev/go/httpclient@v1.0.0Downloads
-
Release v0.9.0 Stable
released this
2026-03-19 07:05:28 -06:00 | 2 commits to main since this releasev0.9.0
code.nochebuena.dev/go/httpclientOverview
httpclientis a resilient HTTP client that wrapsnet/httpwith automatic retry, circuit
breaking, request-ID propagation, and a generic typed JSON helper. It is intended for
application services that make outbound calls to external HTTP APIs and need consistent
reliability behaviour and error mapping without boilerplate.This is the first stable release. The API was designed through multiple architecture reviews
and validated end-to-end via the todo-api proof-of-concept. It is versioned at v0.9.0 rather
than v1.0.0 because the library has not yet been exercised in production across all edge cases;
the pre-1.0 version preserves the option for minor API refinements without a major bump.What's Included
Clientinterface with a singleDo(req *http.Request) (*http.Response, error)methodNew(logger, cfg) Clientconstructor with fullConfigcontrolNewWithDefaults(logger) Clientconvenience constructorConfigstruct with env-tag support (HTTP_CLIENT_NAME,HTTP_TIMEOUT,
HTTP_DIAL_TIMEOUT,HTTP_MAX_RETRIES,HTTP_RETRY_DELAY,HTTP_CB_THRESHOLD,
HTTP_CB_TIMEOUT)- Retry via
github.com/avast/retry-go/v4with exponential backoff; retries only on
network errors and HTTP 5xx responses; 4xx responses are not retried - Circuit breaker via
github.com/sony/gobreaker; trips afterCBThresholdconsecutive
failures; open circuit returnsxerrors.ErrUnavailable - Circuit breaker wraps the entire retry loop: the breaker sees one failure per
fully-exhausted retry sequence, not per individual attempt X-Request-IDheader propagated from context to outbound requests vialogz.GetRequestID- Duck-typed
logz.Loggerinterface for log injection DoJSON[T](ctx, client, req) (*T, error)generic helper: executes the request, maps
HTTP 4xx/5xx to xerrors codes, and decodes the JSON body intoTMapStatusToError(code int, msg string) errorexported function mapping HTTP status codes
to xerrors types:- 404 →
ErrNotFound, 400 →ErrInvalidInput, 401 →ErrUnauthorized,
403 →ErrPermissionDenied, 409 →ErrAlreadyExists, 429 →ErrUnavailable,
all others →ErrInternal
- 404 →
Installation
go get code.nochebuena.dev/go/httpclient@v0.9.0Requires Go 1.21 or later. Depends on
code.nochebuena.dev/go/logz,
code.nochebuena.dev/go/xerrors,github.com/sony/gobreaker, and
github.com/avast/retry-go/v4.Does not depend on
launcherorhealth— it has no lifecycle and is not a component.Design Highlights
Circuit breaker wraps retry.
gobreaker.Executecontains the entireretry.Doloop.
The breaker counts one failure only when all retries are exhausted, not on each attempt. This
prevents transient 5xx bursts from immediately tripping the breaker.Retry only on 5xx. 4xx responses represent caller errors; retrying them is wasteful and
can have side effects. Only network errors and HTTP 5xx responses enter the retry loop.Request-ID propagation is automatic.
logz.GetRequestID(ctx)is called inside the retry
function on every attempt. No manual header setting is needed in calling code; the header is
omitted if no ID is present in the context.DoJSON[T]is a free function, not a method. It works with anyClientimplementation,
including mocks, without requiring a concrete type.MapStatusToErroris exported and independent. It can be used by any code that already
has an*http.Responsein hand, not just callers that went throughDoJSON.Known Limitations & Edge Cases
- No streaming support.
DoJSONreads the entire response body into memory.
client.Docan be used directly for streaming responses, but response body management
is left to the caller. - No multipart or form-encoded body helpers. Only JSON request/response is covered
by the helpers; other content types require manual construction. - Circuit breaker state is per-
Clientinstance. TwoClientvalues targeting the
same downstream service have independent circuit breakers. To share state across a
service's request handlers, inject a singleClientinstance. - Retry triggers only on 5xx. Responses in the 4xx range are not retried, even if the
caller believes the failure is transient (e.g. 429 Too Many Requests with a Retry-After
header is mapped toErrUnavailablebut not retried automatically).
v0.9.0 → v1.0.0 Roadmap
- Evaluate retry-on-429 with Retry-After header respect.
- Consider exposing circuit breaker state metrics (open/closed/half-open) for observability.
- Assess whether a streaming variant of
DoJSON(e.g.DoStream) is needed. - Consider a
DoJSONRequest[Req, Resp]helper that also serialises the request body. - Gather production feedback on default timeout and retry values before hardening the config.
Downloads