Documentation
¶
Overview ¶
Package gocurl is a curl-ergonomic HTTP client for Go, built on net/http.
It lets you paste a curl command from any API's documentation straight into Go code (or run the identical command with the gocurl CLI), removing the translation tax of rewriting curl snippets as net/http requests.
Quick start ¶
The primary entry point accepts a curl command as a single string or as separate arguments and returns a standard *http.Response:
resp, err := gocurl.Curl(ctx,
"-H", "Accept: application/vnd.github+json",
"https://api.github.com/repos/golang/go")
if err != nil {
return err
}
defer resp.Body.Close()
Convenience helpers read the body for you:
body, resp, err := gocurl.CurlString(ctx, "https://api.github.com/repos/golang/go") _, err = gocurl.CurlJSON(ctx, &v, "https://api.github.com/repos/golang/go") n, resp, err := gocurl.CurlDownload(ctx, "go.tar.gz", "https://go.dev/dl/...")
Variables ¶
By default, environment variables ($VAR and ${VAR}) are expanded. For explicit, testable control that does not read the process environment, pass a Variables map to the WithVars entry points:
vars := gocurl.Variables{"token": myToken}
resp, err := gocurl.CurlWithVars(ctx, vars,
"-H", "Authorization: Bearer ${token}",
"https://api.github.com/user")
Scope ¶
gocurl targets curl's HTTP/HTTPS behavior for the flags that appear in real API documentation. It is a convenience layer on top of net/http, not a replacement for it, and it does not implement curl's non-HTTP protocols.
Index ¶
- Constants
- Variables
- func BodyReadError(url string, err error) error
- func BytesBody(b []byte) options.BodySource
- func CanceledError(url string, err error) error
- func ConnectError(url string, err error) error
- func Curl(ctx context.Context, command ...string) (*http.Response, error)
- func CurlArgs(ctx context.Context, args ...string) (*http.Response, error)
- func CurlArgsWithVars(ctx context.Context, vars Variables, args ...string) (*http.Response, error)
- func CurlBytes(ctx context.Context, command ...string) ([]byte, *http.Response, error)
- func CurlBytesArgs(ctx context.Context, args ...string) ([]byte, *http.Response, error)
- func CurlBytesCommand(ctx context.Context, command string) ([]byte, *http.Response, error)
- func CurlCommand(ctx context.Context, command string) (*http.Response, error)
- func CurlCommandWithVars(ctx context.Context, vars Variables, command string) (*http.Response, error)
- func CurlDownload(ctx context.Context, filepath string, command ...string) (int64, *http.Response, error)
- func CurlDownloadArgs(ctx context.Context, filepath string, args ...string) (int64, *http.Response, error)
- func CurlDownloadCommand(ctx context.Context, filepath string, command string) (int64, *http.Response, error)
- func CurlJSON(ctx context.Context, v interface{}, command ...string) (*http.Response, error)
- func CurlJSONArgs(ctx context.Context, v interface{}, args ...string) (*http.Response, error)
- func CurlJSONCommand(ctx context.Context, v interface{}, command string) (*http.Response, error)
- func CurlString(ctx context.Context, command ...string) (string, *http.Response, error)
- func CurlStringArgs(ctx context.Context, args ...string) (string, *http.Response, error)
- func CurlStringCommand(ctx context.Context, command string) (string, *http.Response, error)
- func CurlWithVars(ctx context.Context, vars Variables, command ...string) (*http.Response, error)
- func Execute(ctx context.Context, opts *options.RequestOptions) (*http.Response, error)
- func ExpandVariables(text string, vars Variables) (string, error)
- func FileBody(path string) options.BodySource
- func IsRetryable(err error) bool
- func IsSensitiveHeader(headerName string) bool
- func IsTemporary(err error) bool
- func IsTimeout(err error) bool
- func LoadCookiesFromFile(filePath string) ([]*http.Cookie, error)
- func MultipartBody(fields map[string]string, files ...MultipartFile) options.BodySource
- func ParseError(command string, err error) error
- func ReaderBody(r io.Reader) options.BodySource
- func RedactCommand(cmd string) string
- func RedactHeaders(headers map[string][]string) map[string][]string
- func RedactURL(raw string) string
- func RequestError(url string, err error) error
- func ResponseError(url string, err error) error
- func RetryError(url string, attempts int, err error) error
- func RoundTripperFromHandler(h Handler) http.RoundTripper
- func SaveCookiesToFile(filePath string, cookies []*http.Cookie) error
- func ServerStatusError(url string, status int) error
- func StringBody(s string) options.BodySource
- func TLSError(url string, err error) error
- func TimeoutError(url string, err error) error
- func ValidationError(field string, err error) error
- type Attempt
- type Backoff
- type BreakerConfig
- type Client
- func (c *Client) Close() error
- func (c *Client) Curl(ctx context.Context, command string) (*http.Response, error)
- func (c *Client) CurlBytes(ctx context.Context, command string) ([]byte, *http.Response, error)
- func (c *Client) CurlDownload(ctx context.Context, filePath, command string) (int64, *http.Response, error)
- func (c *Client) CurlJSON(ctx context.Context, v interface{}, command string) (*http.Response, error)
- func (c *Client) CurlString(ctx context.Context, command string) (string, *http.Response, error)
- func (c *Client) Do(ctx context.Context, req *Request) (*http.Response, error)
- func (c *Client) Prepare(command string) (*Request, error)
- func (c *Client) PrepareNoEnv(command string) (*Request, error)
- func (c *Client) PrepareWithVars(vars Variables, command string) (*Request, error)
- func (c *Client) Shutdown(ctx context.Context) error
- type Field
- type GocurlError
- type Handler
- type Hooks
- type Kind
- type Level
- type Limiter
- type Logger
- type Metrics
- type Middleware
- type MultipartFile
- type Option
- func WithAllowInsecureAuth(allow bool) Option
- func WithCircuitBreaker(cfg BreakerConfig) Option
- func WithConnectTimeout(d time.Duration) Option
- func WithCookieFile(path string) Option
- func WithDefaultHeader(key, value string) Option
- func WithExpectContinueTimeout(d time.Duration) Option
- func WithFailOnStatus(fail bool) Option
- func WithFollowRedirects(follow bool) Option
- func WithHTTP2(enabled bool) Option
- func WithHTTP2Only(allowCleartext bool) Option
- func WithHTTP11() Option
- func WithHooks(h Hooks) Option
- func WithIdleConnTimeout(d time.Duration) Option
- func WithInsecure(insecure bool) Option
- func WithLogger(l Logger) Option
- func WithMaxConnsPerHost(n int) Option
- func WithMaxIdleConns(n int) Option
- func WithMaxIdleConnsPerHost(n int) Option
- func WithMaxRedirects(n int) Option
- func WithMaxReplayBytes(n int64) Option
- func WithMetrics(m Metrics) Option
- func WithMiddleware(mw ...Middleware) Option
- func WithProxy(proxyURL string) Option
- func WithRateLimit(rps float64, burst int) Option
- func WithRedirectPolicy(p RedirectPolicy) Option
- func WithRequestIDFunc(fn func() string) Option
- func WithResponseHeaderTimeout(d time.Duration) Option
- func WithRetry(p RetryPolicy) Option
- func WithRetryAttempts(n int) Option
- func WithRetryBudget(ratio, minPerSec float64) Option
- func WithSSRFGuard(policy SSRFPolicy) Option
- func WithTLSConfig(cfg *tls.Config) Option
- func WithTLSHandshakeTimeout(d time.Duration) Option
- func WithTimeout(d time.Duration) Option
- func WithTracer(t Tracer) Option
- func WithTransport(rt http.RoundTripper) Option
- func WithUserAgent(ua string) Option
- type PersistentCookieJar
- type RedirectPolicy
- type Request
- func (r *Request) AddHeader(key, value string) *Request
- func (r *Request) Clone() *Request
- func (r *Request) Method() string
- func (r *Request) Options() *options.RequestOptions
- func (r *Request) URL() string
- func (r *Request) WithBody(b []byte) *Request
- func (r *Request) WithBodyFile(path string) *Request
- func (r *Request) WithBodySource(src options.BodySource) *Request
- func (r *Request) WithHeader(key, value string) *Request
- func (r *Request) WithQuery(key, value string) *Request
- func (r *Request) WithRetryPolicy(p RetryPolicy) *Request
- func (r *Request) WithVars(vars Variables) *Request
- type RequestInfo
- type RequestOption
- type ResponseReader
- type ResultInfo
- type RetryBudget
- type RetryPolicy
- type SSRFPolicy
- type Span
- type TokenBucket
- type Tracer
- type Variables
Constants ¶
const ( CompressionGzip = "gzip" CompressionDeflate = "deflate" CompressionBrotli = "br" )
Compression types supported
const ( // MaxPooledResponseSize is the maximum size for pooled buffers (1MB) MaxPooledResponseSize = 1024 * 1024 // StreamingThreshold is when we switch to streaming mode (1MB) StreamingThreshold = 1024 * 1024 )
const DefaultMaxReplayBytes int64 = 1 << 20 // 1 MiB
DefaultMaxReplayBytes is the default ceiling for buffering a request body for retries when the body cannot be re-obtained via GetBody. Bodies larger than this are sent once and the request becomes non-retryable on attempt 2+.
Variables ¶
var ( ErrParse error = &kindSentinel{KindParse} ErrValidation error = &kindSentinel{KindValidation} ErrConnect error = &kindSentinel{KindConnect} ErrTLS error = &kindSentinel{KindTLS} ErrTimeout error = &kindSentinel{KindTimeout} ErrCanceled error = &kindSentinel{KindCanceled} ErrServerStatus error = &kindSentinel{KindServerStatus} ErrRetryExhausted error = &kindSentinel{KindRetryExhausted} ErrBodyRead error = &kindSentinel{KindBodyRead} )
Sentinel errors for use with errors.Is. They never wrap anything; they exist purely so callers can write errors.Is(err, gocurl.ErrTimeout) without importing or type-asserting the concrete type.
var ErrCircuitOpen = errors.New("gocurl: circuit breaker is open")
ErrCircuitOpen is returned by the circuit breaker middleware when the circuit for a request's key is open (fast-fail). It is classified non-retryable.
var ErrSSRFBlocked = errors.New("blocked by SSRF policy")
ErrSSRFBlocked is wrapped by every SSRF policy rejection so callers can match it with errors.Is. The wrapping GocurlError is classified KindValidation (non-retryable). See specs/07-security.md.
var ErrTooManyRedirects = errors.New("too many redirects")
ErrTooManyRedirects is wrapped by the redirect-cap error so callers (and the CLI's exit-code mapping) can match it with errors.Is even after net/http wraps it in a *url.Error and the engine wraps that in a GocurlError.
var Version = "dev"
Version is the current version of the gocurl library. This can be set at build time using:
go build -ldflags "-X github.com/maniartech/gocurl.Version=1.0.0"
If not set, defaults to "dev".
Functions ¶
func BodyReadError ¶
BodyReadError reports a failure reading/decoding the response body, including the over-limit (truncation) case (Kind == KindBodyRead).
func BytesBody ¶
func BytesBody(b []byte) options.BodySource
BytesBody returns a rewindable BodySource backed by an in-memory byte slice.
func CanceledError ¶
CanceledError reports a context cancellation by the caller (Kind == KindCanceled).
func ConnectError ¶
ConnectError reports a dial/DNS/proxy-connect failure (Kind == KindConnect).
func Curl ¶
Curl executes an HTTP request using curl-compatible syntax.
Automatically detects input format: - Single argument: Parsed as shell command (supports multi-line, backslashes, comments) - Multiple arguments: Treated as separate arguments (no parsing)
Environment variables ($VAR and ${VAR}) are automatically expanded from os.Environ. For explicit control, use CurlCommand() or CurlArgs().
Examples:
// Variadic (auto-detected)
resp, err := Curl(ctx, "-H", "X-Token: abc", "https://example.com")
defer resp.Body.Close()
// Shell command (auto-detected)
resp, err := Curl(ctx, `curl -H 'X-Token: abc' https://example.com`)
defer resp.Body.Close()
// Multi-line (auto-detected)
resp, err := Curl(ctx, `
curl -X POST https://api.example.com \
-H 'Content-Type: application/json' \
-d '{"key":"value"}'
`)
defer resp.Body.Close()
// Access response details
fmt.Println(resp.StatusCode)
fmt.Println(resp.Header.Get("Content-Type"))
func CurlArgs ¶
CurlArgs executes a curl command from variadic arguments. Each argument is a separate token (like os.Args). Environment variables are automatically expanded.
func CurlArgsWithVars ¶
CurlArgsWithVars executes variadic arguments with explicit variables
func CurlBytesArgs ¶
CurlBytesArgs executes variadic args and returns body as []byte + response
func CurlBytesCommand ¶
CurlBytesCommand executes shell command and returns body as []byte + response
func CurlCommand ¶
CurlCommand executes a curl command from a shell-style string. Handles multi-line commands, backslash continuations, and comments. Environment variables are automatically expanded.
func CurlCommandWithVars ¶
func CurlCommandWithVars(ctx context.Context, vars Variables, command string) (*http.Response, error)
CurlCommandWithVars executes a shell-style command with explicit variables
func CurlDownload ¶
func CurlDownload(ctx context.Context, filepath string, command ...string) (int64, *http.Response, error)
CurlDownload executes request and saves body to file, returns bytes written + response
func CurlDownloadArgs ¶
func CurlDownloadArgs(ctx context.Context, filepath string, args ...string) (int64, *http.Response, error)
CurlDownloadArgs executes variadic args and saves body to file
func CurlDownloadCommand ¶
func CurlDownloadCommand(ctx context.Context, filepath string, command string) (int64, *http.Response, error)
CurlDownloadCommand executes shell command and saves body to file
func CurlJSONArgs ¶
CurlJSONArgs executes variadic args and decodes JSON response
func CurlJSONCommand ¶
CurlJSONCommand executes shell command and decodes JSON response
func CurlString ¶
CurlString executes request and returns body as string + response
func CurlStringArgs ¶
CurlStringArgs executes variadic args and returns body as string + response
func CurlStringCommand ¶
CurlStringCommand executes shell command and returns body as string + response
func CurlWithVars ¶
CurlWithVars executes a curl command with explicit variable map. Variables are NOT expanded from environment, only from the provided map. Useful for testing and security-critical code.
func Execute ¶
Execute runs a request described by a typed *options.RequestOptions and returns the live response. It is the programmatic counterpart to the curl-string Curl* helpers: use it when you build the request with the options builder (options.NewRequestOptionsBuilder().….Build()) or by populating options.RequestOptions directly, rather than from a pasted curl command.
The response body is streamed and is subject to opts.ResponseBodyLimit; the caller owns reading and closing resp.Body. With opts.FailOnError set, a >=400 status is returned as a typed error alongside the still-inspectable response (see failOnStatus). For a reusable, middleware-configured client, prefer Client.Prepare/Do; Execute is the one-shot path and shares its engine.
func ExpandVariables ¶
ExpandVariables replaces ${var} or $var with values from map Supports escaping: \${var} becomes literal ${var}
func FileBody ¶
func FileBody(path string) options.BodySource
FileBody returns a rewindable BodySource that streams a file from disk (re-opened for each retry instead of being buffered in memory).
func IsRetryable ¶
IsRetryable reports whether gocurl's resilience layer considers the outermost GocurlError safe to retry for an idempotent request.
func IsSensitiveHeader ¶
IsSensitiveHeader reports whether a header value should be redacted in logs, verbose output, spans, and errors. It first consults the canonical exact-match set, then falls back to a bounded heuristic: no fixed list can enumerate the open set of vendor auth headers (X-Goog-Api-Key, Private-Token, X-Vault-Token, X-Functions-Key, …), so anything that looks credential-bearing — by content or by a credential-style suffix — is redacted. This errs toward redaction, which is the intended fail-safe (Spec 07: never leak secrets).
func IsTemporary ¶
IsTemporary reports whether the outermost GocurlError considers the failure plausibly transient. Advisory only — not a retry decision (see IsRetryable).
func IsTimeout ¶
IsTimeout reports whether err (anywhere in its chain) was caused by a deadline/timeout. It matches a KindTimeout GocurlError as well as a bare context.DeadlineExceeded, so it resolves through a retry-exhausted wrapper.
func LoadCookiesFromFile ¶
LoadCookiesFromFile is a utility function to load cookies from a file into a slice of http.Cookie objects.
func MultipartBody ¶
func MultipartBody(fields map[string]string, files ...MultipartFile) options.BodySource
MultipartBody returns a streaming multipart/form-data BodySource. The body is produced lazily through an io.Pipe; closing the returned reader unblocks the writer goroutine, so an aborted/cancelled request never leaks it.
func ParseError ¶
ParseError creates a parsing error (Kind == KindParse).
func ReaderBody ¶
func ReaderBody(r io.Reader) options.BodySource
ReaderBody returns a NON-rewindable BodySource that streams from r once. It cannot be replayed for retries; use BytesBody/FileBody when retries are needed.
func RedactCommand ¶
RedactCommand returns a copy of a curl command safe to log or display, with credentials (`-u`, Authorization/Bearer/Basic, Cookie, sensitive URL params) redacted. It is the exported entry point to sanitizeCommand.
func RedactHeaders ¶
RedactHeaders creates a copy of headers with sensitive values redacted
func RedactURL ¶
RedactURL returns a copy of raw safe to log or display: sensitive query parameters and any basic-auth userinfo are redacted. It is the exported entry point to the single URL-redaction path (sanitizeURL).
func RequestError ¶
RequestError creates a request execution error. The wrapped transport error is classified (KindConnect/KindTLS/KindTimeout/KindCanceled) rather than blindly tagged, while the Op stays "request" for message compatibility.
func ResponseError ¶
ResponseError creates a response reading error (Kind == KindBodyRead).
func RetryError ¶
RetryError creates a retry exhaustion error (Kind == KindRetryExhausted). It chains the last attempt's (already classified) error so errors.Is(err, ErrTimeout) and friends still resolve through the wrapper.
func RoundTripperFromHandler ¶
func RoundTripperFromHandler(h Handler) http.RoundTripper
RoundTripperFromHandler adapts a Handler into an http.RoundTripper.
func SaveCookiesToFile ¶
SaveCookiesToFile writes cookies to a file in Netscape format
func ServerStatusError ¶
ServerStatusError reports a non-2xx HTTP response surfaced as an error. It is only produced when fail-on-status is enabled (WithFailOnStatus / -f); the engine still returns the *http.Response alongside it so the caller may read the error body.
func StringBody ¶
func StringBody(s string) options.BodySource
StringBody returns a rewindable BodySource backed by a string.
func TimeoutError ¶
TimeoutError reports a deadline exceeded (Kind == KindTimeout).
func ValidationError ¶
ValidationError creates an input validation error (Kind == KindValidation).
Types ¶
type Attempt ¶
type Attempt struct {
Method string // request method
Number int // 1-based attempt index
Response *http.Response // nil on transport error
Err error // nil on HTTP response
Started time.Time // when this attempt began
}
Attempt is the decision input passed to a custom Retryable function.
type Backoff ¶
Backoff computes the delay before the retry that follows a given attempt (1-based). Implementations are pure except for jitter, which draws from the supplied *rand.Rand so tests can inject a seeded source for determinism.
func ConstantBackoff ¶
ConstantBackoff returns a Backoff that always waits d (mirrors the legacy RetryDelay-when-set behavior).
func ExponentialJitter ¶
ExponentialJitter returns an exponential backoff: base*2^(attempt-1) capped at max, with equal jitter (each delay is in [d/2, d]). Equal jitter (rather than AWS full jitter) preserves a usable lower bound on the delay.
type BreakerConfig ¶
type BreakerConfig struct {
FailureThreshold float64 // fraction of failures in the window that trips (default 0.5)
MinRequests int // minimum samples before tripping (default 20)
Window time.Duration // rolling window (default 10s)
OpenTimeout time.Duration // delay before a half-open probe (default 5s)
KeyFunc func(*http.Request) string // circuit key (default: request URL host)
IsFailure func(*http.Response, error) bool // failure predicate (default: err != nil || status >= 500)
}
BreakerConfig configures a CircuitBreaker. Zero-valued fields take sensible defaults (see CircuitBreaker).
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client is a reusable, concurrency-safe HTTP client. Configure it once with functional Options, then execute prepared Requests many times — connections are pooled and reused across calls. A Client owns its transport, so closing one Client never affects another.
See specs/01-client-api.md.
func (*Client) Close ¶
Close releases idle connections held by the Client's transport. It does NOT abort in-flight requests; use Shutdown to drain them first.
func (*Client) CurlDownload ¶
func (c *Client) CurlDownload(ctx context.Context, filePath, command string) (int64, *http.Response, error)
CurlDownload executes a curl command and streams the body to filePath.
func (*Client) CurlJSON ¶
func (c *Client) CurlJSON(ctx context.Context, v interface{}, command string) (*http.Response, error)
CurlJSON executes a curl command and decodes the JSON response into v.
func (*Client) CurlString ¶
CurlString executes a curl command and returns the body as a string.
func (*Client) Do ¶
Do executes a prepared Request and returns the live response. The caller owns resp.Body and must Close it. The body is streamed (never buffered or written to stdout by the Client).
func (*Client) Prepare ¶
Prepare parses a curl command ONCE into a reusable Request. Environment variables ($VAR/${VAR}) are expanded, mirroring CurlCommand.
func (*Client) PrepareNoEnv ¶
PrepareNoEnv parses a curl command without expanding environment variables.
func (*Client) PrepareWithVars ¶
PrepareWithVars parses a curl command, expanding $VAR/${VAR} ONLY from the supplied map (never the process environment).
type Field ¶
Field is a single structured key/value pair for logs and span attributes. Values placed in a Field that leaves the process via a sink must already be redacted; the core only ever passes pre-redacted values.
type GocurlError ¶
type GocurlError struct {
Op string // Operation: "parse", "request", "response", "retry", etc. (legacy/message)
Kind Kind // machine-readable classification
Command string // Command snippet (sanitized)
URL string // Request URL (sanitized)
Status int // HTTP status when Kind == KindServerStatus, else 0
Attempt int // attempts made when Kind == KindRetryExhausted, else 0
Err error // Underlying error
}
GocurlError provides structured error information with context. It is the single concrete error type gocurl returns; callers branch on Kind (the machine-readable discriminator) or use errors.Is with the Err* sentinels, and errors.As to recover this type for full detail.
See specs/08-error-model.md.
func (*GocurlError) Error ¶
func (e *GocurlError) Error() string
Error implements the error interface. The message shape is backward compatible (op: url=… cmd=… underlying); the status is appended to the op segment for KindServerStatus. The joined message is run through scrubErrorString so credentials leaking in from wrapped stdlib errors never appear.
func (*GocurlError) Is ¶
func (e *GocurlError) Is(target error) bool
Is reports whether target is the kindSentinel matching this error's Kind. errors.Is walks the Unwrap chain for us, so a retry-exhausted error wrapping a timeout still matches errors.Is(err, ErrTimeout), and errors.Is(err, context.DeadlineExceeded) keeps resolving through the wrapped error.
func (*GocurlError) Retryable ¶
func (e *GocurlError) Retryable() bool
Retryable reports whether gocurl's resilience layer considers this error safe to retry for an idempotent request: connect and timeout always, server-status only for the retryable status codes. TLS and retry-exhausted are terminal.
func (*GocurlError) Temporary ¶
func (e *GocurlError) Temporary() bool
Temporary reports whether the failure is plausibly transient. Advisory and conservative; it is NOT a retry decision on its own (idempotency still governs retries — see Spec 04).
func (*GocurlError) Timeout ¶
func (e *GocurlError) Timeout() bool
Timeout reports whether the error was caused by a deadline/timeout. It is nil-Err safe and consults a wrapped net.Error / context.DeadlineExceeded.
func (*GocurlError) Unwrap ¶
func (e *GocurlError) Unwrap() error
Unwrap allows errors.Is and errors.As to work
type Handler ¶
Handler executes a single HTTP request and returns its response. It has the same shape as http.RoundTripper.RoundTrip but as a function, so behaviors can be composed. The innermost Handler in a Client's chain is backed by the Client's pooled net/http transport (the request execution engine).
See specs/12-middleware.md.
func HandlerFromRoundTripper ¶
func HandlerFromRoundTripper(rt http.RoundTripper) Handler
HandlerFromRoundTripper adapts an http.RoundTripper into a Handler.
type Hooks ¶
type Hooks struct {
OnRequest func(ctx context.Context, req *http.Request)
OnRetry func(ctx context.Context, req *http.Request, attempt int, lastErr error, lastResp *http.Response)
OnResponse func(ctx context.Context, req *http.Request, resp *http.Response, elapsed time.Duration)
OnError func(ctx context.Context, req *http.Request, err error, kind Kind)
}
Hooks are optional, synchronous lifecycle callbacks. Each is nil-safe. They receive the live request/response (which the caller already owns); anything a hook forwards to a sink must be redacted by the caller.
type Kind ¶
type Kind int
Kind classifies a GocurlError. It is the machine-readable discriminator that callers branch on instead of string-matching error messages; the legacy Op string is retained for backward-compatible message output.
See specs/08-error-model.md.
const ( // KindUnknown is the zero value: an error that was not (or could not be) // classified. A struct-literal GocurlError built without a constructor // reports KindUnknown. KindUnknown Kind = iota // KindParse — tokenize/convert of a curl command failed. KindParse // KindValidation — validateRequestOptions rejected the prepared request. KindValidation // KindConnect — dial / connection refused / DNS / proxy connect failure. KindConnect // KindTLS — handshake, certificate verification, or pin mismatch. KindTLS // KindTimeout — a deadline was exceeded (connect, per-attempt, or overall). KindTimeout // KindCanceled — the context was canceled by the caller. KindCanceled // KindServerStatus — a 4xx/5xx response surfaced as an error (opt-in only, // via WithFailOnStatus / -f). KindServerStatus // KindRetryExhausted — all retry attempts failed. KindRetryExhausted // KindBodyRead — reading/decoding the response body failed (incl. over-limit). KindBodyRead )
type Limiter ¶
Limiter throttles outbound requests. Wait blocks until the request may proceed or the context is done (returning ctx.Err()). It is the pluggable seam for rate limiting: the built-in TokenBucket implements it, and an external limiter (e.g. golang.org/x/time/rate) can be supplied via RateLimiter.
type Logger ¶
Logger is a minimal structured logger. Implementations must be safe for concurrent use. A nil Logger disables logging.
type Metrics ¶
type Metrics interface {
IncRequest(info RequestInfo) // total logical requests started
IncInFlight(delta int) // +1 at start, -1 at end
ObserveLatency(d time.Duration, info ResultInfo) // wall time to response headers
IncRetry(info RequestInfo) // one per retry attempt beyond the first
IncError(kind Kind, info RequestInfo) // one per failed logical request
}
Metrics collects request-level measurements. Implementations must be safe for concurrent use and must not block. A nil Metrics disables metrics. Labels are low-cardinality and pre-redacted (method, host, status, error kind only).
type Middleware ¶
Middleware wraps a Handler to add cross-cutting behavior (retry, tracing, redaction, SSRF guard, …). Middlewares compose so that the FIRST middleware passed is the OUTERMOST: it runs first on the way out and last on the way back.
func CircuitBreaker ¶
func CircuitBreaker(cfg BreakerConfig) Middleware
CircuitBreaker returns a Middleware implementing a per-key (default: per-host) rolling-window circuit breaker. When the failure fraction in the window exceeds the threshold (after MinRequests samples), the circuit opens and requests fast-fail with ErrCircuitOpen; after OpenTimeout a single probe is allowed (half-open), and its outcome closes or re-opens the circuit. The breaker counts only the FINAL outcome of a request (e.g. the result after the retry loop), never individual retry attempts.
func FromMiddlewareFunc ¶
func FromMiddlewareFunc(f middlewares.MiddlewareFunc) Middleware
FromMiddlewareFunc adapts a legacy request-mutating middlewares.MiddlewareFunc (func(*http.Request) (*http.Request, error)) into a Middleware, preserving the behavior of options.RequestOptions.Middleware.
func RateLimiter ¶
func RateLimiter(l Limiter) Middleware
RateLimiter returns a Middleware that blocks each request on l.Wait(ctx) before passing it to the next handler. A nil Limiter is a pass-through.
func SSRFGuard ¶
func SSRFGuard(policy SSRFPolicy) Middleware
SSRFGuard returns a Middleware that runs the SSRF policy pre-flight check on the initial request before it leaves the chain.
type MultipartFile ¶
MultipartFile describes one file part of a multipart/form-data body. Prefer Path (re-openable, so the body is rewindable for retries); Reader is a fallback that makes the body non-rewindable.
type Option ¶
type Option func(*config) error
Option configures a Client. Options are applied in order by New().
func WithAllowInsecureAuth ¶
WithAllowInsecureAuth opts out of the fail-closed plaintext-auth check, so BasicAuth / a bearer token may be sent over cleartext http:// (equivalent to GOCURL_ALLOW_INSECURE_AUTH=1). Off by default.
func WithCircuitBreaker ¶
func WithCircuitBreaker(cfg BreakerConfig) Option
WithCircuitBreaker installs a per-key (default per-host) circuit breaker that fast-fails with ErrCircuitOpen while the circuit is open. It wraps the whole retry loop, counting only the final outcome of each request.
func WithConnectTimeout ¶
WithConnectTimeout sets the connection (dial) timeout.
func WithCookieFile ¶
WithCookieFile sets a cookie jar file used by the Client (curl -b/-c).
func WithDefaultHeader ¶
WithDefaultHeader adds a default header applied to every request that does not already set that header.
func WithExpectContinueTimeout ¶
WithExpectContinueTimeout bounds the wait for a 100-continue response.
func WithFailOnStatus ¶
WithFailOnStatus enables curl -f/--fail behavior: a response with status >= 400 is returned as a ServerStatusError. The live *http.Response is still returned alongside the error so the caller can read the error body. Off by default — a non-2xx status is normally not an error.
func WithFollowRedirects ¶
WithFollowRedirects enables/disables following redirects for requests that do not specify their own policy (e.g. a curl command without -L).
func WithHTTP2Only ¶
WithHTTP2Only forces HTTP/2 exclusively. If allowCleartext is true, HTTP/2 cleartext (h2c) is permitted for non-TLS targets.
HTTP/3 (QUIC) is intentionally out of scope; it is a documented future add-on.
func WithHTTP11 ¶
func WithHTTP11() Option
WithHTTP11 pins HTTP/1.1, suppressing HTTP/2 negotiation. Mutually exclusive with the HTTP/2 options (last one set wins).
There is deliberately no WithHTTP10: curl's --http1.0 implies Connection: close, which would defeat a reusable Client's connection pool on every request. The --http1.0 curl flag is supported on the one-shot Curl* path instead.
func WithIdleConnTimeout ¶
WithIdleConnTimeout sets how long an idle connection is kept before closing.
func WithInsecure ¶
WithInsecure disables TLS certificate verification (curl -k). Use only for testing; it is unsafe in production.
func WithLogger ¶
WithLogger sets the structured Logger (nil disables logging).
func WithMaxConnsPerHost ¶
WithMaxConnsPerHost limits the total connections per host (dialing blocks when reached). 0 means unlimited (net/http semantics).
func WithMaxIdleConns ¶
WithMaxIdleConns sets the maximum number of idle (keep-alive) connections across all hosts. 0 means no limit.
func WithMaxIdleConnsPerHost ¶
WithMaxIdleConnsPerHost sets the maximum idle connections kept per host.
func WithMaxRedirects ¶
WithMaxRedirects sets the maximum number of redirects to follow.
func WithMaxReplayBytes ¶
WithMaxReplayBytes caps how many bytes of a request body are buffered for retries when the body cannot be re-obtained via GetBody. Bodies larger than n are sent once and become non-retryable. n == 0 means unlimited; the default is DefaultMaxReplayBytes (1 MiB).
func WithMetrics ¶
WithMetrics sets the request Metrics collector (nil disables metrics).
func WithMiddleware ¶
func WithMiddleware(mw ...Middleware) Option
WithMiddleware appends user middleware to the Client's chain. The first middleware passed is the outermost.
func WithRateLimit ¶
WithRateLimit installs a client-side token-bucket rate limiter admitting rps requests per second with the given burst. It is the outermost-but-one middleware (inside the circuit breaker), so requests block on a token unless the circuit is already open.
func WithRedirectPolicy ¶
func WithRedirectPolicy(p RedirectPolicy) Option
WithRedirectPolicy sets the redirect policy. If Follow is true and Max is 0, Max defaults to 30 (curl-like).
func WithRequestIDFunc ¶
WithRequestIDFunc sets a generator used to produce an X-Request-ID when a request does not already carry one.
func WithResponseHeaderTimeout ¶
WithResponseHeaderTimeout bounds the wait for response headers after the request is written. 0 means no timeout.
func WithRetry ¶
func WithRetry(p RetryPolicy) Option
WithRetry sets the Client's default RetryPolicy. Retries are idempotency-aware: by default only GET/HEAD/OPTIONS/TRACE/PUT/DELETE (or a request carrying an Idempotency-Key header) are retried — a POST is NOT retried unless AllowMethods opts it in. This differs from the legacy options.RetryConfig / --retry path, which remains method-agnostic.
func WithRetryAttempts ¶
WithRetryAttempts is sugar for WithRetry(DefaultRetryPolicy(n)).
func WithRetryBudget ¶
WithRetryBudget attaches a retry budget (token bucket) limiting retries to the given fraction of request volume, with a per-second floor. It applies to the effective policy when that policy does not already carry its own Budget.
func WithSSRFGuard ¶
func WithSSRFGuard(policy SSRFPolicy) Option
WithSSRFGuard enables the opt-in SSRF guard with the given policy. It blocks the initial request and every redirect hop whose host resolves to a policy-blocked address (loopback/link-local/private/cloud-metadata) unless allow-listed. Use DefaultSSRFPolicy() for the recommended setting.
func WithTLSConfig ¶
WithTLSConfig supplies a *tls.Config that loadTLSConfig merges over the secure defaults for every request (curl flags still apply on top). Use for custom root pools, client certs, or pinning beyond the curl-flag surface.
func WithTLSHandshakeTimeout ¶
WithTLSHandshakeTimeout bounds the TLS handshake duration.
func WithTimeout ¶
WithTimeout sets the overall per-request timeout ceiling (http.Client.Timeout). A per-request --max-time or context deadline may impose a shorter bound.
func WithTracer ¶
WithTracer sets the request Tracer (nil disables tracing).
func WithTransport ¶
func WithTransport(rt http.RoundTripper) Option
WithTransport overrides the Client's owned transport with a custom http.RoundTripper (advanced; useful for tests, mocks, or a hand-tuned transport). When set, TLS/proxy options that build a transport are ignored.
func WithUserAgent ¶
WithUserAgent sets a default User-Agent for requests that do not set their own.
type PersistentCookieJar ¶
type PersistentCookieJar struct {
// contains filtered or unexported fields
}
PersistentCookieJar implements http.CookieJar with file persistence Thread-safe implementation for concurrent access
func NewPersistentCookieJar ¶
func NewPersistentCookieJar(filePath string) (*PersistentCookieJar, error)
NewPersistentCookieJar creates a new cookie jar with file persistence. If filePath is not empty, it will load cookies from that file.
func (*PersistentCookieJar) Cookies ¶
func (pcj *PersistentCookieJar) Cookies(u *url.URL) []*http.Cookie
Cookies implements http.CookieJar interface
func (*PersistentCookieJar) Load ¶
func (pcj *PersistentCookieJar) Load() error
Load reads cookies from a Netscape-format cookie file. Compatible with curl's cookie file format.
func (*PersistentCookieJar) Save ¶
func (pcj *PersistentCookieJar) Save() error
Save persists cookies to the file in Netscape cookie file format. This format is compatible with curl's --cookie and --cookie-jar options.
func (*PersistentCookieJar) SetCookies ¶
func (pcj *PersistentCookieJar) SetCookies(u *url.URL, cookies []*http.Cookie)
SetCookies implements http.CookieJar interface
type RedirectPolicy ¶
type RedirectPolicy struct {
Follow bool
Max int
Allow func(req *http.Request, via []*http.Request) error
}
RedirectPolicy controls how the Client follows HTTP redirects. Allow, if set, is an additional per-redirect authorization hook (e.g. an SSRF guard, Spec 07) that runs after the follow/max checks.
type Request ¶
type Request struct {
// contains filtered or unexported fields
}
Request is an immutable, prepared request template — the "parse once" artifact of the parse-once/execute-many model. Build it from a curl command with Client.Prepare* or programmatically with NewRequest, then execute it many times with Client.Do. Builder methods (WithHeader, WithQuery, …) return a modified COPY, so a Request is safe to share across goroutines.
See specs/02-request-model-and-curl-compat.md.
func NewRequest ¶
func NewRequest(method, rawURL string, opts ...RequestOption) (*Request, error)
NewRequest builds a Request programmatically (no curl parsing). The URL is normalized (a missing scheme defaults to http://, matching the curl path).
func (*Request) Options ¶
func (r *Request) Options() *options.RequestOptions
Options returns a deep copy of the underlying request options. The returned value may be modified freely without affecting the Request.
func (*Request) WithBodyFile ¶
WithBodyFile returns a copy of the request that streams its body from a file.
func (*Request) WithBodySource ¶
func (r *Request) WithBodySource(src options.BodySource) *Request
WithBodySource returns a copy of the request with a streaming body source, overriding any raw body.
func (*Request) WithHeader ¶
WithHeader returns a copy of the request with the header set (replacing any existing values for that key).
func (*Request) WithRetryPolicy ¶
func (r *Request) WithRetryPolicy(p RetryPolicy) *Request
WithRetryPolicy returns a copy of the request with a per-request RetryPolicy that overrides the Client's default for this request only.
type RequestInfo ¶
type RequestInfo struct {
Method string
Host string // host only — never the full URL (cardinality + secrets)
}
RequestInfo carries low-cardinality, pre-redacted request labels.
type RequestOption ¶
type RequestOption func(*options.RequestOptions) error
RequestOption configures a Request built by NewRequest.
func BodyReader ¶
func BodyReader(r io.Reader) RequestOption
BodyReader sets a request body from a reader. It is buffered; for true streaming (and retry replay) use Stream with a BodySource.
func Query ¶
func Query(key, value string) RequestOption
Query sets a query parameter on a NewRequest.
func Stream ¶
func Stream(src options.BodySource) RequestOption
Stream sets a streaming BodySource on a NewRequest (e.g. FileBody, ReaderBody, MultipartBody), overriding any raw body.
type ResponseReader ¶
type ResponseReader struct {
// contains filtered or unexported fields
}
ResponseReader wraps a response for efficient reading
func NewResponseReader ¶
func NewResponseReader(resp *http.Response) *ResponseReader
NewResponseReader creates a reader for efficient response handling
func (*ResponseReader) Bytes ¶
func (rr *ResponseReader) Bytes() ([]byte, error)
Bytes returns the full body as bytes
func (*ResponseReader) Read ¶
func (rr *ResponseReader) Read(p []byte) (int, error)
Read implements io.Reader for streaming
func (*ResponseReader) Reset ¶
func (rr *ResponseReader) Reset()
Reset allows the reader to be reused
func (*ResponseReader) String ¶
func (rr *ResponseReader) String() (string, error)
String returns the body as a string
type ResultInfo ¶
type ResultInfo struct {
RequestInfo
StatusCode int // 0 if no response was received
Kind Kind // KindUnknown on success
}
ResultInfo carries the outcome labels for a completed logical request. Kind is KindUnknown on success.
type RetryBudget ¶
type RetryBudget struct {
// Ratio is the maximum retries expressed as a fraction of requests (e.g. 0.1
// allows retries up to 10% of request volume). Each successful request adds
// Ratio tokens.
Ratio float64
// MinPerSec is a floor on token accrual (tokens/sec) so a low-traffic client
// can still retry occasionally even with little baseline traffic.
MinPerSec float64
// contains filtered or unexported fields
}
RetryBudget caps retries as a fraction of overall request volume so that a partial outage cannot trigger a retry storm. It is a token bucket: each completed request refills it a little, and each retry spends a token. When the bucket is empty, further retries are suppressed (the first attempt of any request is never budget-gated).
A RetryBudget is safe for concurrent use and is shared across all requests of a Client. See specs/04-resilience.md.
func NewRetryBudget ¶
func NewRetryBudget(ratio, minPerSec float64) *RetryBudget
NewRetryBudget returns a retry budget that allows retries up to `ratio` of request volume, with a floor of `minPerSec` tokens/sec for low-traffic clients. The bucket starts full.
func (*RetryBudget) Consume ¶
func (b *RetryBudget) Consume() bool
Consume attempts to spend one retry token. It returns true (and decrements) if a token was available, false otherwise. A nil budget always permits the retry.
func (*RetryBudget) Refund ¶
func (b *RetryBudget) Refund()
Refund credits the budget for a completed request (Ratio tokens), raising the allowance for subsequent retries. A nil budget is a no-op.
type RetryPolicy ¶
type RetryPolicy struct {
MaxAttempts int // total attempts incl. the first; <=1 disables retries
Backoff Backoff // delay schedule (default: ExponentialJitter(100ms,5s))
MaxElapsed time.Duration // 0 = unbounded; overall wall-clock budget across attempts
PerAttempt time.Duration // 0 = none; per-attempt deadline via context.WithTimeout
RetryOnStatus []int // HTTP status codes to retry (nil => 429,500,502,503,504)
RespectRetryAfter bool // honor Retry-After on 429/503
AllowMethods []string // methods eligible for retry; nil => idempotent set
Retryable func(*Attempt) bool // optional override; when set it FULLY governs the retry decision, bypassing the idempotency gate (a non-replayable body still cannot be retried)
Budget *RetryBudget // optional token-bucket cap on retry fraction (nil = unlimited)
// contains filtered or unexported fields
}
RetryPolicy is an immutable value describing how a request is retried. Attach it to a Client with WithRetry / WithRetryAttempts, or to a single prepared Request with Request.WithRetryPolicy. The zero value disables retries (MaxAttempts <= 1 means a single attempt).
Retries are idempotency-aware by default: only methods in the idempotent set (GET, HEAD, OPTIONS, TRACE, PUT, DELETE) — or a request carrying a non-empty Idempotency-Key header — are retried. POST/PATCH/CONNECT are NOT retried unless AllowMethods opts them in. (The legacy options.RetryConfig / --retry path remains method-agnostic for backward compatibility.)
See specs/04-resilience.md.
func DefaultRetryPolicy ¶
func DefaultRetryPolicy(maxAttempts int) RetryPolicy
DefaultRetryPolicy returns the recommended idempotency-aware policy for the given attempt count: exponential jitter (100ms base, 5s cap), retrying 429/500/502/503/504, honoring Retry-After.
type SSRFPolicy ¶
type SSRFPolicy struct {
BlockLoopback bool // 127.0.0.0/8, ::1
BlockLinkLocal bool // 169.254.0.0/16, fe80::/10
BlockPrivate bool // RFC1918, fc00::/7 (ULA)
BlockCloudMetadata bool // 169.254.169.254, fd00:ec2::254, metadata.google.internal
AllowHosts []string // explicit allow-list (host or host:port), checked first
AllowIPs []string // explicit allow-list of IPs or CIDRs, checked first
}
SSRFPolicy controls which destinations a Client may reach. It is OPT-IN (WithSSRFGuard); the default Client enforces nothing, preserving the paste-any-curl promise. Enforcement happens at two points: a pre-flight middleware on the initial request, and a per-redirect check on every hop (so a public URL that 302s to an internal address is still blocked).
func DefaultSSRFPolicy ¶
func DefaultSSRFPolicy() SSRFPolicy
DefaultSSRFPolicy blocks loopback, link-local, private, and cloud-metadata destinations — the recommended setting for untrusted curl input.
func (SSRFPolicy) CheckSSRF ¶
func (p SSRFPolicy) CheckSSRF(ctx context.Context, host string) error
CheckSSRF resolves host and rejects the request if any resolved IP is blocked by the policy and not on the allow-list. A resolution failure is NOT a policy block (it surfaces later as a normal dial error). host may be "host" or "host:port" (bracketed IPv6 accepted).
type Span ¶
type Span interface {
SetAttributes(attrs ...Field)
AddEvent(name string, attrs ...Field)
RecordError(err error)
End()
}
Span is the active span for an in-flight request. End is always called once.
type TokenBucket ¶
type TokenBucket struct {
// contains filtered or unexported fields
}
TokenBucket is a client-side token-bucket rate limiter. It refills at `rps` tokens per second up to a maximum of `burst`, and is safe for concurrent use. It has zero external dependencies.
func NewTokenBucket ¶
func NewTokenBucket(rps float64, burst int) *TokenBucket
NewTokenBucket returns a token bucket admitting `rps` requests per second with a burst capacity of `burst`. The bucket starts full. rps <= 0 is treated as 1; burst < 1 is treated as 1.
type Tracer ¶
type Tracer interface {
// StartSpan returns a child context carrying the span and the span itself.
StartSpan(ctx context.Context, name string, attrs ...Field) (context.Context, Span)
}
Tracer creates a span around one logical request (covering all retry attempts). A nil Tracer disables tracing.
Source Files
¶
- api.go
- body.go
- circuit_breaker.go
- client.go
- clientpool.go
- compression.go
- config.go
- convert.go
- cookie.go
- doc.go
- error_classify.go
- errors.go
- middleware.go
- observability.go
- process.go
- rate_limiter.go
- request.go
- resilience.go
- response.go
- retry.go
- retry_budget.go
- security.go
- security_ssrf.go
- tls_utils.go
- variables.go
- verbose.go
- version.go
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
gocurl
command
|
|
|
internal
|
|
|
corpus
Package corpus holds the curl-compat regression corpus: real command strings copied verbatim from public API docs, each mapped to the parse result it must produce.
|
Package corpus holds the curl-compat regression corpus: real command strings copied verbatim from public API docs, each mapped to the parse result it must produce. |
|
Package tests holds black-box tests that exercise the public gocurl API and the gocurl CLI exactly as an external consumer would — across HTTP methods, flags, and options.
|
Package tests holds black-box tests that exercise the public gocurl API and the gocurl CLI exactly as an external consumer would — across HTTP methods, flags, and options. |
|
Package tokenizer splits a curl command string into shell-style tokens, respecting single/double quotes and backslash escapes.
|
Package tokenizer splits a curl command string into shell-style tokens, respecting single/double quotes and backslash escapes. |