httpclient

package
v0.3.2 Latest Latest
Warning

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

Go to latest
Published: Oct 31, 2026 License: MIT Imports: 37 Imported by: 0

Documentation

Overview

Package httpclient provides named, bounded outbound HTTP clients over reusable net/http transports. Clients own operation lifetimes; custom transports are borrowed.

Index

Constants

View Source
const MaxBatchRequests = 4096

MaxBatchRequests bounds one DoAll call.

View Source
const MaxBodyBytes int64 = 64 << 20
View Source
const MaxDownloadBytes int64 = 1 << 40

MaxDownloadBytes bounds one Download independently of Config.ResponseBytes.

Variables

View Source
var DestinationDenied = safeerror.Sentinel(fault.Invalid, "outbound destination is not permitted")

Functions

func DecodeJSON

func DecodeJSON[T any](ctx context.Context, response Response, descriptor contract.JSON[T]) (T, error)

DecodeJSON validates the complete received DTO against its existing generated contract. Status handling is explicit through Response.EnsureSuccess.

func Module

func Module(name foundation.ProviderID, key foundation.Key[*Client], config Config, requires []foundation.ProviderID, transport func(foundation.Resolver) (http.RoundTripper, error)) foundation.Module

Module binds one named upstream to its typed service key. requires identifies providers owning a borrowed transport so all operations drain before teardown.

Types

type Body

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

Body is an immutable body source. Zero means no body. Stream openers transfer ownership of a new reader per call and must support concurrent Read/Close.

func Bytes

func Bytes(data []byte) Body

func ReplayableBody

func ReplayableBody(length int64, open func(context.Context) (io.ReadCloser, error)) Body

ReplayableBody asserts each opener returns the same complete content in an independent reader. Retry safety still needs a matching operation policy.

func StreamBody

func StreamBody(length int64, open func(context.Context) (io.ReadCloser, error)) Body

func (Body) Format

func (Body) Format(state fmt.State, _ rune)

type Client

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

Client owns native connection pooling and bounded operation lifetimes. A supplied RoundTripper is borrowed and must honor request cancellation, close bodies and avoid implementing an additional retry/redirect policy.

func New

func New(config Config, transport http.RoundTripper) (*Client, error)

func (*Client) Close

func (c *Client) Close(ctx context.Context) error

func (*Client) Do

func (c *Client) Do(ctx context.Context, request Request) (Response, error)

func (*Client) DoAll

func (c *Client) DoAll(ctx context.Context, requests []Request, concurrency int) ([]Outcome, error)

DoAll runs Do for each request with at most concurrency in flight and returns outcomes in request order. Each request still uses the client's own queued admission, deadlines and retry policy; one failure does not cancel the others. concurrency must not exceed Config.Concurrency.

func (*Client) Done

func (c *Client) Done() <-chan struct{}

func (*Client) Download

func (c *Client) Download(ctx context.Context, request Request, destination io.Writer, maximum int64) (DownloadResult, error)

Download streams one successful (2xx) response body into destination under its own byte limit, for files larger than Config.ResponseBytes. Other statuses fail as StatusFailed without writing. The destination is borrowed and receives bytes as they arrive: a failed or interrupted download can leave a partial write, so write to a temporary file and publish it only on success. Retries follow the request policy until the body starts.

func (*Client) Format

func (*Client) Format(state fmt.State, _ rune)

func (*Client) Get

func (c *Client) Get(path string) Request

func (*Client) Name

func (c *Client) Name() Name

func (*Client) Post

func (c *Client) Post(path string) Request

func (*Client) Request

func (c *Client) Request(method, path string) Request

func (*Client) RestrictsDestinations

func (c *Client) RestrictsDestinations() bool

RestrictsDestinations reports whether the client enforces a restricted destination policy on every connection. Callers that fetch untrusted URLs (for example attachment imports) require it.

func (*Client) Snapshot

func (c *Client) Snapshot() Snapshot

func (*Client) Stream

func (c *Client) Stream(ctx context.Context, request Request, consume func(context.Context, *StreamResponse) error) error

Stream lends one response body to consume. It always closes the stream, and retains admission until consume and actual body closure finish. Retained readers reject use after return. Once consume starts, the request is never retried.

type Clients

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

Clients is immutable; default and named selection alias the same instance.

func NewClients

func NewClients(selected Name, entries ...NamedClient) (*Clients, error)

func (*Clients) Client

func (r *Clients) Client(name Name) (*Client, error)

func (*Clients) Default

func (r *Clients) Default() (*Client, error)

