httpclient

package
v0.16.0 Latest Latest
Warning

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

Go to latest
Published: Sep 17, 2026 License: Apache-2.0 Imports: 11 Imported by: 0

Documentation

Overview

Package httpclient provides a shared HTTP client for all external API calls. The client is safe for concurrent use and reuses connections. Call Init once during startup to set the timeout; the default is 10s.

Index

Constants

View Source
const (
	// DefaultRetryAfter is the wait before the one retry of a 429 that
	// named no delay at all.
	DefaultRetryAfter = 2 * time.Second

	// MaxRetryAfter bounds the wait the CLI will sit through before its
	// one retry. The per-minute limiters name at most about a minute; a
	// refusal asking for longer (a daily quota, say) is surfaced at once,
	// with the wait it named, rather than parking the command.
	MaxRetryAfter = 60 * time.Second

	// RetryJitter is the most random delay added on top of the wait a
	// refusal named. The server's windows are aligned to the clock
	// minute, so every client refused in a given minute is told the same
	// second and, retrying on that exact second, would arrive at the
	// boundary together with all the others (truestamp-v2
	// kb/api/json-api.md: "add a little random jitter to Retry-After
	// rather than retrying on the exact second"). A uniform draw from
	// [0, RetryJitter) spreads the herd over a couple of seconds without
	// making a short wait meaningfully longer. It is applied after
	// BoundRetry has decided, so it never turns a retry into a refusal.
	RetryJitter = 2 * time.Second
)
View Source
const DefaultMaxDownloadSize = 200 << 20

DefaultMaxDownloadSize caps a single DownloadCtx response at 200 MB, comfortably larger than any truestamp release tarball today, small enough to prevent a runaway redirect from filling the disk.

View Source
const MaxResponseSize = 1 << 20

MaxResponseSize limits HTTP response bodies to 1 MB to prevent OOM.

Variables

View Source
var ErrRedirectDowngrade = errors.New("refusing redirect that downgrades https to http")

ErrRedirectDowngrade is returned (wrapped in a *url.Error by net/http) when a server tries to redirect an https request to a plaintext http URL. Following such a redirect would strip TLS mid-chain, which for the keyring fetch in particular is a total compromise: an attacker who can substitute the keyring can substitute signing keys, and every downstream signature then validates against their key. See VerifyKeyring's threat note in internal/external/keyring.go.

Functions

func BoundRetry added in v0.16.0

func BoundRetry(delay time.Duration, named bool) (time.Duration, bool)

BoundRetry decides whether a 429 is retried, and after how long. named is false when the refusal named no delay, in which case DefaultRetryAfter applies. The result is false when the wait is over MaxRetryAfter; the delay is still returned so a caller can show it.

func Discard added in v0.16.0

func Discard(resp *http.Response)

Discard drains (up to 64 KiB) and closes a response body that is being thrown away, so the connection can be reused for the retry.

func Do

func Do(req *http.Request) (*http.Response, error)

Do executes an HTTP request using the shared client. The request's existing context.Context (if any) is respected; callers that want cancellation should attach one via http.Request.WithContext before calling. If SetUserAgent has been called and the request has no User-Agent header, the configured value is applied.

func DownloadBytesCtx added in v0.3.1

func DownloadBytesCtx(ctx context.Context, url string, maxBytes int64) ([]byte, error)

DownloadBytesCtx is like DownloadCtx but returns the body in memory. Intended for small artifacts (checksums.txt, signature bundles) that exceed MaxResponseSize occasionally but are still known to be small (<1 MB). Pass 0 for maxBytes to default to 1 MB.

func DownloadCtx added in v0.3.1

func DownloadCtx(ctx context.Context, url, destPath string, maxBytes int64) (int64, error)

DownloadCtx streams the body of a GET request to destPath. The destination is created (or truncated) with 0644 permissions. The response body is read through an io.LimitReader capped at maxBytes; pass 0 (or a negative value) to use DefaultMaxDownloadSize.

Unlike GetJSONCtx, this function is safe for multi-MB responses and never buffers the full body in memory.

Returns the number of bytes written on success.

func GetJSON

func GetJSON(url string) ([]byte, error)

