Skip to content

Core Concepts

Understanding the following concepts will give you a quick, holistic grasp of HTTPC.

Two-Layer API Architecture

HTTPC provides two equivalent ways to make requests, mirroring the relationship between http.Get and http.Client in the standard library net/http:

Package-level functions — zero configuration; internally share a lazily-initialized default client. Suitable for scripts and one-off requests:

go
result, err := httpc.Get("https://api.example.com/data")

Client instances — full control over configuration, connection pools, and lifecycle. Suitable for long-running services:

go
client, err := httpc.NewDefault()
defer func() { _ = client.Close() }()
result, err := client.Get("https://api.example.com/data")

Both approaches accept the same request options (WithHeader, WithJSON...) and return the same *Result type. Package-level functions are thin wrappers around Client instances.

TIP

When to use which? One-off requests or quick prototypes -> package-level functions. Production services, custom configuration, or connection-pool management -> Client instances.

Configuration System: Config vs With* Options

HTTPC splits configuration into two independent layers to avoid confusion:

LayerCarrierScopeTypical fields
Instance configurationConfig structEntire client lifetimeTimeouts, retry strategy, connection pool, TLS
Request optionsWithXxx() functionsSingle requestWithHeader, WithJSON, WithTimeout

Instance configuration is passed to New() via the Config struct, starting from DefaultConfig() and modified as needed:

go
cfg := httpc.DefaultConfig()
cfg.Timeouts.Request = 60 * time.Second
cfg.Retry.MaxRetries = 5
client, err := httpc.New(cfg)

Request options are passed on each call to supplement or override instance-level defaults:

go
result, err := client.Get(url,
    httpc.WithHeader("Authorization", "Bearer "+token),
    httpc.WithTimeout(30*time.Second),
)

You can also start from preset configurations (SecureConfig(), PerformanceConfig(), etc.). See Config API.

Request Lifecycle

Every request goes through the following flow:

text
Options applied -> Middleware chain (if any) -> Engine execution -> Retry (if needed) -> Result returned
    ^                                       ^
  With* functions                  Connection pool / TLS / proxy / SSRF checks
  • Options appliedWith* functions set request headers, body, timeouts, etc.
  • Middleware chain — Custom logic for logging, metrics, audit, etc. (configured via Config.Middleware)
  • Engine execution — Connection-pool reuse, TLS handshake, HTTP/2 negotiation, SSRF validation
  • Retry — Automatic exponential-backoff retry on retryable errors (timeouts, 5xx, 429, etc.)
  • Result — Contains response data, request metadata, and retry statistics; reclaimed automatically by GC, no manual release required

Secure Defaults

HTTPC is secure by default and requires no extra configuration to gain:

  • TLS 1.2+ enforced encryption
  • SSRF protection — blocks connections to private/reserved IP addresses (127.0.0.1, 10.x, 192.168.x, etc.)
  • CRLF injection protection — automatic validation of request headers and URLs
  • Response body size limit — 10MB by default, preventing memory exhaustion

To connect to internal services (VPN, intranet), set Security.AllowPrivateIPs = true or use SSRFExemptCIDRs for precise exemptions. See Security Overview.

Error Model

HTTPC distinguishes between network-layer errors and HTTP status codes:

  • Network-layer errors (connection failures, timeouts, TLS errors, etc.) -> returned as error; use errors.As to extract a ClientError for classification and retryability
  • HTTP status codes (4xx, 5xx) -> not returned as error; check them via methods like result.IsSuccess()
go
result, err := client.Get(url)
if err != nil {
    // Network-layer error — request did not complete successfully
    var clientErr *httpc.ClientError
    if errors.As(err, &clientErr) {
        log.Printf("Error type: %s, retryable: %v", clientErr.Code(), clientErr.IsRetryable())
    }
    return err
}
// Request completed successfully — check the HTTP status code
if !result.IsSuccess() {
    log.Printf("HTTP error: %d", result.StatusCode())
}

See Error Handling.