func (*Clients) DefaultName

func (r *Clients) DefaultName() Name

func (*Clients) Names

func (r *Clients) Names() []Name

type Config

type Config struct {
	// Destination optionally enforces direct, address-checked outbound networking.
	Destination    DestinationPolicy
	Name           Name
	BaseURL        string
	Headers        http.Header `config:",json,secret"`
	ConnectTimeout time.Duration
	AttemptTimeout time.Duration
	Timeout        time.Duration
	Concurrency    int
	RequestBytes   int64
	ResponseBytes  int64
	HeaderBytes    int
	Retry          RetryPolicy
	// PropagateTrace opts this named destination into bounded W3C correlation.
	// When enabled, framework context replaces manual trace headers on the
	// owned request copy. Disabled clients do not add propagation headers.
	PropagateTrace bool
	// TLS customizes certificate verification and client certificates for the
	// owned transport. A supplied custom transport cannot combine with it.
	TLS TLSConfig
}

func DefaultConfig

func DefaultConfig(name Name) Config

func (Config) Format

func (Config) Format(state fmt.State, _ rune)

func (Config) MarshalJSON

func (Config) MarshalJSON() ([]byte, error)

func (Config) Validate

func (c Config) Validate() error

type DestinationMode

type DestinationMode string

DestinationMode selects ordinary networking or an enforced direct-connect policy.

const (
	UnrestrictedDestinations DestinationMode = ""
	RestrictedDestinations   DestinationMode = "restricted"
)

type DestinationPolicy

type DestinationPolicy struct {
	Mode     DestinationMode
	Schemes  []Scheme       `config:",json"`
	Ports    []uint16       `config:",json"`
	Hosts    []string       `config:",json"`
	Networks []netip.Prefix `config:",json"`
}

DestinationPolicy constrains the URL and actual connected address. Empty Hosts allows any DNS name; empty Networks allows public Internet addresses only. Nonempty Networks is an exclusive allowlist, including explicitly allowed private networks. Restricted clients bypass environment proxies and reject custom transports, because either can resolve/connect outside this policy.

func PublicDestinations

func PublicDestinations() DestinationPolicy

PublicDestinations permits HTTPS on port 443 to public Internet addresses. Restrict Hosts for known services; explicitly add other schemes/ports if needed.

func (DestinationPolicy) Validate

func (p DestinationPolicy) Validate() error

type DownloadResult

type DownloadResult struct {
	Status   int
	Headers  http.Header
	Bytes    int64
	Attempts int
}

DownloadResult describes a completed download.

type Error

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

Error exposes stable operation metadata, never URL, header, query, body or underlying transport text. The cause is available only through explicit inspection.

func (*Error) Attempt

func (e *Error) Attempt() int

func (*Error) Error

func (e *Error) Error() string

func (*Error) Format

func (e *Error) Format(state fmt.State, _ rune)

func (*Error) Is

func (e *Error) Is(target error) bool

func (*Error) Kind

func (e *Error) Kind() ErrorKind

func (*Error) Method

func (e *Error) Method() string

func (*Error) Name

func (e *Error) Name() Name

func (*Error) Status

func (e *Error) Status() int

func (*Error) Unwrap

func (e *Error) Unwrap() error

type ErrorKind

type ErrorKind string
const (
	InvalidRequest  ErrorKind = "invalid_request"
	TransportFailed ErrorKind = "transport_failed"
	BodyFailed      ErrorKind = "body_failed"
	StatusFailed    ErrorKind = "status_failed"
	DecodeFailed    ErrorKind = "decode_failed"
	CallbackFailed  ErrorKind = "callback_failed"
	// Overloaded reports that the client's queued operation admission was
	// exhausted; no request was sent. It matches fault.Overloaded.
	Overloaded ErrorKind = "overloaded"
)

type Name

type Name string

func (Name) Validate

func (n Name) Validate() error

type NamedClient

type NamedClient struct {
	Name  Name
	Value *Client
}

NamedClient borrows one existing instance. Selection never creates another owner.

type Outcome

type Outcome struct {
	Response Response
	Err      error
}

Outcome is one DoAll result in request order.

type Part

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

Part is one multipart/form-data part: a field value or a file.

func Field

func Field(name, value string) Part

Field is a multipart text field.

func File

func File(name, filename, contentType string, data []byte) Part

File is a multipart file part. The filename is presentation metadata only.

func (Part) Format

func (Part) Format(state fmt.State, _ rune)

type Request

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

