httpclient

package module
v0.8.4 Latest Latest
Warning

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

Go to latest
Published: Jun 10, 2026 License: MIT Imports: 10 Imported by: 0

Documentation

Overview

Package httpclient is a thin, generics-friendly fasthttp client wrapper with built-in Elastic APM instrumentation (via apmcore), pluggable structured-log hook, and optional retry-with-backoff.

Top-level functions (GET[O], POST[O], …) cover the 99% case:

type charge struct{ ID string `json:"id"` }
out, err := httpclient.POST[charge](ctx, httpclient.RequestOptions{
    URL:     "https://api.example.com/charge",
    Headers: httpclient.JSONHeaders(),
    Data:    map[string]any{"amount": 100},
})

Configure once at boot:

httpclient.UseClient(&fasthttp.Client{ReadTimeout: 10*time.Second})
httpclient.SetHook(func(r httpclient.Record) {
    logger.LogCtx(r.Ctx).Info("← outgoing", zap.Any("outgoing", buildOutgoing(r)))
})

All requests are wrapped in an APM exit span and propagate the active transaction's traceparent header automatically.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func AddHook added in v0.7.0

func AddHook(h Hook)

AddHook appends h to the current hook chain. If no hook is installed, h becomes the sole hook. Subsequent calls compose without dropping prior hooks — safe to call multiple times at boot (e.g. log hook + audit hook).

func DELETE

func DELETE[O any](ctx context.Context, opts RequestOptions) (*O, error)

DELETE issues a DELETE and decodes the body as O.

func Do

func Do(ctx context.Context, method string, opts RequestOptions) ([]byte, error)

Do executes a single fasthttp call described by opts. Returns the raw response body and an error. Status codes outside [200, 300) produce a non-nil error but the body is still returned.

The call is wrapped in an APM exit span via apmcore.TraceFastHTTPCall and the configured Hook is invoked once per attempt.

func FormHeaders

func FormHeaders() map[string]string

FormHeaders returns a map with Content-Type: application/x-www-form-urlencoded.

func GET

func GET[O any](ctx context.Context, opts RequestOptions) (*O, error)

GET issues a GET and decodes the body as O.

func JSONHeaders

func JSONHeaders() map[string]string

JSONHeaders returns a map with Content-Type: application/json. Useful shorthand in RequestOptions.Headers.

func PATCH

func PATCH[O any](ctx context.Context, opts RequestOptions) (*O, error)

PATCH issues a PATCH and decodes the body as O.

func POST

func POST[O any](ctx context.Context, opts RequestOptions) (*O, error)

POST issues a POST and decodes the body as O.

func PUT

func PUT[O any](ctx context.Context, opts RequestOptions) (*O, error)

PUT issues a PUT and decodes the body as O.

func Request

func Request[O any](ctx context.Context, method string, opts RequestOptions) (*O, error)

Request is the generic verb-agnostic helper used by GET / POST / … It executes the call and unmarshals the response body into *O.

If the call returns a *StatusError, both the body and the error are returned — so callers can still inspect partial responses.

func SetConfig

func SetConfig(c *Config)

SetConfig replaces the active configuration. Pass nil to restore defaults.

func SetHook

func SetHook(h Hook)

SetHook installs a global hook called after every attempt, replacing any previously installed hook. Pass nil to disable. To add a hook without removing an existing one, use AddHook instead.

func UseClient

func UseClient(c *fasthttp.Client)

UseClient swaps the underlying *fasthttp.Client used by all top-level helpers. Pass nil to restore the default.

Types

type Config

type Config struct {
	DefaultTimeout time.Duration // default 30s; zero means no timeout
}

Config tunes package defaults. Apply via SetConfig.

type Hook

type Hook func(Record)

Hook is invoked once per attempt — including retried ones — right after the call returns. Hooks must not panic; use them for logging or metrics, not control flow.

type Record

type Record struct {
	Ctx            context.Context
	Method         string
	URL            string
	Status         int
	ResponseTime   time.Duration
	ReqHeaders     map[string]string
	ReqBody        []byte // bytes that hit the wire (already JSON or form-encoded)
	ResHeaders     map[string]string
	ResBody        []byte
	Err            error
	Attempt        int  // 1-based; >1 means this is a retry
	RetryExhausted bool // true on the final attempt when retries are exhausted due to a retryable failure
}

Record carries everything a hook (logger / auditor) needs to know about an outgoing call.

type RequestOptions

type RequestOptions struct {
	URL         string
	QueryString map[string]string
	Headers     map[string]string

	// Data is the request body. Any value is marshalled with sonic
	// (JSON) by default; when Headers["Content-Type"] is
	// "application/x-www-form-urlencoded" and Data is a map[string]any
	// or map[string]string, it is form-encoded instead.
	Data any

	// Timeout overrides the per-call timeout. Zero falls back to
	// Config.DefaultTimeout.
	Timeout time.Duration

	// Retry policy. Zero attempts → no retry (one call). Each attempt
	// produces its own APM span.
	Retry RetryPolicy
}

RequestOptions describes a single outgoing call. All fields are optional except URL.

type RetryPolicy

type RetryPolicy struct {
	// MaxAttempts is the total number of calls (1 = no retry).
	MaxAttempts int
	// InitialBackoff is the wait before the second attempt.
	InitialBackoff time.Duration
	// MaxBackoff caps the per-attempt wait.
	MaxBackoff time.Duration
	// Multiplier scales the backoff after each attempt (default 2 if 0).
	Multiplier float64
	// ShouldRetry decides whether err+status warrants another attempt.
	// Nil → retry on transport error or 5xx.
	ShouldRetry func(status int, err error) bool
}

RetryPolicy describes how many times to retry a failed call and how to wait between attempts.

type StatusError

type StatusError struct {
	Method     string
	URL        string
	StatusCode int
	Body       []byte
}

StatusError indicates the request reached the server but the status code is outside [200, 300). Body is preserved.

func (*StatusError) Error

func (e *StatusError) Error() string

Jump to

Keyboard shortcuts

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