GetJSON performs a GET request with context.Background and returns the response body. Prefer GetJSONCtx when a cancellable context is available (e.g. from Cobra's cmd.Context()).

func GetJSONCtx added in v0.3.0

func GetJSONCtx(ctx context.Context, url string) ([]byte, error)

GetJSONCtx performs a context-aware GET request and returns the response body. Errors are typed: *TransportError before a response exists, *StatusError for a non-2xx status, *TruncatedError for a body over MaxResponseSize. Their Error() strings are unchanged from the untyped versions they replaced.

func Init

func Init(timeout time.Duration)

Init creates a new HTTP client with the given timeout. Must be called once during startup before any external calls. The redirect policy in [checkRedirect] is reinstalled here; building a bare http.Client instead would silently drop it.

func IsTransport added in v0.12.0

func IsTransport(err error) bool

IsTransport reports whether err is (or wraps) a *TransportError.

func IsTruncated added in v0.12.0

func IsTruncated(err error) bool

IsTruncated reports whether err is (or wraps) a *TruncatedError.

func Jitter added in v0.16.0

func Jitter(delay time.Duration) time.Duration

Jitter returns delay plus a random amount in [0, RetryJitter). Both retry sites, internal/jsonapi.Send and the transport below, apply it to the wait they are about to sleep.

func NewAttemptTransport added in v0.16.0

func NewAttemptTransport(timeout time.Duration) http.RoundTripper

NewAttemptTransport returns a copy of the default transport that gives up on a single attempt whose response headers have not arrived within timeout. It is the per-attempt bound to pair with NewRetryAfterTransport.

func NewRetryAfterTransport added in v0.16.0

func NewRetryAfterTransport(base http.RoundTripper) http.RoundTripper

NewRetryAfterTransport wraps base (http.DefaultTransport when nil) with the retry-once-on-429 behaviour above. internal/auth installs it on the clients that call the OAuth token and revocation endpoints. Give the client no http.Client.Timeout: that bound spans the whole call, wait included, and would cancel the very wait the refusal asked for; bound each attempt on the base transport instead (see NewAttemptTransport).

func ParseRetryAfter added in v0.16.0

func ParseRetryAfter(value string, now time.Time) (delay time.Duration, ok bool)

ParseRetryAfter parses a Retry-After header value (RFC 9110 §10.2.3): a non-negative integer number of seconds, or an HTTP-date, in which case the delay is measured from now. ok is false for an absent or malformed value. A date already in the past parses as a zero delay.

func Rewind added in v0.16.0

func Rewind(req *http.Request) (*http.Request, bool)

Rewind returns a copy of req that can be sent again, or false when the body was streamed and cannot be reproduced. Requests built by http.NewRequest from a bytes or strings reader carry GetBody and rewind; an arbitrary io.Reader does not.

func SetTransport added in v0.9.0

func SetTransport(rt http.RoundTripper)

SetTransport installs rt as the round-tripper for the shared client. The CLI uses it to layer the auth package's reactive 401 → refresh → retry-once behavior on top of the default transport. Call after Init, which replaces the client.

func SetUserAgent added in v0.7.0

func SetUserAgent(version string)

SetUserAgent configures the User-Agent header stamped onto every outbound request through Do. Typical value: "truestamp-cli/<version> (<os>/<arch>)". Pass an empty string to disable the override (requests keep whatever UA the caller set, or Go's default).

func Status added in v0.12.0

func Status(err error) int

Status returns the HTTP status carried by err, or 0 when err is not (and does not wrap) a *StatusError.

func Truncate

func Truncate(s string, maxLen int) string

Truncate shortens a string to maxLen characters, appending "..." if truncated.

func Wait added in v0.16.0

func Wait(ctx context.Context, d time.Duration) error

Wait blocks for d, or until ctx is done, whichever comes first, and returns ctx.Err() in the second case.

Types

type StatusError added in v0.12.0

type StatusError struct {
	StatusCode int
	URL        string
	Body       string // truncated to 80 bytes
	HTML       bool   // the body looked like an HTML error page
}

StatusError is returned for any non-2xx response. It preserves the status code so a caller can tell a definitive answer (404) from an availability problem (429, 5xx).

func (*StatusError) Error added in v0.12.0

func (e *StatusError) Error() string

type TransportError added in v0.12.0

type TransportError struct {
	URL string
	Err error
}

TransportError wraps a failure that happened before any HTTP response was received: request construction, DNS resolution, dial, TLS handshake, client timeout, context cancellation, or a body that could not be read. A verifier must report these as `skip` rather than `fail` (whitepaper Appendix E.17/E.18/E.21/E.22), so the distinction has to survive to the call site instead of collapsing into one opaque error.

func (*TransportError) Error added in v0.12.0

func (e *TransportError) Error() string

func (*TransportError) Unwrap added in v0.12.0

func (e *TransportError) Unwrap() error

type TruncatedError added in v0.12.0

type TruncatedError struct {
	URL   string
	Limit int
}

TruncatedError is returned when a 2xx body exceeded MaxResponseSize. Without it the cut-off bytes reach the caller's JSON decoder and surface as a syntax error, which reads as "the server sent junk" when the real cause is our own cap.

func (*TruncatedError) Error added in v0.12.0

func (e *TruncatedError) Error() string

Jump to

Keyboard shortcuts

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