Request is an immutable client-bound request. Builders retain errors until Do or Stream, so invalid requests never invoke body factories or transports.

func JSON

func JSON[T any](ctx context.Context, request Request, descriptor contract.JSON[T], payload T) (Request, error)

JSON reuses the request DTO's generated codec and supplies Content-Type. It retains the concrete payload type and encodes once before any network attempt.

func (Request) BasicAuth

func (r Request) BasicAuth(user string, password secret.String) Request

BasicAuth sets RFC 7617 Basic credentials. The user ID must not contain a colon or control characters; the password is revealed only into the header.

func (Request) Bearer

func (r Request) Bearer(token secret.String) Request

func (Request) Form

func (r Request) Form(values url.Values) Request

Form encodes values as an application/x-www-form-urlencoded replayable body.

func (Request) Format

func (Request) Format(state fmt.State, _ rune)

func (Request) Header

func (r Request) Header(name, value string) Request

func (Request) Method

func (r Request) Method() string

func (Request) Multipart

func (r Request) Multipart(parts ...Part) Request

Multipart encodes parts once as a replayable multipart/form-data body within the client's request byte limit and sets its Content-Type with the boundary.

func (Request) QueryPair

func (r Request) QueryPair(name, value string) Request

func (Request) URL

func (r Request) URL() string

func (Request) Validate

func (r Request) Validate() error

func (Request) WithBody

func (r Request) WithBody(body Body) Request

func (Request) WithRetry

func (r Request) WithRetry(policy RetryPolicy) Request

type Response

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

Response owns a complete, bounded body and copied response headers. Accessors exposing bytes/headers are explicit and return independent snapshots.

func (Response) Attempts

func (r Response) Attempts() int

func (Response) Bytes

func (r Response) Bytes() []byte

func (Response) EnsureSuccess

func (r Response) EnsureSuccess() error

func (Response) Format

func (r Response) Format(state fmt.State, _ rune)

func (Response) Headers

func (r Response) Headers() http.Header

func (Response) Status

func (r Response) Status() int

func (Response) Text

func (r Response) Text() (string, error)

type RetryMode

type RetryMode string
const (
	SafeReads           RetryMode = "safe_reads"
	IdempotentOperation RetryMode = "idempotent_operation"
)

type RetryPolicy

type RetryPolicy struct {
	Mode                       RetryMode
	Attempts                   int
	InitialBackoff, MaxBackoff time.Duration
	Jitter                     bool
	Statuses                   []int `config:",json"`
}

RetryPolicy always counts total attempts, including the first. Mutation retries require an explicit IdempotentOperation policy and a replayable request body. Jitter draws each backoff uniformly from zero to its exponential cap ("full jitter") so synchronized clients spread out. Statuses replaces the default retryable statuses (408, 429, 500, 502, 503, 504) when nonempty. A Retry-After response header on a retryable status sets the wait; a value beyond MaxBackoff ends retrying and returns that response.

func DefaultRetryPolicy

func DefaultRetryPolicy() RetryPolicy

func NoRetries

func NoRetries() RetryPolicy

func (RetryPolicy) Validate

func (p RetryPolicy) Validate() error

type Scheme

type Scheme string
const (
	HTTPS Scheme = "https"
	HTTP  Scheme = "http"
)

type Snapshot

type Snapshot struct{ Requests, Attempts, Failures uint64 }

Snapshot contains only bounded operation counts; no request values are retained.

type StreamResponse

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

func (*StreamResponse) Attempts

func (r *StreamResponse) Attempts() int

func (*StreamResponse) EnsureSuccess

func (r *StreamResponse) EnsureSuccess() error

func (*StreamResponse) Format

func (r *StreamResponse) Format(state fmt.State, _ rune)

func (*StreamResponse) Headers

func (r *StreamResponse) Headers() http.Header

func (*StreamResponse) Read

func (r *StreamResponse) Read(p []byte) (int, error)

func (*StreamResponse) Status

func (r *StreamResponse) Status() int

type TLSConfig

type TLSConfig struct {
	CertificateAuthorities string
	AppendSystemRoots      bool
	Certificate            secret.String
	PrivateKey             secret.String
	ServerName             string
}

TLSConfig customizes the owned transport's TLS. CertificateAuthorities (PEM) replaces the system roots unless AppendSystemRoots is set. Certificate and PrivateKey (PEM) present a client certificate and must be set together. ServerName overrides verification of the URL host name.

func (TLSConfig) IsZero

func (t TLSConfig) IsZero() bool

Jump to

Keyboard shortcuts

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