request

package
v0.1.2 Latest Latest
Warning

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

Go to latest
Published: Jul 7, 2026 License: MIT Imports: 13 Imported by: 0

Documentation

Overview

Package request is the shared HTTP transport every integration surface runs on: the API surface, the scraper, and the search providers all make their outbound calls through one Transport rather than each holding its own *http.Client. That keeps a single home for the cross-cutting concerns a remote, unattended agent must get right: bounded timeouts, retry with backoff on the right failure classes only, per-host rate limiting, and typed error classification through the fault model.

Two design choices make the transport testable and deterministic. It runs every call against a Doer interface (*http.Client satisfies it), so a test or a chaos adapter substitutes the network without touching call sites. And every delay is taken through a clock.Timing rather than time.Sleep, so a clock.Manual drives backoff and rate-limit waits with no real sleeping and a run reproduces exactly.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func StatusFault

func StatusFault(status int) *fault.Error

StatusFault returns the classified fault for a status code, or nil for a success (below 400). The code is "http_<status>" so it stays stable across processes and in the event log. It is exported so an API surface can classify a response it read itself with the same rule the transport uses.

Types

type Doer

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

Doer performs a single HTTP round trip. *http.Client satisfies it. The transport is defined against this interface, not a concrete client, so a test or a fault-injecting adapter can stand in for the network.

type Option

type Option func(*Transport)

Option configures a Transport.

func WithBackoff

func WithBackoff(base, ceiling time.Duration) Option

WithBackoff sets the base and ceiling for exponential backoff between retries. A base of 0 disables waiting entirely (retries fire immediately), which keeps deterministic tests simple. Negative values are treated as 0.

func WithClock

func WithClock(c clock.Timing) Option

WithClock sets the clock backing backoff and rate-limit waits. The default is clock.System; tests pass a clock.Manual for deterministic, sleepless delays.

func WithDoer

func WithDoer(d Doer) Option

WithDoer sets the underlying HTTP doer. The default dials through netguard (anti-SSRF: public addresses only, private/loopback/metadata denied). Tests and chaos adapters use this to substitute the network, and a caller that must reach a specific private host injects its own netguard.Client with a matching policy.

func WithJitterEntropy

func WithJitterEntropy(src io.Reader) Option

WithJitterEntropy enables full jitter on each backoff wait, drawing from src so retries from many goroutines do not align into a thundering herd. Entropy is injected (the project reserves direct rand sources to package ids): pass crypto/rand from the composition root in production, or a deterministic reader for reproducible replay. Without this option backoff stays the exact deterministic schedule, so a run replays bit-for-bit by default.

func WithMaxAttempts

func WithMaxAttempts(n int) Option

WithMaxAttempts caps the total number of tries, including the first. A value below 1 is clamped to 1 (no retry).

func WithRateLimit

func WithRateLimit(perSecond float64) Option

WithRateLimit enforces a minimum interval between requests to the same host, expressed as a maximum requests-per-second. A value <= 0 disables rate limiting.

type Transport

type Transport struct {
	// contains filtered or unexported fields
}

Transport executes HTTP requests with bounded retries, exponential backoff, and per-host rate limiting. It is safe for concurrent use.

func New

func New(opts ...Option) *Transport

New builds a Transport. Defaults: a netguard client (public addresses only, anti-SSRF) so a freshly-built transport cannot be steered into dialing a private, loopback, or cloud-metadata address; clock.System; three attempts; 200ms base backoff capped at 10s; no jitter (deterministic schedule); no rate limit. Enable jitter with WithJitterEntropy.

func (*Transport) Backoff

func (t *Transport) Backoff(attempt int) time.Duration

Backoff returns the deterministic, un-jittered wait before the given 1-based attempt: base * 2^(attempt-2) for attempt >= 2, capped at the ceiling, and zero for the first attempt (which has no preceding wait). It is exported so the schedule can be asserted directly in tests.

func (*Transport) Do

func (t *Transport) Do(ctx context.Context, req *http.Request) (*http.Response, error)

Do executes req with rate limiting, retries, and backoff, and returns the final response. The returned error is always a classified *fault.Error or nil:

  • A 2xx/3xx response returns the response and a nil error.
  • A non-retryable status (most 4xx) returns the response and a Terminal fault, so the caller may still read the body to surface the API's error detail.
  • A retryable failure (network error, 408, 429, 5xx) is retried up to the attempt ceiling; if it never succeeds the last response (if any) and a Transient fault are returned.
  • A cancelled or expired context returns a Cancelled fault and is never retried.

Retries re-send the body via req.GetBody; a retryable request without GetBody is sent once and its failure returned, since the transport cannot safely replay it.

Jump to

Keyboard shortcuts

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