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
- Variables
- func DecodeJSON[T any](ctx context.Context, response Response, descriptor contract.JSON[T]) (T, error)
- func Module(name foundation.ProviderID, key foundation.Key[*Client], config Config, ...) foundation.Module
- type Body
- type Client
- func (c *Client) Close(ctx context.Context) error
- func (c *Client) Do(ctx context.Context, request Request) (Response, error)
- func (c *Client) DoAll(ctx context.Context, requests []Request, concurrency int) ([]Outcome, error)
- func (c *Client) Done() <-chan struct{}
- func (c *Client) Download(ctx context.Context, request Request, destination io.Writer, maximum int64) (DownloadResult, error)
- func (*Client) Format(state fmt.State, _ rune)
- func (c *Client) Get(path string) Request
- func (c *Client) Name() Name
- func (c *Client) Post(path string) Request
- func (c *Client) Request(method, path string) Request
- func (c *Client) RestrictsDestinations() bool
- func (c *Client) Snapshot() Snapshot
- func (c *Client) Stream(ctx context.Context, request Request, ...) error
- type Clients
- type Config
- type DestinationMode
- type DestinationPolicy
- type DownloadResult
- type Error
- type ErrorKind
- type Name
- type NamedClient
- type Outcome
- type Part
- type Request
- func (r Request) BasicAuth(user string, password secret.String) Request
- func (r Request) Bearer(token secret.String) Request
- func (r Request) Form(values url.Values) Request
- func (Request) Format(state fmt.State, _ rune)
- func (r Request) Header(name, value string) Request
- func (r Request) Method() string
- func (r Request) Multipart(parts ...Part) Request
- func (r Request) QueryPair(name, value string) Request
- func (r Request) URL() string
- func (r Request) Validate() error
- func (r Request) WithBody(body Body) Request
- func (r Request) WithRetry(policy RetryPolicy) Request
- type Response
- type RetryMode
- type RetryPolicy
- type Scheme
- type Snapshot
- type StreamResponse
- type TLSConfig
Constants ¶
const MaxBatchRequests = 4096
MaxBatchRequests bounds one DoAll call.
const MaxBodyBytes int64 = 64 << 20
const MaxDownloadBytes int64 = 1 << 40
MaxDownloadBytes bounds one Download independently of Config.ResponseBytes.
Variables ¶
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 ¶
Bytes returns a replayable body backed by a copy of data, recording an invalid request if data exceeds MaxBodyBytes.
func ReplayableBody ¶
ReplayableBody asserts each opener returns the same complete content in an independent reader. Retry safety still needs a matching operation policy.
func StreamBody ¶
StreamBody declares a non-replayable reader source with the given length, or -1 for unknown length. Each opened reader transfers ownership to the client.
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)
New validates and snapshots config and creates a client. A supplied transport is borrowed and cannot be combined with restricted destinations or custom TLS settings.
func (*Client) Close ¶
Close stops admission and waits for active operations before closing owned idle connections. A canceled wait does not stop active operations.
func (*Client) Do ¶
Do executes request and buffers the bounded response body. HTTP error statuses require a separate EnsureSuccess check.
func (*Client) DoAll ¶
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{}
Done returns the channel that closes when the client's operation group finishes shutting down.
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) Request ¶
Request declares a request using the client's base URL, copied default headers and retry policy. Invalid methods or URLs are retained as validation errors.
func (*Client) RestrictsDestinations ¶
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) 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)
NewClients builds a named registry whose default aliases the selected client.
func (*Clients) Client ¶
Client looks up the explicit name and returns an error if it is unavailable.
func (*Clients) Default ¶
Default returns the selected client or an error if no default is available.
func (*Clients) DefaultName ¶
DefaultName returns the selected name, or an empty name for a nil registry.
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
}
Config declares one named client's transport, resource bounds and request defaults.
func DefaultConfig ¶
DefaultConfig returns the named client's default timeouts, resource bounds and retry policy.
func (Config) MarshalJSON ¶
MarshalJSON encodes a configuration label without exposing settings.
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
Validate checks the destination mode and bounded scheme, port, host and network allowlists.
type DownloadResult ¶
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) Error ¶
Error returns the failure kind, client name, attempt and status without formatting its cause.
func (*Error) Is ¶
Is matches invalid, internal and overloaded framework faults according to the failure kind.
type ErrorKind ¶
type ErrorKind string
ErrorKind identifies an outbound HTTP failure category.
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 NamedClient ¶
NamedClient borrows one existing instance. Selection never creates another owner.
type Part ¶
type Part struct {
// contains filtered or unexported fields
}
Part is one multipart/form-data part: a field value or a file.
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 ¶
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) Header ¶
Header returns a request with a copied, validated header set containing the replacement value.
func (Request) Multipart ¶
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 ¶
QueryPair returns a request with an additional encoded query value, retaining validation errors on the result.
func (Request) Validate ¶
Validate returns retained declaration errors and checks the destination policy. The zero request is invalid.
func (Request) WithBody ¶
WithBody returns a request with body, retaining an error if the body is invalid or exceeds the client's request limit.
func (Request) WithRetry ¶
func (r Request) WithRetry(policy RetryPolicy) Request
WithRetry returns a request with the validated retry policy.
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) EnsureSuccess ¶
EnsureSuccess returns a StatusFailed error unless the status is in the 2xx range.
func (Response) Format ¶
Format writes the response status and body length without exposing content.
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
DefaultRetryPolicy allows up to three safe-read attempts with jittered exponential backoff.
func (RetryPolicy) Validate ¶
func (p RetryPolicy) Validate() error
Validate checks the retry mode, attempt and backoff bounds, and retryable status codes.
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
}
StreamResponse exposes the response body within the client's streaming callback lifetime.
func (*StreamResponse) Attempts ¶
func (r *StreamResponse) Attempts() int
Attempts returns the final attempt number, or zero for a nil response.
func (*StreamResponse) EnsureSuccess ¶
func (r *StreamResponse) EnsureSuccess() error
EnsureSuccess checks for a 2xx status; a nil response is invalid.
func (*StreamResponse) Format ¶
func (r *StreamResponse) Format(state fmt.State, _ rune)
Format writes the response status without exposing content.
func (*StreamResponse) Headers ¶
func (r *StreamResponse) Headers() http.Header
Headers returns a copy of the headers, or nil for a nil response.
func (*StreamResponse) Read ¶
func (r *StreamResponse) Read(p []byte) (int, error)
Read reads from the owned response body; a nil response is invalid.
func (*StreamResponse) Status ¶
func (r *StreamResponse) Status() int
Status returns the HTTP status code, or zero for a nil response.
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.