httpclient

package module
v1.7.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 19, 2026 License: AGPL-3.0 Imports: 17 Imported by: 0

README

einherjar/httpclient

version license go

To cross the realms, one must know the road — and how to wait when the bridge is down.

code.nochebuena.dev/einherjar/httpclient is the outbound HTTP client component of the Einherjar framework. It composes retry (via avast/retry-go) and a circuit breaker (via sony/gobreaker) behind a single Provider interface with one method: Do. Generic helpers DoJSON and DoJSONRequest reduce boilerplate for JSON APIs without hiding the underlying client.


Usage

Setup
import "code.nochebuena.dev/einherjar/httpclient"

// With env-var config
client := httpclient.New(logger, httpclient.DefaultConfig())

// Or zero-config with defaults
client := httpclient.NewWithDefaults(logger)

httpclient is not a lifecycle.Component — it is stateless and requires no registration with the launcher.

Sending requests
req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.example.com/users", nil)
if err != nil {
    return err
}

resp, err := client.Do(req)
if err != nil {
    return err
}
defer resp.Body.Close()
JSON GET helper
type User struct {
    ID   string `json:"id"`
    Name string `json:"name"`
}

req, _ := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.example.com/users/123", nil)
user, err := httpclient.DoJSON[User](ctx, client, req)
// user is *User on success
JSON POST helper
type CreateReq struct {
    Name  string `json:"name"`
    Email string `json:"email"`
}
type CreateResp struct {
    ID string `json:"id"`
}

resp, err := httpclient.DoJSONRequest[CreateReq, CreateResp](
    ctx, client,
    http.MethodPost, "https://api.example.com/users",
    CreateReq{Name: "Alice", Email: "alice@example.com"},
)
// resp is *CreateResp on success
Error mapping
// Map HTTP status codes to xerrors for consistent error handling
err := httpclient.MapStatusToError(resp.StatusCode, "upstream error")
// 400 → ErrInvalidInput
// 401 → ErrUnauthorized
// 403 → ErrPermissionDenied
// 404 → ErrNotFound
// 409 → ErrAlreadyExists
// 429 → ErrRateLimited
// 503 → ErrUnavailable

Environment variables

Variable Required Default Description
EINHERJAR_HTTP_CLIENT_NAME No http Circuit breaker name (appears in logs)
EINHERJAR_HTTP_TIMEOUT No 30s Total request timeout
EINHERJAR_HTTP_DIAL_TIMEOUT No 5s TCP connection timeout
EINHERJAR_HTTP_MAX_RETRIES No 3 Maximum retry attempts
EINHERJAR_HTTP_RETRY_DELAY No 1s Delay between retries
EINHERJAR_HTTP_CB_THRESHOLD No 10 Consecutive failures before circuit opens
EINHERJAR_HTTP_CB_TIMEOUT No 1m Time before circuit attempts half-open

Dependency graph

contracts  (zero dependencies)
    ↑
  core
    ↑
httpclient  (contracts, core, retry-go, gobreaker)
    ↑
  your app

Verification

cd httpclient/
go build ./...
go vet ./...
go test ./...
gofmt -l .

A warrior who cannot reach the other realm is useless to the battle. Build the bridge. Make it resilient. Know when to wait.

Documentation

Overview

Package httpclient provides a resilient HTTP client with automatic retry, circuit breaking, request-ID propagation, and typed JSON helpers.

Basic Usage

client := httpclient.NewWithDefaults(logger)
resp, err := client.Do(req)

Typed JSON Helpers

DoJSON decodes the response body into T without needing a manual http.Request:

type UserResp struct{ ID, Name string }
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
user, err := httpclient.DoJSON[UserResp](ctx, client, req)

DoJSONRequest marshals a request body, sends it, and decodes the response:

result, err := httpclient.DoJSONRequest[CreateReq, CreateResp](
    ctx, client, http.MethodPost, url, createReq)

Request ID Propagation

If the context carries a request ID (set by core/logz.WithRequestID or the web/mw.RequestID middleware), it is forwarded as X-Request-ID on every outbound attempt, including retries.

Resilience

The retry loop (avast/retry-go) wraps individual HTTP attempts. The circuit breaker (sony/gobreaker) wraps the entire retry sequence, so the breaker opens after CBThreshold fully-exhausted retry sequences fail — not after CBThreshold individual HTTP errors.

Configuration

EINHERJAR_HTTP_CLIENT_NAME  — circuit breaker label; default "http"
EINHERJAR_HTTP_TIMEOUT      — total request timeout; default 30s
EINHERJAR_HTTP_DIAL_TIMEOUT — TCP dial timeout; default 5s
EINHERJAR_HTTP_MAX_RETRIES  — attempts per request; default 3
EINHERJAR_HTTP_RETRY_DELAY  — base delay between retries; default 1s
EINHERJAR_HTTP_CB_THRESHOLD — consecutive failures to open breaker; default 10
EINHERJAR_HTTP_CB_TIMEOUT   — breaker half-open probe interval; default 1m

Index

Constants

This section is empty.

Variables

View Source
var Module observability.Identifiable = &moduleID{}

Module identifies this package to observability systems. httpclient is a stateless provider — it is not registered with the launcher as a lifecycle component. Register Module manually with any version registry if needed.

Functions

func DoJSON

func DoJSON[T any](ctx context.Context, client Provider, req *http.Request) (*T, error)

DoJSON executes req and decodes the JSON response body into T. Returns a xerrors-typed error for HTTP 4xx/5xx responses.

func DoJSONRequest

func DoJSONRequest[Req, Resp any](ctx context.Context, client Provider, method, rawURL string, body Req) (*Resp, error)

DoJSONRequest marshals body as JSON, sends it with the given method to rawURL, and decodes the response into Resp. For requests without a body, use DoJSON instead.

func MapStatusToError

func MapStatusToError(code int, msg string) error

MapStatusToError maps an HTTP status code to the matching xerrors type.

Types

type Config

type Config struct {
	// Name identifies this client in logs and circuit breaker metrics.
	Name        string        `env:"EINHERJAR_HTTP_CLIENT_NAME"  envDefault:"http"`
	Timeout     time.Duration `env:"EINHERJAR_HTTP_TIMEOUT"      envDefault:"30s"`
	DialTimeout time.Duration `env:"EINHERJAR_HTTP_DIAL_TIMEOUT" envDefault:"5s"`
	MaxRetries  uint          `env:"EINHERJAR_HTTP_MAX_RETRIES"  envDefault:"3"`
	RetryDelay  time.Duration `env:"EINHERJAR_HTTP_RETRY_DELAY"  envDefault:"1s"`
	CBThreshold uint32        `env:"EINHERJAR_HTTP_CB_THRESHOLD" envDefault:"10"`
	CBTimeout   time.Duration `env:"EINHERJAR_HTTP_CB_TIMEOUT"   envDefault:"1m"`
}

Config holds configuration for the HTTP client.

func DefaultConfig

func DefaultConfig() Config

DefaultConfig returns a Config with sensible production defaults.

type Provider

type Provider interface {
	Do(req *http.Request) (*http.Response, error)
}

Provider executes HTTP requests with automatic retry and circuit breaking. Inject Provider into services that make outbound HTTP calls; construct with New or NewWithDefaults.

func New

func New(logger logging.Logger, cfg Config) Provider

New returns a Provider with the given configuration.

func NewWithDefaults

func NewWithDefaults(logger logging.Logger) Provider

NewWithDefaults returns a Provider with sensible defaults.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL