Documentation
¶
Overview ¶
Package httpkit provides shared HTTP client construction and utilities for all outbound HTTP calls in Thane. It enforces consistent timeouts, connection management, and good-citizen defaults across all packages.
Issue #53: Go's net.Dial intermittently fails on macOS with "no route to host" for LAN targets. The shared transport here sets explicit dial and TLS timeouts, limits idle connections, and provides a foundation for future diagnostics (GODEBUG=netdns=2, custom DialContext hooks, etc).
Index ¶
- Constants
- func DrainAndClose(rc io.ReadCloser, limit int64)
- func NewClient(opts ...ClientOption) *http.Client
- func NewTransport() *http.Transport
- func ReadErrorBody(rc io.ReadCloser, limit int64) string
- type AgentSurface
- type ClientOption
- func WithDisableKeepAlives() ClientOption
- func WithLogger(l *slog.Logger) ClientOption
- func WithRetry(count int, delay time.Duration) ClientOption
- func WithRetryOnStatus(statuses ...int) ClientOption
- func WithTLSInsecureSkipVerify() ClientOption
- func WithTimeout(d time.Duration) ClientOption
- func WithTransport(t *http.Transport) ClientOption
- func WithTruthfulUserAgent(surface AgentSurface) ClientOption
- func WithUserAgent(ua string) ClientOption
- func WithoutUserAgent() ClientOption
Constants ¶
const ( // DefaultDialTimeout is the maximum time to establish a TCP connection. DefaultDialTimeout = 10 * time.Second // DefaultKeepAlive is the interval between TCP keep-alive probes. DefaultKeepAlive = 30 * time.Second // DefaultTLSHandshakeTimeout is the maximum time for the TLS handshake. DefaultTLSHandshakeTimeout = 10 * time.Second // DefaultResponseHeader is the maximum time to wait for response headers // after a request is fully written. DefaultResponseHeader = 15 * time.Second // DefaultIdleConnTimeout is how long idle connections stay in the pool. DefaultIdleConnTimeout = 90 * time.Second // DefaultMaxIdleConns is the total number of idle connections across all hosts. DefaultMaxIdleConns = 20 // DefaultMaxIdleConnsPerHost is the per-host idle connection limit. DefaultMaxIdleConnsPerHost = 5 )
Default timeouts and connection pool limits for the shared transport.
const ( AgentSurfaceGeneral = buildinfo.AgentSurfaceGeneral AgentSurfaceForge = buildinfo.AgentSurfaceForge )
Variables ¶
This section is empty.
Functions ¶
func DrainAndClose ¶
func DrainAndClose(rc io.ReadCloser, limit int64)
DrainAndClose reads up to limit bytes from rc and closes it. Use to ensure HTTP connections are returned to the pool.
func NewClient ¶
func NewClient(opts ...ClientOption) *http.Client
NewClient builds an *http.Client with the shared transport and good-citizen defaults (timeouts, User-Agent, connection limits).
func NewTransport ¶
NewTransport creates an http.Transport with sensible defaults. This is the foundation for all outbound connections.
func ReadErrorBody ¶
func ReadErrorBody(rc io.ReadCloser, limit int64) string
ReadErrorBody reads up to limit bytes from rc for error messages, then drains and closes the remainder to allow connection reuse. Returns an empty string if rc is nil.
Types ¶
type AgentSurface ¶
type AgentSurface = buildinfo.AgentSurface
AgentSurface re-exports the standard buildinfo surface vocabulary so callers can opt into precise truthful disclosure without inventing one-off User-Agent strings.
type ClientOption ¶
type ClientOption func(*clientConfig)
ClientOption configures a Client built by NewClient.
func WithDisableKeepAlives ¶
func WithDisableKeepAlives() ClientOption
WithDisableKeepAlives disables HTTP keep-alives on the transport.
func WithLogger ¶
func WithLogger(l *slog.Logger) ClientOption
WithLogger sets a logger for retry diagnostics.
func WithRetry ¶
func WithRetry(count int, delay time.Duration) ClientOption
WithRetry enables automatic retry on transient connection errors (e.g., EHOSTUNREACH, ENETUNREACH, connection refused). Retries are only attempted when the request body can be rewound, but this does not guarantee that the server has not already received the prior attempt. In practice, the retryable error set (dial/connect failures) occurs before any bytes reach the server. Designed to handle macOS ARP table race conditions (issue #53).
func WithRetryOnStatus ¶
func WithRetryOnStatus(statuses ...int) ClientOption
WithRetryOnStatus extends WithRetry with HTTP-level retry: if the server returns one of the listed statuses, the transport drains and closes the response body and retries the request. Callers should still set WithRetry to configure the backoff count/delay; this option on its own has no effect.
Typical use: cloud API clients opt in with 429 and 5xx to ride out transient rate-limit / upstream hiccups without bubbling them up to the agent loop. Local service clients (Ollama, LMStudio) don't need this and should omit it.
The retry honors the Retry-After response header when present; the configured backoff delay is used only when the server doesn't supply one.
func WithTLSInsecureSkipVerify ¶
func WithTLSInsecureSkipVerify() ClientOption
WithTLSInsecureSkipVerify skips TLS certificate verification. Use only for local/development targets.
func WithTimeout ¶
func WithTimeout(d time.Duration) ClientOption
WithTimeout sets the overall request timeout on the http.Client. A zero value disables the timeout (useful for streaming responses).
func WithTransport ¶
func WithTransport(t *http.Transport) ClientOption
WithTransport overrides the default shared transport. Use sparingly — the shared transport handles connection pooling.
func WithTruthfulUserAgent ¶
func WithTruthfulUserAgent(surface AgentSurface) ClientOption
WithTruthfulUserAgent uses Thane's canonical truthful User-Agent generation for the given standard surface.
func WithUserAgent ¶
func WithUserAgent(ua string) ClientOption
WithUserAgent overrides the default User-Agent header.
func WithoutUserAgent ¶
func WithoutUserAgent() ClientOption
WithoutUserAgent disables the automatic User-Agent roundtripper.