A fast, secure HTTP client library for Go with sensible defaults, minimal dependencies, and built-in resilience.
- Features
- Installation
- Quick Start
- HTTP Methods
- Request Options
- Response Handling
- Context & Cancellation
- File Download
- Domain Client (Session Management)
- Session Manager
- Configuration
- Middleware
- Proxy Configuration
- Security Features
- Error Handling
- Concurrency Safety
- Interfaces
- Documentation
- License
| Feature | Description |
|---|---|
| Secure by Default | TLS 1.2+, SSRF protection, CRLF injection prevention, path traversal blocking |
| High Performance | Connection pooling, HTTP/2, goroutine-safe, sync.Pool optimization |
| Built-in Resilience | Smart retry with exponential backoff and jitter |
| Automatic Decompression | Transparent gzip/deflate handling with zip-bomb protection |
| Developer Friendly | Clean API, intuitive options pattern, comprehensive documentation |
| Minimal Dependencies | 1 dependency (golang.org/x/sys), pure Go stdlib |
| Cookie Management | Full cookie jar support with security validation |
| File Operations | Secure file download with progress tracking and resume support |
go get -u github.com/cybergodev/httpcRequirements: Go 1.25+
package main
import (
"fmt"
"log"
"github.com/cybergodev/httpc"
)
func main() {
// Package-level function - convenient for simple requests
result, err := httpc.Get("https://httpbin.org/get")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Status: %d, Duration: %v\n", result.StatusCode(), result.Meta.Duration)
}package main
import (
"fmt"
"log"
"time"
"github.com/cybergodev/httpc"
)
func main() {
user := map[string]string{"name": "John", "email": "[email protected]"}
result, err := httpc.Post("https://httpbin.org/post",
httpc.WithJSON(user),
httpc.WithBearerToken("your-token"),
httpc.WithTimeout(30*time.Second),
)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Response: %s\n", result.Body())
}package main
import (
"fmt"
"log"
"github.com/cybergodev/httpc"
)
func main() {
// Create a reusable client with default configuration
client, err := httpc.NewDefault()
if err != nil {
log.Fatal(err)
}
defer client.Close()
// Make multiple requests
result, err := client.Get("https://api.example.com/users",
httpc.WithQuery("page", 1),
httpc.WithQuery("limit", 20),
)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Status: %d\n", result.StatusCode())
}// GET with query parameters
result, _ := httpc.Get("https://api.example.com/users",
httpc.WithQuery("page", 1),
httpc.WithQueryMap(map[string]any{"limit": 20}),
)
// POST JSON body
result, _ := httpc.Post("https://api.example.com/users",
httpc.WithJSON(map[string]string{"name": "John"}),
)
// PUT / PATCH / DELETE
result, _ := httpc.Put(url, httpc.WithJSON(data))
result, _ := httpc.Patch(url, httpc.WithJSON(partialData))
result, _ := httpc.Delete(url)
// HEAD / OPTIONS
result, _ := httpc.Head(url)
result, _ := httpc.Options(url)
// Generic request with custom method
result, _ := httpc.Request(ctx, "PROPFIND", url)// Single header
httpc.WithHeader("Authorization", "Bearer token")
// Multiple headers
httpc.WithHeaderMap(map[string]string{
"X-Custom": "value",
"X-Request-ID": "123",
})
// User-Agent
httpc.WithUserAgent("my-app/1.0")// Bearer token
httpc.WithBearerToken("your-jwt-token")
// Basic auth
httpc.WithBasicAuth("username", "password")// Single parameter
httpc.WithQuery("page", 1)
// Multiple parameters
httpc.WithQueryMap(map[string]any{"page": 1, "limit": 20})// JSON
httpc.WithJSON(data)
// XML
httpc.WithXML(data)
// Form data (application/x-www-form-urlencoded)
httpc.WithForm(map[string]string{"key": "value"})
// Multipart form data (file upload)
formData := &httpc.FormData{
Fields: map[string]string{"key": "value"},
Files: map[string]*httpc.FileData{
"upload": {Filename: "doc.pdf", Content: fileBytes, ContentType: "application/pdf"},
},
}
httpc.WithFormData(formData)
// Or use shorthand for single file upload
httpc.WithFile("file", "document.pdf", fileBytes)
// Raw body (auto-detect Content-Type)
httpc.WithBody([]byte("raw data"))
httpc.WithBody(data, httpc.BodyJSON) // explicit BodyKind
// BodyKind constants: BodyAuto, BodyJSON, BodyXML, BodyForm, BodyBinary, BodyMultipart
httpc.WithBinary(binaryData, "application/pdf")
// Stream body (for large request bodies)
httpc.WithStreamBody(true)// Single cookie
httpc.WithCookie(http.Cookie{Name: "session", Value: "abc123"})
// Batch multiple cookies (efficient, pre-allocates capacity)
httpc.WithCookies([]http.Cookie{
{Name: "session_id", Value: "abc123"},
{Name: "user_pref", Value: "dark_mode"},
})
// Multiple cookies from map
httpc.WithCookieMap(map[string]string{
"session_id": "abc123",
"user_pref": "dark_mode",
})
// Cookie string
httpc.WithCookieString("session=abc123; token=xyz")
// Secure cookie with validation (validates cookie security attributes)
cfg := httpc.DefaultCookieSecurityConfig()
cfg.RequireSecure = true
httpc.WithSecureCookie(cfg)// Context for cancellation
httpc.WithContext(ctx)
// Timeout
httpc.WithTimeout(30 * time.Second)
// Retry configuration
httpc.WithMaxRetries(3)
// Redirect control
httpc.WithFollowRedirects(false)
httpc.WithMaxRedirects(5)
// Per-request SSRF override (escape hatch for one trusted internal URL)
httpc.WithAllowPrivateIPs(true)// Before request
httpc.WithOnRequest(func(req httpc.RequestMutator) error {
log.Printf("Sending %s %s", req.Method(), req.URL())
return nil
})
// After response
httpc.WithOnResponse(func(resp httpc.ResponseMutator) error {
log.Printf("Received %d", resp.StatusCode())
return nil
})| Category | Options |
|---|---|
| Headers | WithHeader(key, value), WithHeaderMap(map), WithUserAgent(ua) |
| Auth | WithBearerToken(token), WithBasicAuth(user, pass) |
| Query | WithQuery(key, value), WithQueryMap(map) |
| Body | WithJSON(data), WithXML(data), WithForm(map), WithFormData(*FormData), WithFile(field, filename, content), WithBody(data, ...BodyKind), WithBinary([]byte, ...contentType), WithStreamBody(bool) |
| Cookies | WithCookie(cookie), WithCookies([]Cookie), WithCookieMap(map), WithCookieString("a=1; b=2"), WithSecureCookie(config) |
| Control | WithTimeout(dur), WithMaxRetries(n), WithContext(ctx), WithAllowPrivateIPs(bool) |
| Redirects | WithFollowRedirects(bool), WithMaxRedirects(n) |
| Callbacks | WithOnRequest(fn), WithOnResponse(fn) |
result, _ := httpc.Get("https://api.example.com/users/123")
// Result struct composition:
// result.Request -> *RequestInfo (URL, Method, Headers, Cookies)
// result.Response -> *ResponseInfo (StatusCode, Status, Proto, Headers, Body, RawBody, ContentLength, Cookies)
// result.Meta -> *RequestMeta (Duration, Attempts, RedirectCount, RedirectChain)
// Quick access methods (nil-safe)
fmt.Println(result.StatusCode()) // 200
fmt.Println(result.Proto()) // "HTTP/1.1" or "HTTP/2.0"
fmt.Println(result.RawBody()) // Response body ([]byte)
fmt.Println(result.Body()) // Response body (string)
// Status checks
if result.IsSuccess() { } // 2xx
if result.IsRedirect() { } // 3xx
if result.IsClientError() { } // 4xx
if result.IsServerError() { } // 5xx
// Parse JSON response
var data map[string]interface{}
if err := result.Unmarshal(&data); err != nil {
log.Fatal(err)
}
// Cookie access
cookie := result.GetCookie("session")
if result.HasCookie("session") { }
// Request cookies sent
reqCookie := result.GetRequestCookie("token")
if result.HasRequestCookie("token") { }
// Get all cookies
allResponse := result.ResponseCookies()
allRequest := result.RequestCookies()
// Save response to file
if err := result.SaveToFile("response.json"); err != nil {
log.Fatal(err)
}
// Metadata
fmt.Println(result.Meta.Duration) // Request duration
fmt.Println(result.Meta.Attempts) // Retry count
fmt.Println(result.Meta.RedirectCount) // Redirect count
fmt.Println(result.Meta.RedirectChain) // Redirect URLs
// String representation (safe for logging - masks sensitive headers)
fmt.Println(result.String())HTTPC transparently decompresses gzip and deflate responses. It advertises
Accept-Encoding: gzip, deflate by default (override or extend it with
WithHeader("Accept-Encoding", ...)). A decompression-bomb guard caps the
decompressed size at Security.MaxDecompressedBodySize (default 100 MB).
result, _ := httpc.Get("https://httpbin.org/gzip",
httpc.WithHeaderMap(map[string]string{"Accept-Encoding": "gzip, deflate"}),
)
fmt.Println(result.Body()) // already decompressedNote: Brotli (
br) and LZW (compress) are not supported and return an error if a server sends them. Since httpc does not advertise them, this only happens if you setAccept-Encodingmanually.
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
result, err := httpc.Get("https://api.example.com",
httpc.WithContext(ctx),
)
if errors.Is(err, context.DeadlineExceeded) {
fmt.Println("Request timed out")
}File downloads include built-in security protections:
- UNC path blocking - Prevents access to Windows network paths
- System path protection - Blocks writes to critical system directories
- Path traversal detection - Prevents directory escape attacks
- Resume support - Automatically resumes interrupted downloads
result, _ := httpc.Download(context.Background(),
"https://example.com/file.zip",
&httpc.DownloadConfig{FilePath: "downloads/file.zip"},
)
fmt.Printf("Downloaded: %s at %s/s\n",
httpc.FormatBytes(result.BytesWritten),
httpc.FormatSpeed(result.AverageSpeed))opts := httpc.DefaultDownloadConfig()
opts.FilePath = "downloads/large.zip"
opts.ProgressCallback = func(downloaded, total int64, speed float64) {
pct := float64(downloaded) / float64(total) * 100
fmt.Printf("\r%.1f%% - %s/s", pct, httpc.FormatSpeed(speed))
}
result, _ := httpc.Download(context.Background(), url, opts)opts := httpc.DefaultDownloadConfig()
opts.FilePath = "downloads/large.zip"
opts.ResumeDownload = true
result, _ := httpc.Download(context.Background(), url, opts)
if result.Resumed {
fmt.Println("Download resumed from previous position")
}Download accepts a context.Context directly, so cancellation and timeouts
apply out of the box — there is no separate "WithContext" entry point:
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
defer cancel()
result, _ := httpc.Download(ctx,
"https://example.com/large.zip",
&httpc.DownloadConfig{FilePath: "downloads/large.zip"},
)
// Full control: download config + context + request options
result, _ := httpc.Download(ctx, url, opts,
httpc.WithBearerToken("your-token"),
)| Field | Type | Description |
|---|---|---|
FilePath |
string |
Destination path for the downloaded file |
ProgressCallback |
DownloadProgressCallback |
Progress callback: func(downloaded, total int64, speed float64) |
Overwrite |
bool |
Overwrite existing file (default: false) |
ResumeDownload |
bool |
Resume interrupted download (default: false) |
Checksum |
string |
Expected checksum for verification |
ChecksumAlgorithm |
ChecksumAlgorithm |
Checksum algorithm (e.g., httpc.ChecksumSHA256) |
| Function | Description |
|---|---|
Download(ctx, url, cfg, ...options) |
Canonical entry point — single function for all package-level downloads |
| Field | Type | Description |
|---|---|---|
FilePath |
string |
Local file path written to |
BytesWritten |
int64 |
Total bytes written |
Duration |
time.Duration |
Download duration |
AverageSpeed |
float64 |
Average download speed (bytes/sec) |
StatusCode |
int |
HTTP status code |
ContentLength |
int64 |
Content-Length from server |
Resumed |
bool |
Whether download was resumed |
ResponseCookies |
[]*http.Cookie |
Cookies from response |
ActualChecksum |
string |
Computed checksum of downloaded file |
Proto |
string |
HTTP protocol version (e.g., "HTTP/1.1", "HTTP/2.0") |
ResponseHeaders |
http.Header |
Response headers |
RequestURL |
string |
Actual URL requested |
RequestMethod |
string |
HTTP method used |
RequestHeaders |
http.Header |
Request headers sent |
For multiple requests to the same domain with automatic cookie and header management:
client, _ := httpc.NewDomainDefault("https://api.example.com")
defer client.Close()
// Login - server sets cookies
client.Post("/login", httpc.WithJSON(credentials))
// Set persistent header (used for all requests)
client.SetHeader("Authorization", "Bearer "+token)
// Subsequent requests include cookies + headers automatically
profile, _ := client.Get("/profile")
data, _ := client.Get("/data")// Single cookie
client.SetCookie(&http.Cookie{Name: "session", Value: "abc"})
// Multiple cookies
client.SetCookies([]*http.Cookie{
{Name: "session", Value: "abc"},
{Name: "token", Value: "xyz"},
})
// Read cookies
cookie := client.GetCookie("session") // Single cookie
allCookies := client.GetCookies() // All cookies
// Remove cookies
client.DeleteCookie("session")
client.ClearCookies()client.SetHeader("X-Custom", "value")
client.SetHeaders(map[string]string{"X-App": "v1", "X-Version": "1.0"})
headers := client.GetHeaders()
client.DeleteHeader("X-Old")
client.ClearHeaders()client.URL() // Full base URL
client.Domain() // Domain only
client.Session() // Underlying SessionManagerresult, _ := client.Download(ctx, "/files/data.csv", &httpc.DownloadConfig{FilePath: "data.csv"})
result, _ := client.Download(ctx, "/files/large.zip", downloadOpts)result, _ := client.Get("/users")
result, _ := client.Post("/users", httpc.WithJSON(data))
result, _ := client.Put("/users/1", httpc.WithJSON(data))
result, _ := client.Patch("/users/1", httpc.WithJSON(data))
result, _ := client.Delete("/users/1")
result, _ := client.Head("/users")
result, _ := client.Options("/users")
result, _ := client.Request(ctx, "PROPFIND", "/resource")The SessionManager provides thread-safe cookie and header management, used internally by DomainClient but also available standalone:
// Create session manager
sm, _ := httpc.NewSessionManagerDefault()
// Or with cookie security validation
cfg := httpc.DefaultSessionConfig()
cfg.CookieSecurity = httpc.StrictCookieSecurityConfig()
sm, _ := httpc.NewSessionManager(cfg)
// Manage cookies
sm.SetCookie(&http.Cookie{Name: "session", Value: "abc"})
sm.SetCookies([]*http.Cookie{{Name: "token", Value: "xyz"}})
cookie := sm.GetCookie("session")
allCookies := sm.GetCookies()
sm.DeleteCookie("session")
sm.ClearCookies()
// Manage headers
sm.SetHeader("Authorization", "Bearer token")
sm.SetHeaders(map[string]string{"X-App": "v1"})
headers := sm.GetHeaders()
sm.DeleteHeader("X-Old")
sm.ClearHeaders()
// Update from response
sm.UpdateFromResult(result)
sm.UpdateFromCookies(responseCookies)
// Cookie security
sm.SetCookieSecurity(httpc.StrictCookieSecurityConfig())// Recommended defaults
client, _ := httpc.NewDefault()
// Maximum security (SSRF protection enabled)
client, _ := httpc.New(httpc.SecureConfig())
// High throughput
client, _ := httpc.New(httpc.PerformanceConfig())
// Lightweight (no retries)
client, _ := httpc.New(httpc.MinimalConfig())
// Testing only - disables security features!
client, _ := httpc.New(httpc.TestingConfig())config := httpc.Config{
// Timeouts
Timeouts: httpc.TimeoutConfig{
Request: 30 * time.Second,
Dial: 10 * time.Second,
TLSHandshake: 10 * time.Second,
ResponseHeader: 30 * time.Second,
IdleConn: 90 * time.Second,
},
// Connection
Connection: httpc.ConnectionConfig{
MaxIdleConns: 100,
MaxConnsPerHost: 20,
EnableHTTP2: true,
EnableCookies: false,
},
// Security
Security: httpc.SecurityConfig{
MinTLSVersion: tls.VersionTLS12,
MaxTLSVersion: tls.VersionTLS13,
MaxResponseBodySize: 50 * 1024 * 1024, // 50 MB
AllowPrivateIPs: false,
},
// Retry
Retry: httpc.RetryConfig{
MaxRetries: 3,
Delay: 1 * time.Second,
BackoffFactor: 2.0,
EnableJitter: true,
},
// Defaults (per-request defaults: User-Agent, headers, redirect policy)
Defaults: httpc.RequestDefaults{
UserAgent: "MyApp/1.0",
FollowRedirects: true,
MaxRedirects: 10,
},
}
// Validate configuration before creating client (New() also validates internally)
if err := httpc.ValidateConfig(&config); err != nil {
log.Fatal(err)
}
client, _ := httpc.New(config)
// Inspect configuration (sensitive values are automatically masked)
fmt.Println(config.String())| Function | Description |
|---|---|
DefaultConfig() |
Recommended defaults |
SecureConfig() |
Maximum security (SSRF protection enabled) |
PerformanceConfig() |
High throughput |
MinimalConfig() |
Lightweight (no retries) |
TestingConfig() |
Testing only - disables security features |
ValidateConfig(cfg) |
Validate configuration, returns error |
Config.String() |
Safe string representation (sensitive values masked) |
| Option | Type | Default | Description |
|---|---|---|---|
Timeouts (Timeouts: httpc.TimeoutConfig{...}) |
|||
Timeouts.Request |
time.Duration |
180s |
Overall request timeout |
Timeouts.Dial |
time.Duration |
10s |
TCP connection timeout |
Timeouts.TLSHandshake |
time.Duration |
10s |
TLS handshake timeout |
Timeouts.ResponseHeader |
time.Duration |
0 |
Response header timeout (0 = disabled, uses context timeout) |
Timeouts.IdleConn |
time.Duration |
90s |
Idle connection timeout |
Connection (Connection: httpc.ConnectionConfig{...}) |
|||
Connection.MaxIdleConns |
int |
50 |
Max idle connections |
Connection.MaxConnsPerHost |
int |
10 |
Max connections per host |
Connection.ProxyURL |
string |
"" |
Proxy URL (http/https/socks5/socks5h) |
Connection.EnableSystemProxy |
bool |
false |
Auto-detect system proxy |
Connection.ProxyPool |
[]string |
nil |
Proxy URLs for rotation (see Proxy Configuration) |
Connection.ProxyPoolStrategy |
ProxyStrategy |
ProxyStrategyRoundRobin |
Proxy selection algorithm (ProxyStrategyRoundRobin or ProxyStrategyRandom) |
Connection.ProxyFailureThreshold |
int |
3 |
Consecutive connection failures before circuit-breaking a proxy |
Connection.ProxyCooldown |
time.Duration |
30s |
How long a circuit-broken proxy stays out of rotation |
Connection.ProxyRotateOnStatus |
[]int |
nil |
HTTP status codes that trigger proxy rotation (e.g., []int{403}) |
Connection.EnableHTTP2 |
bool |
true |
Enable HTTP/2 |
Connection.EnableCookies |
bool |
false |
Enable cookie jar |
Connection.EnableDoH |
bool |
false |
Enable DNS-over-HTTPS |
Connection.DoHCacheTTL |
time.Duration |
5m |
DoH cache duration |
Connection.MaxResponseHeaderBytes |
int64 |
0 |
Max response header size (0 = Go stdlib default 10MB) |
Security (Security: httpc.SecurityConfig{...}) |
|||
Security.TLSConfig |
*tls.Config |
nil |
Custom TLS config |
Security.MinTLSVersion |
uint16 |
TLS 1.2 |
Minimum TLS version |
Security.MaxTLSVersion |
uint16 |
TLS 1.3 |
Maximum TLS version |
Security.InsecureSkipVerify |
bool |
false |
Skip TLS verification (testing only!) |
Security.CertificatePinner |
CertificatePinner |
nil |
Certificate pinning — reject MITM even if CA compromised |
Security.MaxResponseBodySize |
int64 |
10MB |
Max response body size |
Security.MaxRequestBodySize |
int64 |
0 |
Max request body size (0 = no limit; does not fall back to MaxResponseBodySize) |
Security.AllowPrivateIPs |
bool |
false |
Allow private IPs (SSRF protection enabled by default) |
Security.ValidateURL |
bool |
true |
Enable URL validation |
Security.ValidateHeaders |
bool |
true |
Enable header validation |
Security.StrictContentLength |
bool |
true |
Strict content-length check |
Security.RedirectWhitelist |
[]string |
nil |
Allowed redirect domains |
Security.MaxDecompressedBodySize |
int64 |
100MB |
Max decompressed body size (zip bomb protection) |
Security.SSRFExemptCIDRs |
[]string |
nil |
CIDR ranges exempted from SSRF blocking |
Security.CookieSecurity |
*CookieSecurityConfig |
nil |
Cookie security validation rules |
Retry (Retry: httpc.RetryConfig{...}) |
|||
Retry.MaxRetries |
int |
3 |
Max retry attempts |
Retry.Delay |
time.Duration |
1s |
Initial retry delay |
Retry.BackoffFactor |
float64 |
2.0 |
Backoff multiplier |
Retry.EnableJitter |
bool |
true |
Add jitter to retries |
Retry.MaxRetryDelay |
time.Duration |
30s |
Cap on maximum retry delay |
Retry.CustomPolicy |
RetryPolicy |
nil |
Custom retry logic |
Middleware (Middleware: httpc.MiddlewareConfig{...}) |
|||
Middleware.Middlewares |
[]MiddlewareFunc |
nil |
Middleware chain |
Defaults (Defaults: httpc.RequestDefaults{...}) |
|||
Defaults.UserAgent |
string |
"httpc/1.0" |
Default User-Agent |
Defaults.Headers |
map[string]string |
{} |
Default headers |
Defaults.FollowRedirects |
bool |
true |
Follow redirects |
Defaults.MaxRedirects |
int |
10 |
Max redirect count |
Every configurable middleware follows the same pattern: a XxxConfig struct,
a DefaultXxxConfig() constructor, and a XxxMiddleware(cfg) factory.
Pass nil to accept defaults.
// Request logging
httpc.LoggingMiddleware(&httpc.LoggingConfig{LogFunc: log.Printf})
// Panic recovery (no config needed)
httpc.RecoveryMiddleware()
// Request ID (nil config = defaults: "X-Request-ID" header, crypto/rand generator)
httpc.RequestIDMiddleware(&httpc.RequestIDConfig{HeaderName: "X-Request-ID"})
// Timeout enforcement
httpc.TimeoutMiddleware(&httpc.TimeoutMiddlewareConfig{Duration: 30 * time.Second})
// Static headers
httpc.HeaderMiddleware(&httpc.HeaderConfig{
Headers: map[string]string{"X-App-Version": "1.0.0"},
})
// Metrics collection
httpc.MetricsMiddleware(&httpc.MetricsConfig{
OnMetrics: func(method, url string, statusCode int, duration time.Duration, err error) {
metrics.Record(method, url, statusCode, duration)
},
})
// Security audit
auditCfg := httpc.DefaultAuditConfig()
auditCfg.OnAudit = func(a httpc.AuditEvent) {
log.Printf("[AUDIT] %s %s -> %d (%v)", a.Method, a.URL, a.StatusCode, a.Duration)
}
httpc.AuditMiddleware(auditCfg)
// Audit with custom config (JSON format, include headers)
auditCfgJSON := httpc.DefaultAuditConfig()
auditCfgJSON.OnAudit = func(a httpc.AuditEvent) { log.Printf("[AUDIT] %v", a) }
auditCfgJSON.IncludeHeaders = true
auditCfgJSON.Format = "json"
httpc.AuditMiddleware(auditCfgJSON)| Field | Type | Description |
|---|---|---|
Timestamp |
time.Time |
Request timestamp |
Method |
string |
HTTP method |
URL |
string |
Request URL |
StatusCode |
int |
Response status code |
Duration |
time.Duration |
Request duration |
Attempts |
int |
Retry attempts |
Error |
error |
Error if any |
SourceIP |
string |
Source IP (set via context key SourceIPKey) |
UserID |
string |
User ID (set via context key UserIDKey) |
RedirectChain |
[]string |
Redirect URLs |
ReqHeaders |
map[string][]string |
Request headers (when IncludeHeaders: true) |
RespHeaders |
map[string][]string |
Response headers (when IncludeHeaders: true) |
AuditEvent supports JSON serialization via MarshalJSON().
chainedMiddleware := httpc.Chain(
httpc.RecoveryMiddleware(),
httpc.LoggingMiddleware(&httpc.LoggingConfig{LogFunc: log.Printf}),
httpc.RequestIDMiddleware(nil),
httpc.HeaderMiddleware(&httpc.HeaderConfig{Headers: map[string]string{"X-App": "v1"}}),
)
config.Middleware.Middlewares = []httpc.MiddlewareFunc{chainedMiddleware}func CustomMiddleware() httpc.MiddlewareFunc {
return func(next httpc.Handler) httpc.Handler {
return func(ctx context.Context, req httpc.RequestMutator) (httpc.ResponseMutator, error) {
// Before request
req.SetHeader("X-Custom", "value")
// Call next handler
resp, err := next(ctx, req)
// After response
return resp, err
}
}
}// Manual proxy
config := httpc.DefaultConfig()
config.Connection.ProxyURL = "http://127.0.0.1:8080"
// Or HTTPS proxy: "https://proxy.example.com:8443"
// System proxy auto-detection (Windows/macOS/Linux)
config := httpc.DefaultConfig()
config.Connection.EnableSystemProxy = true // Reads from environment and system settingsDistribute requests across multiple proxies with automatic failover and status-based rotation:
config := httpc.DefaultConfig()
config.Connection.ProxyPool = []string{
"http://proxy1.example.com:7070",
"http://proxy2.example.com:8080",
"socks5://proxy3.example.com:1080",
}
// Strategy: ProxyStrategyRoundRobin (default) or ProxyStrategyRandom
config.Connection.ProxyPoolStrategy = httpc.ProxyStrategyRoundRobin
// Circuit breaking: after 5 consecutive connection failures, skip the proxy
// for 60s, then retry it (half-open probe). Defaults: threshold=3, cooldown=30s.
config.Connection.ProxyFailureThreshold = 5
config.Connection.ProxyCooldown = 60 * time.Second
// Rotate proxy on 403 (CF/WAF IP blocking). The request is retried
// through a different proxy IP. Requires Retry.MaxRetries > 0.
// Unlike connection failures, status-based rotation does NOT circuit-break
// the proxy — blocks are often target-specific.
config.Connection.ProxyRotateOnStatus = []int{403}
config.Retry.MaxRetries = 3Priority: ProxyURL > ProxyPool > EnableSystemProxy > direct.
| Feature | Description |
|---|---|
| TLS 1.2+ | Modern encryption standards by default |
| Certificate Pinning | Defense against MITM even with a compromised CA |
| SSRF Protection | Two-layer DNS validation blocks private IPs |
| CRLF Injection Prevention | Header and URL validation |
| Path Traversal Protection | Safe file operations |
| Domain Whitelist | Restrict redirects to allowed domains |
| Response Size Limit | Configurable limit to prevent memory exhaustion |
config := httpc.DefaultConfig()
config.Security.RedirectWhitelist = []string{"api.example.com", "secure.example.com"}By default, AllowPrivateIPs is false (SSRF protection enabled), blocking connections to private/reserved IP addresses. Set to true only when connecting to internal services:
// SSRF protection is enabled by default
client, _ := httpc.New(httpc.DefaultConfig())
// Allow private IPs for internal service access
cfg := httpc.DefaultConfig()
cfg.Security.AllowPrivateIPs = true
client, _ := httpc.New(cfg)
// Or exempt specific CIDRs (e.g., VPN/VPC ranges)
cfg := httpc.DefaultConfig()
cfg.Security.SSRFExemptCIDRs = []string{"10.0.0.0/8", "100.64.0.0/10"}
client, _ := httpc.New(cfg)For a single trusted internal call without relaxing the whole client, use the
per-request WithAllowPrivateIPs option (it overrides the client's SSRF policy
for that one request only):
// Default client blocks private IPs; this call opts in for one request
result, err := httpc.Get("http://localhost:8080/health",
httpc.WithAllowPrivateIPs(true),
)Certificate pinning defends against man-in-the-middle attacks even when a trusted
Certificate Authority is compromised: the TLS handshake is rejected unless the
server presents a pinned public key. Enable it by assigning a CertificatePinner
to Security.CertificatePinner.
// Pin by base64-encoded SHA-256 hash of the SubjectPublicKeyInfo (SPKI).
// Supply multiple hashes to support key rotation (accept if ANY matches).
pinner, err := httpc.NewSPKIHashPinner(
"YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2fuihg=", // current key
"C5+lpZ7tcVwmwQIMcRtPbsQtWLABXhQzejna0wHFr8M=", // backup key (rotation)
)
if err != nil {
log.Fatal(err)
}
cfg := httpc.DefaultConfig()
cfg.Security.CertificatePinner = pinner
client, err := httpc.New(cfg)Generate an SPKI hash from a certificate:
openssl x509 -in cert.pem -pubkey -noout | openssl pkey -pubin -outform der \
| openssl dgst -sha256 -binary | openssl enc -base64| Function | Description |
|---|---|
NewSPKIHashPinner(hashes ...string) |
Pin by base64 SHA-256 SPKI hashes (recommended; HPKP format) |
NewPublicKeyPinner(publicKeys ...[]byte) |
Pin by DER-encoded PKIX public keys |
NewCertificatePinnerChain(pinners ...CertificatePinner) |
Combine multiple pinners (accept if ANY matches) |
By default, security warnings are printed to stderr when using insecure configurations (e.g., TestingConfig(), InsecureSkipVerify: true). Redirect or suppress these warnings:
// Suppress security warnings (e.g., in CI environments)
httpc.SetSecurityWarnOutput(io.Discard)
// Or redirect to a custom logger
httpc.SetSecurityWarnOutput(os.Stderr)result, err := httpc.Get(url)
if err != nil {
var clientErr *httpc.ClientError
if errors.As(err, &clientErr) {
fmt.Printf("Error: %s (code: %s)\n", clientErr.Message, clientErr.Code())
fmt.Printf("Retryable: %v\n", clientErr.IsRetryable())
}
return err
}
// Check response status
if !result.IsSuccess() {
return fmt.Errorf("unexpected status code: %d", result.StatusCode())
}| Field | Type | Description |
|---|---|---|
Type |
ErrorType |
Error classification |
Message |
string |
Human-readable error description |
Cause |
error |
Underlying error (unwrap with %w) |
URL |
string |
Request URL |
Method |
string |
HTTP method |
Attempts |
int |
Number of retry attempts |
StatusCode |
int |
HTTP status code (if applicable) |
Host |
string |
Target host |
const (
ErrorTypeUnknown // Unknown or unclassified error
ErrorTypeNetwork // Network-level error (connection refused, DNS failure)
ErrorTypeTimeout // Request timeout
ErrorTypeContextCanceled // Context canceled
ErrorTypeResponseRead // Error reading response body
ErrorTypeTransport // HTTP transport error
ErrorTypeRetryExhausted // All retries exhausted
ErrorTypeTLS // TLS handshake error
ErrorTypeCertificate // Certificate validation error
ErrorTypeDNS // DNS resolution error
ErrorTypeValidation // Request validation error
ErrorTypeHTTP // HTTP-level error (4xx, 5xx)
)var (
ErrClientClosed // Client has been closed
ErrNilConfig // Nil configuration provided
ErrInvalidHeader // Header validation failed
ErrInvalidTimeout // Timeout is negative or exceeds limits
ErrInvalidRetry // Retry configuration is invalid
ErrInvalidConnection // Connection configuration is invalid
ErrInvalidSecurity // Security configuration is invalid
ErrInvalidMiddleware // Middleware configuration is invalid
ErrEmptyFilePath // File path is empty
ErrFileExists // File already exists (and Overwrite is false)
ErrResponseBodyEmpty // Response body is empty
ErrResponseBodyTooLarge // Response body exceeds size limit
)// Check error type using errors.As
var clientErr *httpc.ClientError
if errors.As(err, &clientErr) {
fmt.Printf("Type: %s, Retryable: %v\n", clientErr.Code(), clientErr.IsRetryable())
}HTTPC is designed to be goroutine-safe:
client, _ := httpc.NewDefault() // Default configuration
defer client.Close()
var wg sync.WaitGroup
for i := 0; i < 100; i++ {
wg.Add(1)
go func() {
defer wg.Done()
result, _ := client.Get("https://api.example.com")
// Process response...
}()
}
wg.Wait()Package-level functions (Get, Post, etc.) use a shared default client. You can customize it:
// Set a custom default client
customClient, _ := httpc.New(httpc.SecureConfig())
_ = httpc.SetDefaultClient(customClient)
// Close and reset the default client
_ = httpc.CloseDefaultClient()Thread Safety Guarantees:
- All
Clientmethods are safe for concurrent use - Package-level functions safely use a shared default client
Resultobjects are NOT safe for concurrent access — each goroutine should use its ownResult- Internal metrics use atomic operations
HTTPC exposes interfaces for testing and extensibility. Use these when you need to mock HTTP calls in your tests:
The simplest interface — a single Request method:
type Doer interface {
Request(ctx context.Context, method, url string, options ...RequestOption) (*Result, error)
}The full client interface — extends Doer with convenience methods and download support:
type Client interface {
Doer
Get(url string, options ...RequestOption) (*Result, error)
Post(url string, options ...RequestOption) (*Result, error)
Put(url string, options ...RequestOption) (*Result, error)
Patch(url string, options ...RequestOption) (*Result, error)
Delete(url string, options ...RequestOption) (*Result, error)
Head(url string, options ...RequestOption) (*Result, error)
Options(url string, options ...RequestOption) (*Result, error)
Download(ctx context.Context, url string, cfg *DownloadConfig, options ...RequestOption) (*DownloadResult, error)
Close() error
}Extends Client with domain-scoped session management:
type DomainClienter interface {
Client
URL() string
Domain() string
SetHeader(key, value string) error
SetHeaders(headers map[string]string) error
DeleteHeader(key string)
ClearHeaders()
GetHeaders() map[string]string
SetCookie(cookie *http.Cookie) error
SetCookies(cookies []*http.Cookie) error
DeleteCookie(name string)
ClearCookies()
GetCookies() []*http.Cookie
GetCookie(name string) *http.Cookie
Session() *SessionManager
}type MockClient struct {
httpc.Client // embed for forward compatibility
}
func (m *MockClient) Get(url string, options ...httpc.RequestOption) (*httpc.Result, error) {
return &httpc.Result{
Response: &httpc.ResponseInfo{StatusCode: 200, Body: `{"ok": true}`},
}, nil
}| Resource | Description |
|---|---|
| Getting Started | Installation and first steps |
| Configuration | Client configuration and presets |
| Request Options | Complete options reference |
| Error Handling | Error handling patterns |
| HTTP Redirects | Redirect handling and tracking |
| Cookie API | Cookie management |
| File Download | File download with progress |
| Request Inspection | Request/response inspection |
| Concurrency Safety | Thread safety guarantees |
| Security | Security features and best practices |
21 runnable examples covering all features, ordered from basic to advanced.
Each example is a standalone package main guarded by a //go:build examples
tag (so it stays out of the normal build), so run them one at a time:
go run examples/01_basic_usage.goExamples that call live endpoints (
httpbin.org,example.com) require network access. The certificate-pinning and SSRF-protection examples are self-contained — a rejection is the protection working as designed.
| Category | Examples |
|---|---|
| Basics | 01_basic_usage, 02_http_methods, 03_response_handling, 04_compression |
| Core Features | 05_request_options, 06_error_handling, 07_timeout_retry, 08_client_configuration, 09_redirects, 10_cookies_advanced |
| Stateful Clients | 11_session, 12_domain_client, 13_proxy_configuration, 14_doh |
| Advanced | 15_middleware, 16_concurrent_requests, 17_file_operations, 18_rest_api_client, 19_advanced_patterns |
| Security | 20_certificate_pinning, 21_ssrf_protection |
MIT License - see LICENSE file for details.
If this project helps you, please give it a Star!