httpx

package module
v0.0.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 19, 2026 License: Apache-2.0 Imports: 15 Imported by: 0

README

httpx

main branch GitHub go.mod Go version Go Reference Go Report Card license Release

A comprehensive Go package for building and executing HTTP requests with advanced features.

🚀 Zero Dependencies - Built entirely using the Go standard library for maximum reliability, security, and minimal maintenance overhead. See go.mod

Key Features

  • 🔨 Fluent Request Builder - Chainable API for constructing HTTP requests
  • 🔄 Automatic Retry Logic - Configurable retry strategies with exponential backoff
  • 🎯 Type-Safe Generic Client - Go generics for type-safe HTTP responses
  • Input Validation - Comprehensive validation with error accumulation
  • 🔐 Authentication Support - Built-in Basic and Bearer token authentication
  • 🌐 Proxy Support - HTTP/HTTPS proxy configuration with authentication (supports corporate proxies, authenticated proxies, and custom ports)
  • 📝 Optional Logging - slog integration for observability (disabled by default)
  • 📦 Zero External Dependencies - Only Go standard library, no third-party packages

Table of Contents

Installation

Requirements: Go 1.22 or higher

go get github.com/slashdevops/httpx

Upgrade

To upgrade to the latest version, run:

go get -u github.com/slashdevops/httpx

Quick Start

Simple GET Request
import "github.com/slashdevops/httpx"

// Build and execute a simple GET request
req, err := httpx.NewRequestBuilder("https://api.example.com").
    WithMethodGET().
    WithPath("/users/123").
    WithHeader("Accept", "application/json").
    Build()

if err != nil {
    log.Fatal(err)
}

// Use with standard http.Client
resp, err := http.DefaultClient.Do(req)
Type-Safe Requests with Generic Client
type User struct {
    ID    int    `json:"id"`
    Name  string `json:"name"`
    Email string `json:"email"`
}

// Create a typed client with configuration
client := httpx.NewGenericClient[User](
    httpx.WithTimeout[User](10 * time.Second),
    httpx.WithMaxRetries[User](3),
    httpx.WithRetryStrategy[User](httpx.ExponentialBackoffStrategy),
)

// Execute typed request
response, err := client.Get("https://api.example.com/users/123")
if err != nil {
    log.Fatal(err)
}

// response.Data is strongly typed as User
fmt.Printf("User: %s (%s)\n", response.Data.Name, response.Data.Email)
Request with Retry Logic
// Create client with retry logic
retryClient := httpx.NewClientBuilder().
    WithMaxRetries(3).
    WithRetryStrategy(httpx.ExponentialBackoffStrategy).
    WithRetryBaseDelay(500 * time.Millisecond).
    Build()

// Use with generic client
client := httpx.NewGenericClient[User](
    httpx.WithHTTPClient[User](retryClient),
    httpx.)

response, err := client.Get("/users/123")

Features

Request Builder

The RequestBuilder provides a fluent, chainable API for constructing HTTP requests with comprehensive validation.

Key Features
  • ✅ HTTP methods: GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS, TRACE, CONNECT
  • ✅ Convenience methods for all standard HTTP methods (WithMethodGET, WithMethodPOST, WithMethodPUT, WithMethodDELETE, WithMethodPATCH, WithMethodHEAD, WithMethodOPTIONS, WithMethodTRACE, WithMethodCONNECT)
  • ✅ Query parameters with automatic URL encoding
  • ✅ Custom headers with validation
  • ✅ Authentication (Basic Auth, Bearer Token)
  • ✅ Multiple body formats (JSON, string, bytes, io.Reader)
  • ✅ Context support for timeouts and cancellation
  • ✅ Input validation with error accumulation
  • ✅ Comprehensive error messages
Usage Example
req, err := httpx.NewRequestBuilder("https://api.example.com").
    WithMethodPOST().
    WithPath("/users").
    WithQueryParam("notify", "true").
    WithHeader("Content-Type", "application/json").
    WithHeader("X-Request-ID", "unique-id-123").
    WithBearerAuth("your-token-here").
    WithJSONBody(map[string]string{
        "name":  "John Doe",
        "email": "[email protected]",
    }).
    Build()

if err != nil {
    // Handle validation errors
    log.Fatal(err)
}
Validation Features

The RequestBuilder validates inputs and accumulates errors:

builder := httpx.NewRequestBuilder("https://api.example.com")
builder.HTTPMethod("")           // Error: empty method
builder.WithHeader("", "value")      // Error: empty header key
builder.WithQueryParam("key=", "val") // Error: invalid character in key

// Check for errors before building
if builder.HasErrors() {
    for _, err := range builder.GetErrors() {
        log.Printf("Validation error: %v", err)
    }
}

// Or let Build() report all errors
req, err := builder.Build()
if err != nil {
    // err contains all accumulated validation errors
    log.Fatal(err)
}
Reset and Reuse
builder := httpx.NewRequestBuilder("https://api.example.com")

// Use builder
req1, _ := builder.WithWithMethodGET().WithPath("/users").Build()

// Reset and reuse
builder.Reset()
req2, _ := builder.WithWithMethodPOST().WithPath("/posts").Build()
Generic HTTP Client

The GenericClient provides type-safe HTTP requests with automatic JSON marshaling and unmarshaling using Go generics.

Key Features
  • 🎯 Type-safe responses with automatic JSON unmarshaling
  • 🔄 Convenience methods: Get, Post, Put, Delete, Patch
  • 🔌 Execute method for use with RequestBuilder
  • 📦 ExecuteRaw for non-JSON responses
  • 🌐 Base URL resolution for relative paths
  • 📋 Default headers applied to all requests
  • ❌ Structured error responses
  • 🔁 Full integration with retry logic
Basic Usage
type Post struct {
    ID     int    `json:"id"`
    Title  string `json:"title"`
    Body   string `json:"body"`
    UserID int    `json:"userId"`
}

client := httpx.NewGenericClient[Post](
    httpx.WithTimeout[Post](10 * time.Second),
    httpx.WithMaxRetries[Post](3),
    httpx.WithRetryStrategy[Post](httpx.ExponentialBackoffStrategy),
)

// GET request
response, err := client.Get("https://api.example.com/posts/1")
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Title: %s\n", response.Data.Title)

// POST request
newPost := Post{Title: "New Post", Body: "Content", UserID: 1}
postData, _ := json.Marshal(newPost)
response, err = client.Post("https://api.example.com/posts", bytes.NewReader(postData))
With RequestBuilder

Combine GenericClient with RequestBuilder for maximum flexibility:

type User struct {
    ID    int    `json:"id"`
    Name  string `json:"name"`
    Email string `json:"email"`
}

client := httpx.NewGenericClient[User](
    httpx.WithTimeout[User](15 * time.Second),
    httpx.WithMaxRetries[User](3),
)

// Build complex request
req, err := httpx.NewRequestBuilder("https://api.example.com").
    WithMethodPOST().
    WithPath("/users").
    WithContentType("application/json").
    WithHeader("X-Request-ID", "unique-123").
    WithJSONBody(User{Name: "Jane", Email: "[email protected]"}).
    Build()

if err != nil {
    log.Fatal(err)
}

// Execute with type safety
response, err := client.Execute(req)
if err != nil {
    log.Fatal(err)
}

fmt.Printf("Created user ID: %d\n", response.Data.ID)
Error Handling

The generic client returns structured errors:

response, err := client.Get("/users/999999")
if err != nil {
    // Check if it's an API error
    if apiErr, ok := err.(*httpx.ErrorResponse); ok {
        fmt.Printf("API Error %d: %s\n", apiErr.StatusCode, apiErr.Message)
        // StatusCode: 404
        // Message: "User not found"
    } else {
        // Network error, parsing error, etc.
        log.Printf("Request failed: %v\n", err)
    }
    return
}
Multiple Typed Clients

Use different clients for different response types:

type User struct { /* ... */ }
type Post struct { /* ... */ }

userClient := httpx.NewGenericClient[User](
    httpx.WithTimeout[User](10 * time.Second),
)

postClient := httpx.NewGenericClient[Post](
    httpx.WithTimeout[Post](10 * time.Second),
)

// Fetch user
userResp, _ := userClient.Get("/users/1")

// Fetch user's posts
postsResp, _ := postClient.Get(fmt.Sprintf("/users/%d/posts", userResp.Data.ID))
Retry Logic

The package provides transparent retry logic with configurable strategies.

Retry Strategies

Doubles the wait time between retries:

client := httpx.NewClientBuilder().
    WithMaxRetries(3).
    WithRetryStrategy(httpx.ExponentialBackoffStrategy).
    WithRetryBaseDelay(500 * time.Millisecond).
    WithRetryMaxDelay(10 * time.Second).
    Build()

Wait times: 500ms → 1s → 2s → 4s (capped at maxDelay)

Fixed Delay

Waits a constant duration between retries:

client := httpx.NewClientBuilder().
    WithMaxRetries(3).
    WithRetryStrategy(httpx.FixedDelayStrategy).
    WithRetryBaseDelay(1 * time.Second).
    Build()

Wait times: 1s → 1s → 1s

Jitter Backoff

Adds randomization to exponential backoff to prevent thundering herd:

client := httpx.NewClientBuilder().
    WithMaxRetries(3).
    WithRetryStrategy(httpx.JitterBackoffStrategy).
    WithRetryBaseDelay(500 * time.Millisecond).
    WithRetryMaxDelay(10 * time.Second).
    Build()

Wait times: Random between 0-500ms → 0-1s → 0-2s

What Gets Retried?

The retry logic automatically retries:

  • Network errors (connection failures, timeouts)
  • HTTP 5xx server errors (500-599)
  • HTTP 429 (Too Many Requests)

Does NOT retry:

  • HTTP 4xx client errors (except 429)
  • HTTP 2xx/3xx successful responses
  • Requests without GetBody (non-replayable requests)
Retry with Generic Client
// Create retry client
retryClient := httpx.NewClientBuilder().
    WithMaxRetries(3).
    WithRetryStrategy(httpx.ExponentialBackoffStrategy).
    Build()

// Use with generic client
client := httpx.NewGenericClient[User](
    httpx.WithHTTPClient[User](retryClient),
    httpx.)

// Requests automatically retry on failure
response, err := client.Get("/users/1")
Client Builder

The ClientBuilder provides fine-grained control over HTTP client configuration.

Configuration Options
client := httpx.NewClientBuilder().
    // Timeouts
    WithTimeout(30 * time.Second).
    WithIdleConnTimeout(90 * time.Second).
    WithTLSHandshakeTimeout(10 * time.Second).
    WithExpectContinueTimeout(1 * time.Second).

    // Connection pooling
    WithMaxIdleConns(100).
    WithMaxIdleConnsPerHost(10).
    WithDisableKeepAlive(false).

    // Retry configuration
    WithMaxRetries(3).
    WithRetryStrategy(httpx.ExponentialBackoffStrategy).
    WithRetryBaseDelay(500 * time.Millisecond).
    WithRetryMaxDelay(10 * time.Second).

    Build()
Default Values
Setting Default Valid Range
Timeout 5s 1s - 30s
MaxRetries 3 1 - 10
RetryBaseDelay 500ms 300ms - 5s
RetryMaxDelay 10s 300ms - 120s
MaxIdleConns 100 1 - 200
IdleConnTimeout 90s 1s - 120s
TLSHandshakeTimeout 10s 1s - 15s

The builder validates all settings and uses defaults for out-of-range values.

Proxy Configuration

The httpx package provides comprehensive HTTP/HTTPS proxy support across all client types. Configure proxies to route your requests through corporate firewalls, load balancers, or testing proxies.

Key Features
  • ✅ HTTP and HTTPS proxy support
  • 🔐 Proxy authentication (username/password)
  • 🔄 Works with retry logic
  • 🎯 Compatible with all client types
  • 🌐 Full URL or host:port formats
  • 📝 Graceful fallback on invalid URLs
Basic Usage
With ClientBuilder
// HTTP proxy
client := httpx.NewClientBuilder().
    WithProxy("http://proxy.example.com:8080").
    WithTimeout(10 * time.Second).
    Build()

// HTTPS proxy
client := httpx.NewClientBuilder().
    WithProxy("https://secure-proxy.example.com:3128").
    Build()
With GenericClient
type User struct {
    ID    int    `json:"id"`
    Name  string `json:"name"`
    Email string `json:"email"`
}

client := httpx.NewGenericClient[User](
    httpx.WithProxy[User]("http://proxy.example.com:8080"),
    httpx.WithTimeout[User](10*time.Second),
    httpx.WithMaxRetries[User](3),
)

response, err := client.Get("https://api.example.com/users/1")
With Retry Client
client := httpx.NewHTTPRetryClient(
    httpx.WithProxyRetry("http://proxy.example.com:8080"),
    httpx.WithMaxRetriesRetry(5),
    httpx.WithRetryStrategyRetry(
        httpx.ExponentialBackoff(500*time.Millisecond, 30*time.Second),
    ),
)
Proxy Authentication

Include credentials directly in the proxy URL:

client := httpx.NewClientBuilder().
    WithProxy("http://username:[email protected]:8080").
    Build()

Security Note: For production, consider using environment variables or secret management:

proxyURL := fmt.Sprintf("http://%s:%s@%s:%s",
    os.Getenv("PROXY_USER"),
    os.Getenv("PROXY_PASS"),
    os.Getenv("PROXY_HOST"),
    os.Getenv("PROXY_PORT"),
)

client := httpx.NewClientBuilder().
    WithProxy(proxyURL).
    Build()
Common Proxy Ports
  • HTTP Proxy: 8080, 3128, 8888
  • HTTPS Proxy: 3128, 8443
  • Squid: 3128 (most common)
  • Corporate Proxies: 8080, 80
Disable Proxy

Override environment proxy settings by passing an empty string:

// Disable proxy (ignore HTTP_PROXY environment variable)
client := httpx.NewClientBuilder().
    WithProxy("").
    Build()
Complete Example
package main

import (
    "fmt"
    "log"
    "time"

    "github.com/slashdevops/httpx"
)

type APIResponse struct {
    Message string `json:"message"`
    Status  string `json:"status"`
}

func main() {
    // Configure client with proxy and full options
    client := httpx.NewGenericClient[APIResponse](
        httpx.WithProxy[APIResponse]("http://proxy.example.com:8080"),
        httpx.WithTimeout[APIResponse](15*time.Second),
        httpx.WithMaxRetries[APIResponse](5),
        httpx.WithRetryStrategy[APIResponse](httpx.JitterBackoffStrategy),
        httpx.WithRetryBaseDelay[APIResponse](500*time.Millisecond),
        httpx.WithRetryMaxDelay[APIResponse](30*time.Second),
    )

    // Build request with authentication
    req, err := httpx.NewRequestBuilder("https://api.example.com").
        WithMethodGET().
        WithPath("/data").
        WithBearerAuth("your-token-here").
        WithHeader("Accept", "application/json").
        Build()

    if err != nil {
        log.Fatal(err)
    }

    // Execute through proxy
    response, err := client.Do(req)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Printf("Response: %s\n", response.Data.Message)
}
Error Handling

The library gracefully handles proxy configuration errors:

client := httpx.NewClientBuilder().
    WithProxy("://invalid-url").  // Invalid URL
    WithLogger(logger).            // Optional: log warnings
    Build()

// Client builds successfully, but proxy is not configured
// Warning logged if logger is provided
Logging

The httpx package supports optional logging using Go's standard log/slog package. Logging is disabled by default to maintain clean, silent HTTP operations. Enable it when you need observability into retries, errors, and other HTTP client operations.

Quick Start
Basic Usage
import (
    "log/slog"
    "os"

    "github.com/slashdevops/httpx"
)

// Create a logger
logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
    Level: slog.LevelWarn,
}))

// Use with ClientBuilder
client := httpx.NewClientBuilder().
    WithMaxRetries(3).
    WithLogger(logger).  // Enable logging
    Build()
With Generic Client
type User struct {
    ID   int    `json:"id"`
    Name string `json:"name"`
}

logger := slog.New(slog.NewJSONHandler(os.Stderr, nil))

client := httpx.NewGenericClient[User](
    httpx.WithMaxRetries[User](3),
    httpx.WithLogger[User](logger),
)
With NewHTTPRetryClient
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))

client := httpx.NewHTTPRetryClient(
    httpx.WithMaxRetriesRetry(3),
    httpx.WithRetryStrategyRetry(httpx.ExponentialBackoff(500*time.Millisecond, 10*time.Second)),
    httpx.WithLoggerRetry(logger),
)
What Gets Logged
Retry Attempts (Warn Level)

When a request fails and is being retried:

time=2026-01-17T21:00:00.000+00:00 level=WARN msg="HTTP request returned server error, retrying" attempt=1 max_retries=3 delay=500ms status_code=500 url=https://api.example.com/users method=GET

Attributes logged:

  • attempt: Current retry attempt number (1-indexed)
  • max_retries: Maximum number of retries configured
  • delay: How long the client will wait before retrying
  • status_code: HTTP status code (for server errors) OR
  • error: Error message (for network/connection errors)
  • url: Full request URL
  • method: HTTP method (GET, POST, etc.)
All Retries Failed (Error Level)

When all retry attempts are exhausted:

time=2026-01-17T21:00:00.500+00:00 level=ERROR msg="All retry attempts failed" attempts=4 status_code=503 url=https://api.example.com/users method=GET

Attributes logged:

  • attempts: Total number of attempts made (including initial request)
  • status_code OR error: Final failure reason
  • url: Full request URL
  • method: HTTP method
Logger Configuration
Log Levels

Choose the appropriate log level based on your needs:

// Only log final failures (recommended for production)
logger := slog.New(slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{
    Level: slog.LevelError,
}))

// Log all retry attempts (useful for debugging)
logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
    Level: slog.LevelWarn,
}))

// Log everything including debug info from other packages
logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
    Level: slog.LevelDebug,
}))
Output Formats
Text Format (Development)

Best for human readability during development:

logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
    Level: slog.LevelWarn,
}))

Output:

time=2026-01-17T21:00:00.000+00:00 level=WARN msg="HTTP request returned server error, retrying" attempt=1 max_retries=3 delay=500ms status_code=500
JSON Format (Production)

Best for structured logging and log aggregation:

logger := slog.New(slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{
    Level: slog.LevelError,
}))

Output:

{"time":"2026-01-17T21:00:00.000Z","level":"ERROR","msg":"All retry attempts failed","attempts":4,"status_code":503,"url":"https://api.example.com/users","method":"GET"}
Writing to Files
logFile, err := os.OpenFile("http.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
    log.Fatal(err)
}
defer logFile.Close()

logger := slog.New(slog.NewJSONHandler(logFile, &slog.HandlerOptions{
    Level: slog.LevelWarn,
}))
Logging Best Practices
  1. Default to No Logging: Keep logging disabled in production unless actively troubleshooting:

    // Production - no logging (default)
    client := httpx.NewClientBuilder().
        WithMaxRetries(3).
        Build()  // No WithLogger() call = no logging
    
  2. Use Structured Logging in Production: JSON format is machine-readable and works well with log aggregators:

    logger := slog.New(slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{
        Level: slog.LevelError,  // Only final failures
    }))
    
  3. Enable for Specific Troubleshooting: Turn on logging temporarily when investigating issues:

    // Temporarily enable for debugging
    var logger *slog.Logger
    if os.Getenv("DEBUG_HTTP") != "" {
        logger = slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
            Level: slog.LevelWarn,
        }))
    }
    
    client := httpx.NewClientBuilder().
        WithMaxRetries(3).
        WithLogger(logger).  // Will be nil if not debugging
        Build()
    
  4. Add Context with Attributes: Enhance logs with additional context:

    // Create logger with service context
    logger := slog.New(slog.NewJSONHandler(os.Stderr, nil)).
        With("service", "api-client").
        With("version", "1.0.0")
    
    client := httpx.NewClientBuilder().
        WithLogger(logger).
        Build()
    
  5. Different Loggers for Different Clients: Use separate loggers for different clients to distinguish traffic:

    // User service client
    userLogger := slog.New(slog.NewJSONHandler(os.Stderr, nil)).
        With("client", "user-service")
    userClient := httpx.NewClientBuilder().
        WithLogger(userLogger).
        Build()
    
    // Payment service client
    paymentLogger := slog.New(slog.NewJSONHandler(os.Stderr, nil)).
        With("client", "payment-service")
    paymentClient := httpx.NewClientBuilder().
        WithLogger(paymentLogger).
        Build()
    
Performance Considerations
  • Minimal Overhead: When logging is disabled (logger is nil), the overhead is just a simple nil check
  • No Allocations: Log statements use slog's efficient attribute system
  • Deferred Work: The logger only formats messages if the log level is enabled
Disabling Logging

Simply pass nil or omit the logger:

// Explicitly pass nil
client := httpx.NewClientBuilder().
    WithLogger(nil).  // No logging
    Build()

// Or just don't call WithLogger
client := httpx.NewClientBuilder().
    WithMaxRetries(3).
    Build()  // No logging (default)
Migration Guide

If you have existing code without logging, no changes are needed. The feature is fully backward compatible:

// Old code - still works, no logging
client := httpx.NewClientBuilder().
    WithMaxRetries(3).
    Build()

// New code - add logging when needed
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
client := httpx.NewClientBuilder().
    WithMaxRetries(3).
    WithLogger(logger).  // Just add this line
    Build()
Logging Examples
Example 1: Development Debugging
package main

import (
    "log/slog"
    "os"
    "time"

    "github.com/slashdevops/httpx"
)

func main() {
    // Text output with warn level for debugging
    logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
        Level: slog.LevelWarn,
    }))

    client := httpx.NewClientBuilder().
        WithMaxRetries(3).
        WithRetryBaseDelay(500 * time.Millisecond).
        WithLogger(logger).
        Build()

    // You'll see retry attempts in the console
    resp, err := client.Get("https://api.example.com/flaky-endpoint")
    if err != nil {
        log.Fatal(err)
    }
    defer resp.Body.Close()
}
Example 2: Production Monitoring
package main

import (
    "log/slog"
    "os"

    "github.com/slashdevops/httpx"
)

func main() {
    // JSON output, only errors, to stderr
    logger := slog.New(slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{
        Level: slog.LevelError,
    })).With(
        "service", "payment-processor",
        "environment", "production",
    )

    client := httpx.NewClientBuilder().
        WithMaxRetries(3).
        WithLogger(logger).
        Build()

    // Only final failures will be logged
    resp, err := client.Get("https://payment-api.example.com/status")
    // ...
}
Example 3: Conditional Logging
package main

import (
    "log/slog"
    "os"

    "github.com/slashdevops/httpx"
)

func createClient() *http.Client {
    var logger *slog.Logger

    // Only enable logging if DEBUG environment variable is set
    if os.Getenv("DEBUG") != "" {
        logger = slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
            Level: slog.LevelDebug,
        }))
    }

    return httpx.NewClientBuilder().
        WithMaxRetries(3).
        WithLogger(logger).  // Will be nil in production
        Build()
}
Troubleshooting
Not Seeing Any Logs?
  1. Check logger level: Make sure the level is set to at least LevelWarn:

    logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
        Level: slog.LevelWarn,  // Not Info or Debug
    }))
    
  2. Verify logger is passed: Make sure you called WithLogger():

    client := httpx.NewClientBuilder().
        WithLogger(logger).  // Don't forget this!
        Build()
    
  3. Check if retries are happening: Logs only appear when requests fail and retry. Successful first attempts don't log.

Too Many Logs?
  1. Increase log level to LevelError to only see final failures
  2. Disable logging in production environments where retry behavior is well understood
  3. Use sampling if your log aggregation system supports it
Logging Summary

The logging feature in httpx provides:

  • Optional - Disabled by default, zero overhead when not in use
  • Standard - Uses Go's log/slog package
  • Flexible - Configurable output format, level, and destination
  • Informative - Rich attributes for debugging and monitoring
  • Backward Compatible - Existing code works without changes

Enable it when you need visibility, keep it off for clean, silent operations.

Examples

Complete Example: CRUD Operations
package main

import (
    "fmt"
    "log"
    "time"

    "github.com/slashdevops/httpx"
)

type Todo struct {
    ID        int    `json:"id"`
    Title     string `json:"title"`
    Completed bool   `json:"completed"`
    UserID    int    `json:"userId"`
}

func main() {
    // Create retry client
    retryClient := httpx.NewClientBuilder().
        WithMaxRetries(3).
        WithRetryStrategy(httpx.ExponentialBackoffStrategy).
        WithTimeout(10 * time.Second).
        Build()

    // Create typed client
    client := httpx.NewGenericClient[Todo](
        httpx.WithHTTPClient[Todo](retryClient),
        httpx.        httpx.    )

    // GET - Read
    fmt.Println("Fetching todo...")
    todo, err := client.Get("/todos/1")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Todo: %s (completed: %v)\n", todo.Data.Title, todo.Data.Completed)

    // POST - Create
    fmt.Println("\nCreating new todo...")
    newTodo := Todo{
        Title:     "Learn httputils",
        Completed: false,
        UserID:    1,
    }

    req, _ := httpx.NewRequestBuilder("https://jsonplaceholder.typicode.com").
        WithMethodPOST().
        WithPath("/todos").
        WithContentType("application/json").
        WithJSONBody(newTodo).
        Build()

    created, err := client.Execute(req)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Created todo ID: %d\n", created.Data.ID)

    // PUT - Update
    fmt.Println("\nUpdating todo...")
    updateTodo := created.Data
    updateTodo.Completed = true

    req, _ = httpx.NewRequestBuilder("https://jsonplaceholder.typicode.com").
        WithMethodPUT().
        WithPath(fmt.Sprintf("/todos/%d", updateTodo.ID)).
        WithContentType("application/json").
        WithJSONBody(updateTodo).
        Build()

    updated, err := client.Execute(req)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Updated: completed = %v\n", updated.Data.Completed)

    // DELETE
    fmt.Println("\nDeleting todo...")
    deleteResp, err := client.Delete(fmt.Sprintf("/todos/%d", updateTodo.ID))
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Deleted (status: %d)\n", deleteResp.StatusCode)
}
Authentication Example
// Basic Authentication
req, err := httpx.NewRequestBuilder("https://api.example.com").
    WithMethodGET().
    WithPath("/protected/resource").
    WithBasicAuth("username", "password").
    Build()

// Bearer Token Authentication
req, err := httpx.NewRequestBuilder("https://api.example.com").
    WithMethodGET().
    WithPath("/protected/resource").
    WithBearerAuth("your-jwt-token").
    Build()

// With Generic Client
client := httpx.NewGenericClient[Resource](
    httpx.    httpx.)
Context and Timeout
// Request with timeout
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

req, err := httpx.NewRequestBuilder("https://api.example.com").
    WithMethodGET().
    WithPath("/slow-endpoint").
    Context(ctx).
    Build()

// Request with cancellation
ctx, cancel := context.WithCancel(context.Background())

go func() {
    time.Sleep(2 * time.Second)
    cancel() // Cancel after 2 seconds
}()

req, err := httpx.NewRequestBuilder("https://api.example.com").
    WithMethodGET().
    WithPath("/endpoint").
    Context(ctx).
    Build()
Custom Headers and Query Parameters
req, err := httpx.NewRequestBuilder("https://api.example.com").
    WithMethodGET().
    WithPath("/search").
    WithQueryParam("q", "golang").
    WithQueryParam("sort", "relevance").
    WithQueryParam("limit", "10").
    WithHeader("Accept", "application/json").
    WithHeader("Accept-Language", "en-US").
    WithHeader("X-Request-ID", generateRequestID()).
    WithHeader("X-Correlation-ID", getCorrelationID()).
    WithUserAgent("MyApp/1.0 (Go)").
    Build()

API Reference

RequestBuilder
Constructor
  • NewRequestBuilder(baseURL string) *RequestBuilder
HTTP Methods
  • WithMethodGET() *RequestBuilder
  • WithMethodPOST() *RequestBuilder
  • WithMethodPUT() *RequestBuilder
  • WithMethodDELETE() *RequestBuilder
  • WithMethodPATCH() *RequestBuilder
  • WithMethodHEAD() *RequestBuilder
  • WithMethodOPTIONS() *RequestBuilder
  • WithMethodTRACE() *RequestBuilder
  • WithMethodCONNECT() *RequestBuilder
  • WithMethod(method string) *RequestBuilder - Custom HTTP method with validation
URL and Parameters
  • WithPath(path string) *RequestBuilder - Set URL path
  • WithQueryParam(key, value string) *RequestBuilder - Add single query parameter
  • QueryParams(params map[string]string) *RequestBuilder - Add multiple query parameters
Headers
  • WithHeader(key, value string) *RequestBuilder - Set single header
  • Headers(headers map[string]string) *RequestBuilder - Set multiple headers
  • WithContentType(contentType string) *RequestBuilder - Set Content-Type header
  • WithAccept(accept string) *RequestBuilder - Set Accept header
  • WithUserAgent(userAgent string) *RequestBuilder - Set User-Agent header
Authentication
  • WithBasicAuth(username, password string) *RequestBuilder - Set Basic authentication
  • WithBearerAuth(token string) *RequestBuilder - Set Bearer token authentication
Body
  • WithJSONBody(body any) *RequestBuilder - Set JSON body (auto-marshals)
  • RawBody(body io.Reader) *RequestBuilder - Set raw body
  • WithStringBody(body string) *RequestBuilder - Set string body
  • BytesBody(body []byte) *RequestBuilder - Set bytes body
Other
  • Context(ctx context.Context) *RequestBuilder - Set request context
  • Build() (*http.Request, error) - Build and validate request
Error Handling
  • HasErrors() bool - Check if there are validation errors
  • GetErrors() []error - Get all validation errors
  • Reset() *RequestBuilder - Reset builder state
GenericClient[T any]
Constructor
  • NewGenericClient[T any](options ...GenericClientOption[T]) *GenericClient[T]
Options
  • WithHTTPClient[T any](httpClient HTTPClient) GenericClientOption[T] - Use a pre-configured HTTP client (takes precedence)
  • WithTimeout[T any](timeout time.Duration) GenericClientOption[T] - Set request timeout
  • WithMaxRetries[T any](maxRetries int) GenericClientOption[T] - Set maximum retry attempts
  • WithRetryStrategy[T any](strategy Strategy) GenericClientOption[T] - Set retry strategy (fixed, jitter, exponential)
  • WithRetryStrategyAsString[T any](strategy string) GenericClientOption[T] - Set retry strategy from string
  • WithRetryBaseDelay[T any](baseDelay time.Duration) GenericClientOption[T] - Set base delay for retry strategies
  • WithRetryMaxDelay[T any](maxDelay time.Duration) GenericClientOption[T] - Set maximum delay for retry strategies
  • WithMaxIdleConns[T any](maxIdleConns int) GenericClientOption[T] - Set maximum idle connections
  • WithIdleConnTimeout[T any](idleConnTimeout time.Duration) GenericClientOption[T] - Set idle connection timeout
  • WithTLSHandshakeTimeout[T any](tlsHandshakeTimeout time.Duration) GenericClientOption[T] - Set TLS handshake timeout
  • WithExpectContinueTimeout[T any](expectContinueTimeout time.Duration) GenericClientOption[T] - Set expect continue timeout
  • WithMaxIdleConnsPerHost[T any](maxIdleConnsPerHost int) GenericClientOption[T] - Set maximum idle connections per host
  • WithDisableKeepAlive[T any](disableKeepAlive bool) GenericClientOption[T] - Disable HTTP keep-alive
Methods
  • Execute(req *http.Request) (*Response[T], error) - Execute request with type safety
  • ExecuteRaw(req *http.Request) (*http.Response, error) - Execute and return raw response
  • Do(req *http.Request) (*Response[T], error) - Alias for Execute
  • Get(url string) (*Response[T], error) - Execute GET request
  • Post(url string, body io.Reader) (*Response[T], error) - Execute POST request
  • Put(url string, body io.Reader) (*Response[T], error) - Execute PUT request
  • Delete(url string) (*Response[T], error) - Execute DELETE request
  • Patch(url string, body io.Reader) (*Response[T], error) - Execute PATCH request
  • GetBaseURL() string - Get configured base URL
  • GetDefaultHeaders() map[string]string - Get configured headers
ClientBuilder
Constructor
  • NewClientBuilder() *ClientBuilder
Configuration Methods
  • WithTimeout(timeout time.Duration) *ClientBuilder
  • WithMaxRetries(maxRetries int) *ClientBuilder
  • WithRetryStrategy(strategy Strategy) *ClientBuilder
  • WithRetryBaseDelay(baseDelay time.Duration) *ClientBuilder
  • WithRetryMaxDelay(maxDelay time.Duration) *ClientBuilder
  • WithMaxIdleConns(maxIdleConns int) *ClientBuilder
  • WithMaxIdleConnsPerHost(maxIdleConnsPerHost int) *ClientBuilder
  • WithIdleConnTimeout(idleConnTimeout time.Duration) *ClientBuilder
  • WithTLSHandshakeTimeout(tlsHandshakeTimeout time.Duration) *ClientBuilder
  • WithExpectContinueTimeout(expectContinueTimeout time.Duration) *ClientBuilder
  • WithDisableKeepAlive(disableKeepAlive bool) *ClientBuilder
  • Build() *http.Client - Build configured client
Retry Strategies
  • ExponentialBackoff(base, maxDelay time.Duration) RetryStrategy
  • FixedDelay(delay time.Duration) RetryStrategy
  • JitterBackoff(base, maxDelay time.Duration) RetryStrategy
Types
Response[T any]
type Response[T any] struct {
    Data       T           // Parsed response data
    StatusCode int         // HTTP status code
    Headers    http.Header // Response headers
    RawBody    []byte      // Raw response body
}
ErrorResponse
type ErrorResponse struct {
    Message    string `json:"message,omitempty"`
    StatusCode int    `json:"statusCode,omitempty"`
    ErrorMsg   string `json:"error,omitempty"`
    Details    string `json:"details,omitempty"`
}
Strategy
const (
    FixedDelayStrategy         Strategy = "fixed"
    JitterBackoffStrategy      Strategy = "jitter"
    ExponentialBackoffStrategy Strategy = "exponential"
)

Best Practices

1. Always Check for Errors
req, err := httpx.NewRequestBuilder(baseURL).
    WithMethodGET().
    WithPath("/endpoint").
    Build()

if err != nil {
    log.Printf("Request building failed: %v", err)
    return
}
2. Use Type-Safe Clients for JSON APIs
// Define your model
type User struct {
    ID   int    `json:"id"`
    Name string `json:"name"`
}

// Create typed client
client := httpx.NewGenericClient[User](
    httpx.)

// Enjoy type safety
response, err := client.Get("/users/1")
// response.Data is User, not interface{}
3. Configure Retry Logic for Production
client := httpx.NewClientBuilder().
    WithMaxRetries(3).
    WithRetryStrategy(httpx.ExponentialBackoffStrategy).
    WithRetryBaseDelay(500 * time.Millisecond).
    WithRetryMaxDelay(10 * time.Second).
    WithTimeout(30 * time.Second).
    Build()
4. Reuse HTTP Clients
// Create once, reuse many times
retryClient := httpx.NewClientBuilder().
    WithMaxRetries(3).
    Build()

userClient := httpx.NewGenericClient[User](
    httpx.WithHTTPClient[User](retryClient),
)

postClient := httpx.NewGenericClient[Post](
    httpx.WithHTTPClient[Post](retryClient),
)
5. Use Context for Timeouts
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

req, err := httpx.NewRequestBuilder(baseURL).
    WithMethodGET().
    WithPath("/endpoint").
    Context(ctx).
    Build()
6. Validate Before Building
builder := httpx.NewRequestBuilder(baseURL).
    WithMethodGET().
    WithPath("/endpoint")

// Add potentially invalid inputs
builder.WithHeader(userProvidedKey, userProvidedValue)
builder.WithQueryParam(userProvidedParam, userProvidedValue)

// Check for errors before building
if builder.HasErrors() {
    for _, err := range builder.GetErrors() {
        log.Printf("Validation error: %v", err)
    }
    return
}

req, err := builder.Build()
7. Handle API Errors Properly
response, err := client.Get("/resource")
if err != nil {
    if apiErr, ok := err.(*httpx.ErrorResponse); ok {
        switch apiErr.StatusCode {
        case 404:
            log.Printf("Resource not found: %s", apiErr.Message)
        case 401:
            log.Printf("Authentication failed: %s", apiErr.Message)
        case 429:
            log.Printf("Rate limit exceeded: %s", apiErr.Message)
        default:
            log.Printf("API error %d: %s", apiErr.StatusCode, apiErr.Message)
        }
    } else {
        log.Printf("Network error: %v", err)
    }

    return
}

Thread Safety

All utilities in this package are safe for concurrent use:

client := httpx.NewGenericClient[User](
    httpx.)

// Safe to use from multiple goroutines
var wg sync.WaitGroup
for i := 1; i <= 10; i++ {
    wg.Add(1)
    go func(id int) {
        defer wg.Done()
        user, err := client.Get(fmt.Sprintf("/users/%d", id))
        if err != nil {
            log.Printf("Error fetching user %d: %v", id, err)
            return
        }
        log.Printf("Fetched user: %s", user.Data.Name)
    }(i)
}

wg.Wait()

Testing

The package has comprehensive test coverage (88%+):

go test ./... -v
go test ./... -cover

Contributing

Contributions are welcome! Please ensure:

  1. Build passes: go build ./...
  2. All tests pass: go test ./...
  3. Code is formatted: go fmt ./...
  4. Linters pass: golangci-lint run ./...
  5. Add tests for new features
  6. Update documentation

License

Apache License 2.0. See LICENSE for details.

Credits

Developed by the slashdevops team using Agentic Development. Inspired by popular HTTP client libraries and Go best practices.

Documentation

Overview

Package httpx provides comprehensive utilities for building and executing HTTP requests with advanced features including fluent request building, automatic retry logic, and type-safe generic clients.

Requirements: Go 1.22 or higher is required to use this package.

Zero Dependencies: This package is built entirely using the Go standard library, with no external dependencies. This ensures maximum reliability, security, and minimal maintenance overhead for your projects.

This package is designed to simplify HTTP client development in Go by providing:

  • A fluent, chainable API for building HTTP requests with validation
  • Type-safe HTTP clients using Go generics for automatic JSON marshaling
  • Configurable retry logic with multiple backoff strategies
  • Comprehensive error handling and validation
  • Production-ready defaults with full customization support
  • Zero external dependencies - built purely with Go standard library

Quick Start

Build and execute a simple GET request:

req, err := httpx.NewRequestBuilder("https://api.example.com").
    WithMethodGET().
    Path("/users/123").
    Header("Accept", "application/json").
    Build()

Use type-safe generic client:

type User struct {
    ID    int    `json:"id"`
    Name  string `json:"name"`
}

client := httpx.NewGenericClient[User](
    httpx.//	)
response, err := client.Get("/users/123")
fmt.Printf("User: %s\n", response.Data.Name)

Request Builder

The RequestBuilder provides a fluent API for constructing HTTP requests with comprehensive input validation and error accumulation. All inputs are validated before the request is built, ensuring early error detection.

Basic usage:

req, err := httpx.NewRequestBuilder("https://api.example.com").
    WithMethodPOST().
    Path("/users").
    QueryParam("notify", "true").
    Header("Content-Type", "application/json").
    BearerAuth("your-token-here").
    JSONBody(user).
    Build()

Request builder features:

  • HTTP methods: GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS, TRACE, CONNECT
  • Convenience methods: WithMethodGET, WithMethodPOST, WithMethodPUT, WithMethodDELETE, WithMethodPATCH, WithMethodHEAD, WithMethodOPTIONS, WithMethodTRACE, WithMethodCONNECT
  • Query parameters with automatic URL encoding and validation
  • Custom headers with format validation
  • Authentication: Basic Auth and Bearer Token with validation
  • Multiple body formats: JSON (auto-marshal), string, bytes, io.Reader
  • Context support for timeouts and cancellation
  • Input validation with error accumulation
  • Detailed error messages indicating what failed
  • Reset and reuse builder

Validation features:

The builder validates all inputs and accumulates errors, allowing you to detect multiple issues at once:

builder := httpx.NewRequestBuilder("https://api.example.com")
builder.HTTPMethod("")           // Error: empty method
builder.WithHeader("", "value")      // Error: empty header key
builder.WithQueryParam("key=", "val") // Error: invalid character in key

// Check accumulated errors
if builder.HasErrors() {
    for _, err := range builder.GetErrors() {
        log.Printf("Validation error: %v", err)
    }
}

// Or let Build() report all errors
req, err := builder.Build() // Returns all accumulated errors

Builder reuse:

builder := httpx.NewRequestBuilder("https://api.example.com")
req1, _ := builder.WithWithMethodGET().WithPath("/users").Build()
builder.Reset() // Clear state
req2, _ := builder.WithWithMethodPOST().WithPath("/posts").Build()

Generic HTTP Client

The GenericClient provides type-safe HTTP requests using Go generics with automatic JSON marshaling and unmarshaling. This eliminates the need for manual type assertions and reduces boilerplate code.

Basic usage:

type User struct {
    ID    int    `json:"id"`
    Name  string `json:"name"`
    Email string `json:"email"`
}

client := httpx.NewGenericClient[User](
    httpx.WithTimeout[User](10*time.Second),
    httpx.WithMaxRetries[User](3),
    httpx.WithRetryStrategy[User](httpx.ExponentialBackoffStrategy),
)

// GET request - response.Data is strongly typed as User
response, err := client.Get("/users/1")
if err != nil {
    log.Fatal(err)
}
fmt.Printf("User: %s (%s)\n", response.Data.Name, response.Data.Email)

Generic client features:

  • Type-safe responses with automatic JSON unmarshaling
  • Compile-time type checking for response data
  • Convenience methods: Get, Post, Put, Delete, Patch
  • Execute method for custom requests (works with RequestBuilder)
  • ExecuteRaw for non-JSON responses (images, files, etc.)
  • Flexible configuration via option pattern
  • Built-in retry logic with configurable strategies
  • Connection pooling and timeout configuration
  • TLS handshake and idle connection timeout settings
  • Structured error responses with ErrorResponse type
  • Full integration with ClientBuilder for complex configurations
  • Debug logging support (uses slog)

Configuration options:

  • WithTimeout: Set request timeout
  • WithMaxRetries: Set maximum retry attempts
  • WithRetryStrategy: Configure retry strategy (fixed, jitter, exponential)
  • WithRetryBaseDelay: Set base delay for retry strategies
  • WithRetryMaxDelay: Set maximum delay for retry strategies
  • WithMaxIdleConns: Set maximum idle connections
  • WithIdleConnTimeout: Set idle connection timeout
  • WithTLSHandshakeTimeout: Set TLS handshake timeout
  • WithExpectContinueTimeout: Set expect continue timeout
  • WithMaxIdleConnsPerHost: Set maximum idle connections per host
  • WithDisableKeepAlive: Disable HTTP keep-alive
  • WithProxy: Configure HTTP/HTTPS proxy server
  • WithHTTPClient: Use a pre-configured HTTP client (takes precedence)

Integration with RequestBuilder:

req, err := httpx.NewRequestBuilder("https://api.example.com").
    WithMethodPOST().
    Path("/users").
    ContentType("application/json").
    Header("X-Request-ID", "unique-123").
    JSONBody(newUser).
    Build()

response, err := client.Execute(req) // Type-safe execution

Multiple typed clients:

userClient := httpx.NewGenericClient[User](...)
postClient := httpx.NewGenericClient[Post](...)

user, _ := userClient.Get("/users/1")
posts, _ := postClient.Get(fmt.Sprintf("/users/%d/posts", user.Data.ID))

Error handling:

response, err := client.Get("/users/999")
if err != nil {
    if apiErr, ok := err.(*httpx.ErrorResponse); ok {
        // Structured API error
        fmt.Printf("API Error %d: %s\n", apiErr.StatusCode, apiErr.Message)
    } else {
        // Network error, parsing error, etc.
        log.Printf("Request failed: %v\n", err)
    }
}

Retry Logic

The package provides transparent retry logic that automatically retries failed requests using configurable backoff strategies. Retry logic preserves all request properties including headers and authentication.

What gets retried:

  • Network errors (connection failures, timeouts)
  • HTTP 5xx server errors (500-599)
  • HTTP 429 (Too Many Requests)

What does NOT get retried:

  • HTTP 4xx client errors (except 429)
  • HTTP 2xx/3xx successful responses
  • Requests without GetBody (non-replayable)

Available retry strategies:

  1. Exponential Backoff (recommended for most use cases):

    strategy := httpx.ExponentialBackoff(500*time.Millisecond, 10*time.Second) // Wait times: 500ms → 1s → 2s → 4s → 8s (capped at maxDelay)

  2. Fixed Delay (useful for predictable retry timing):

    strategy := httpx.FixedDelay(1*time.Second) // Wait times: 1s → 1s → 1s

  3. Jitter Backoff (prevents thundering herd problem):

    strategy := httpx.JitterBackoff(500*time.Millisecond, 10*time.Second) // Wait times: random(0-500ms) → random(0-1s) → random(0-2s)

Direct usage (advanced):

client := httpx.NewHTTPRetryClient(
    httpx.WithMaxRetriesRetry(3),
    httpx.WithRetryStrategyRetry(httpx.ExponentialBackoff(500*time.Millisecond, 10*time.Second)),
    httpx.WithBaseTransport(http.DefaultTransport),
)

Client Builder

The ClientBuilder provides a fluent API for configuring HTTP clients with retry logic, timeouts, and connection pooling. All settings are validated and default to production-ready values if out of range.

Basic configuration:

client := httpx.NewClientBuilder().
    WithTimeout(30 * time.Second).
    WithMaxRetries(3).
    WithRetryStrategy(httpx.ExponentialBackoffStrategy).
    Build()

Advanced configuration:

client := httpx.NewClientBuilder().
    // Timeouts
    WithTimeout(30 * time.Second).
    WithIdleConnTimeout(90 * time.Second).
    WithTLSHandshakeTimeout(10 * time.Second).
    WithExpectContinueTimeout(1 * time.Second).

    // Connection pooling
    WithMaxIdleConns(100).
    WithMaxIdleConnsPerHost(10).
    WithDisableKeepAlive(false).

    // Retry configuration
    WithMaxRetries(3).
    WithRetryStrategy(httpx.ExponentialBackoffStrategy).
    WithRetryBaseDelay(500 * time.Millisecond).
    WithRetryMaxDelay(10 * time.Second).

    // Proxy configuration
    WithProxy("http://proxy.example.com:8080").

    Build()

Combine with GenericClient:

retryClient := httpx.NewClientBuilder().
    WithMaxRetries(3).
    WithRetryStrategy(httpx.ExponentialBackoffStrategy).
    Build()

client := httpx.NewGenericClient[User](
    httpx.WithHTTPClient[User](retryClient),
    httpx.//	)

Default values (validated and adjusted if out of range):

  • Timeout: 5 seconds (valid: 1s-30s)
  • MaxRetries: 3 (valid: 1-10)
  • RetryBaseDelay: 500ms (valid: 300ms-5s)
  • RetryMaxDelay: 10s (valid: 300ms-120s)
  • MaxIdleConns: 100 (valid: 1-200)
  • IdleConnTimeout: 90s (valid: 1s-120s)
  • TLSHandshakeTimeout: 10s (valid: 1s-15s)

Error Handling

The package provides comprehensive error handling with specific error types:

  1. ErrorResponse (from GenericClient): Structured API errors with status code and message

  2. ClientError: Generic HTTP client operation errors

  3. RequestBuilder validation errors: Accumulated during building, reported at Build() time

Error handling examples:

// Generic client errors
response, err := client.Get("/users/999")
if err != nil {
    if apiErr, ok := err.(*httpx.ErrorResponse); ok {
        switch apiErr.StatusCode {
        case 404:
            log.Printf("Not found: %s", apiErr.Message)
        case 401:
            log.Printf("Unauthorized: %s", apiErr.Message)
        case 429:
            log.Printf("Rate limited: %s", apiErr.Message)
        default:
            log.Printf("API error %d: %s", apiErr.StatusCode, apiErr.Message)
        }
    } else {
        log.Printf("Network error: %v", err)
    }
}

// Builder validation errors
builder := httpx.NewRequestBuilder("https://api.example.com")
builder.WithHeader("", "value") // Invalid
if builder.HasErrors() {
    for _, err := range builder.GetErrors() {
        log.Printf("Validation: %v", err)
    }
}

Best Practices

  1. Use type-safe clients for JSON APIs:

    client := httpx.NewGenericClient[User](...) response, err := client.Get("/users/1") // response.Data is User, not interface{}

  2. Configure retry logic for production:

    retryClient := httpx.NewClientBuilder(). WithMaxRetries(3). WithRetryStrategy(httpx.ExponentialBackoffStrategy). Build()

  3. Reuse HTTP clients (they're safe for concurrent use):

    client := httpx.NewGenericClient[User](...) // Use from multiple goroutines safely

  4. Use contexts for timeouts and cancellation:

    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() req, _ := builder.WithContext(ctx).Build()

  5. Validate before building:

    if builder.HasErrors() { // Handle validation errors }

  6. Handle API errors appropriately:

    if apiErr, ok := err.(*httpx.ErrorResponse); ok { // Handle specific status codes }

Proxy Configuration

The package provides comprehensive proxy support for all HTTP clients. Proxy configuration works transparently across all client types and supports both HTTP and HTTPS proxies with optional authentication.

Basic proxy configuration with ClientBuilder:

client := httpx.NewClientBuilder().
    WithProxy("http://proxy.example.com:8080").
    Build()

HTTPS proxy:

client := httpx.NewClientBuilder().
    WithProxy("https://secure-proxy.example.com:3128").
    Build()

Proxy with authentication:

client := httpx.NewClientBuilder().
    WithProxy("http://username:[email protected]:8080").
    Build()

Proxy with GenericClient:

client := httpx.NewGenericClient[User](
    httpx.WithProxy[User]("http://proxy.example.com:8080"),
    httpx.WithTimeout[User](10*time.Second),
    httpx.WithMaxRetries[User](3),
)

Proxy with retry client:

client := httpx.NewHTTPRetryClient(
    httpx.WithProxyRetry("http://proxy.example.com:8080"),
    httpx.WithMaxRetriesRetry(5),
)

Disable proxy (override environment variables):

client := httpx.NewClientBuilder().
    WithProxy(""). // Empty string disables proxy
    Build()

Common proxy ports:

  • HTTP proxy: 8080, 3128, 8888
  • HTTPS proxy: 3128, 8443
  • SOCKS proxy: 1080 (not directly supported, use custom transport)

The proxy configuration:

  • Works transparently with all request types
  • Preserves all headers and authentication
  • Compatible with retry logic
  • Supports connection pooling
  • Respects timeout settings
  • Validates proxy URL format
  • Falls back gracefully on invalid URLs

Thread Safety

All utilities in this package are safe for concurrent use across multiple goroutines:

  • RequestBuilder instances should not be shared between goroutines
  • GenericClient instances are safe for concurrent use
  • HTTP clients built by ClientBuilder are safe for concurrent use
  • Retry logic preserves request immutability

Example concurrent usage:

client := httpx.NewGenericClient[User](...)

var wg sync.WaitGroup
for i := 1; i <= 10; i++ {
    wg.Add(1)
    go func(id int) {
        defer wg.Done()
        user, err := client.Get(fmt.Sprintf("/users/%d", id))
        // Process user
    }(i)
}
wg.Wait()

Debugging

The package uses slog for debug logging. Enable debug logging to see:

  • Request details (method, URL, headers, body)
  • Response details (status, headers, body)
  • Retry attempts and delays

Enable debug logging:

logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
    Level: slog.LevelDebug,
}))
slog.SetDefault(logger)

Documentation

You can view the full documentation and examples locally by running:

go doc -http=:8080

Then navigate to http://localhost:8080/pkg/github.com/slashdevops/httpx/ in your browser to browse the complete documentation, examples, and source code.

See Also

For complete examples and API reference, see the README.md file or visit: https://pkg.go.dev/github.com/slashdevops/httpx

Example

Example demonstrates using exponential backoff.

package main

import (
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"
	"sync/atomic"
	"time"

	"github.com/slashdevops/httpx"
)

func main() {
	var requestCount int32
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		count := atomic.AddInt32(&requestCount, 1)
		if count <= 3 { // Fail first 3 times
			fmt.Printf("Server: Request %d -> 500 Internal Server Error\n", count)
			w.WriteHeader(http.StatusInternalServerError)
		} else {
			fmt.Printf("Server: Request %d -> 200 OK\n", count)
			w.WriteHeader(http.StatusOK)
			_, _ = w.Write([]byte("Success after backoff"))
		}
	}))
	defer server.Close()

	// Create a client with exponential backoff.
	// Base delay 5ms, max delay 50ms, max 4 retries.
	retryClient := httpx.NewHTTPRetryClient(
		httpx.WithMaxRetriesRetry(4),
		httpx.WithRetryStrategyRetry(httpx.ExponentialBackoff(5*time.Millisecond, 50*time.Millisecond)),
	)

	fmt.Println("Client: Making request with exponential backoff...")
	resp, err := retryClient.Get(server.URL)
	if err != nil {
		fmt.Printf("Client: Request failed: %v\n", err)
		return
	}
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	fmt.Printf("Client: Received response: Status=%s, Body='%s'\n", resp.Status, string(body))
	// Note: Duration will vary slightly, but should reflect increasing delays.
	fmt.Printf("Client: Total time approx > %dms (due to backoff)\n", (5 + 10 + 20)) // 5ms + 10ms + 20ms delays

}
Output:

Client: Making request with exponential backoff...
Server: Request 1 -> 500 Internal Server Error
Server: Request 2 -> 500 Internal Server Error
Server: Request 3 -> 500 Internal Server Error
Server: Request 4 -> 200 OK
Client: Received response: Status=200 OK, Body='Success after backoff'
Client: Total time approx > 35ms (due to backoff)
Example (WithLogger_disabled)

Example_withLogger_disabled demonstrates the default behavior with no logging. This example shows that by default, logging is disabled for clean, silent operation.

package main

import (
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"
	"sync/atomic"
	"time"

	"github.com/slashdevops/httpx"
)

func main() {
	attempts := atomic.Int32{}

	// Create a test server that fails twice, then succeeds
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		attempt := attempts.Add(1)
		if attempt <= 2 {
			w.WriteHeader(http.StatusInternalServerError)
			return
		}
		w.WriteHeader(http.StatusOK)
		fmt.Fprint(w, "Success")
	}))
	defer server.Close()

	// Create client WITHOUT logging (default behavior)
	// No logger means silent retries - clean operation without log noise
	client := httpx.NewClientBuilder().
		WithMaxRetries(3).
		WithRetryBaseDelay(500 * time.Millisecond). // Use valid delay
		WithRetryMaxDelay(10 * time.Second).        // Use valid delay
		Build()                                     // No WithLogger call = no logging

	fmt.Println("Making request with logging disabled (default)...")
	resp, err := client.Get(server.URL)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	fmt.Printf("Response: %s\n", body)
	fmt.Println("No retry logs appear - silent operation")

}
Output:

Making request with logging disabled (default)...
Response: Success
No retry logs appear - silent operation

Index

Examples

Constants

View Source
const (
	ValidMaxIdleConns             = 200
	ValidMinIdleConns             = 1
	ValidMaxIdleConnsPerHost      = 200
	ValidMinIdleConnsPerHost      = 1
	ValidMaxIdleConnTimeout       = 120 * time.Second
	ValidMinIdleConnTimeout       = 1 * time.Second
	ValidMaxTLSHandshakeTimeout   = 15 * time.Second
	ValidMinTLSHandshakeTimeout   = 1 * time.Second
	ValidMaxExpectContinueTimeout = 5 * time.Second
	ValidMinExpectContinueTimeout = 1 * time.Second
	ValidMaxTimeout               = 30 * time.Second
	ValidMinTimeout               = 1 * time.Second
	ValidMaxRetries               = 10
	ValidMinRetries               = 1
	ValidMaxBaseDelay             = 5 * time.Second
	ValidMinBaseDelay             = 300 * time.Millisecond
	ValidMaxMaxDelay              = 120 * time.Second
	ValidMinMaxDelay              = 300 * time.Millisecond

	// DefaultMaxRetries is the default number of retry attempts
	DefaultMaxRetries = 3

	// DefaultBaseDelay is the default base delay for backoff strategies
	DefaultBaseDelay = 500 * time.Millisecond

	// DefaultMaxDelay is the default maximum delay for backoff strategies
	DefaultMaxDelay = 10 * time.Second

	// DefaultMaxIdleConns is the default maximum number of idle connections
	DefaultMaxIdleConns = 100

	// DefaultIdleConnTimeout is the default idle connection timeout
	DefaultIdleConnTimeout = 90 * time.Second

	// DefaultTLSHandshakeTimeout is the default TLS handshake timeout
	DefaultTLSHandshakeTimeout = 10 * time.Second

	// DefaultExpectContinueTimeout is the default expect continue timeout
	DefaultExpectContinueTimeout = 1 * time.Second

	// DefaultDisableKeepAlive is the default disable keep-alive setting
	DefaultDisableKeepAlive = false

	// DefaultMaxIdleConnsPerHost is the default maximum number of idle connections per host
	DefaultMaxIdleConnsPerHost = 100

	// DefaultTimeout is the default timeout for HTTP requests
	DefaultTimeout = 5 * time.Second
)

Variables

View Source
var ErrAllRetriesFailed = errors.New("all retry attempts failed")

Functions

func NewHTTPRetryClient

func NewHTTPRetryClient(options ...RetryClientOption) *http.Client

NewHTTPRetryClient creates a new http.Client configured with the retry transport. Use the provided options to customize the retry behavior. By default, it uses 3 retries with exponential backoff strategy and no logging.

Example (WithCustomTransport)

ExampleNewHTTPRetryClient_withCustomTransport demonstrates using a custom base transport with specific transport settings while maintaining transparent retry behavior.

package main

import (
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"
	"sync/atomic"
	"time"

	"github.com/slashdevops/httpx"
)

func main() {
	var requestCount int32

	// Create a test server that fails initially to show retry behavior with custom transport
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		count := atomic.AddInt32(&requestCount, 1)
		fmt.Printf("Server: Request %d from custom transport\n", count)

		if count <= 1 {
			w.WriteHeader(http.StatusInternalServerError)
		} else {
			w.WriteHeader(http.StatusOK)
			if _, err := w.Write([]byte("Custom transport with retries works!")); err != nil {
				fmt.Printf("Failed to write response: %v\n", err)
			}
		}
	}))
	defer server.Close()

	// Create a custom transport with specific settings
	customTransport := &http.Transport{
		MaxIdleConns:        50,               // Custom connection pool size
		IdleConnTimeout:     30 * time.Second, // Custom idle timeout
		DisableKeepAlives:   false,            // Enable keep-alive
		MaxIdleConnsPerHost: 10,               // Custom per-host connection limit
		TLSHandshakeTimeout: 5 * time.Second,  // Custom TLS timeout
	}

	// Create retry client with custom transport
	client := httpx.NewHTTPRetryClient(
		httpx.WithMaxRetriesRetry(3),
		httpx.WithRetryStrategyRetry(httpx.ExponentialBackoff(5*time.Millisecond, 50*time.Millisecond)),
		httpx.WithBaseTransport(customTransport),
	)

	fmt.Println("Client: Making request with custom transport...")
	resp, err := client.Get(server.URL)
	if err != nil {
		fmt.Printf("Client: Request failed: %v\n", err)
		return
	}
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	fmt.Printf("Client: Response: %s\n", string(body))
	fmt.Printf("Client: Custom transport config preserved (MaxIdleConns: %d)\n",
		customTransport.MaxIdleConns)

}
Output:

Client: Making request with custom transport...
Server: Request 1 from custom transport
Server: Request 2 from custom transport
Client: Response: Custom transport with retries works!
Client: Custom transport config preserved (MaxIdleConns: 50)
Example (WithExistingAuth)

ExampleNewHTTPRetryClient_withExistingAuth demonstrates how the default client transparently preserves existing authentication headers in requests.

package main

import (
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"
	"sync/atomic"
	"time"

	"github.com/slashdevops/httpx"
)

func main() {
	var requestCount int32

	// Create a server that requires authentication
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		count := atomic.AddInt32(&requestCount, 1)
		auth := r.Header.Get("Authorization")

		if auth == "" {
			fmt.Printf("Server: Request %d -> 401 Unauthorized (no auth)\n", count)
			w.WriteHeader(http.StatusUnauthorized)
			return
		}

		fmt.Printf("Server: Request %d with %s -> ", count, auth)
		if count <= 2 {
			fmt.Println("500 Internal Server Error")
			w.WriteHeader(http.StatusInternalServerError)
		} else {
			fmt.Println("200 OK")
			w.WriteHeader(http.StatusOK)
			if _, err := w.Write([]byte("Authenticated and retried successfully")); err != nil {
				fmt.Printf("Failed to write response: %v\n", err)
			}
		}
	}))
	defer server.Close()

	// Create default client - works transparently with any existing auth
	client := httpx.NewHTTPRetryClient(
		httpx.WithMaxRetriesRetry(3),
		httpx.WithRetryStrategyRetry(httpx.ExponentialBackoff(5*time.Millisecond, 50*time.Millisecond)),
	)

	// Create request with existing auth token (from your app's auth system)
	req, _ := http.NewRequest("GET", server.URL, nil)
	req.Header.Set("Authorization", "Bearer my-token-123")

	fmt.Println("Client: Making authenticated request...")
	resp, err := client.Do(req)
	if err != nil {
		fmt.Printf("Client: Request failed: %v\n", err)
		return
	}
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	fmt.Printf("Client: Success! Status=%s, Body='%s'\n", resp.Status, string(body))
	fmt.Printf("Client: Auth header preserved through %d retries\n", atomic.LoadInt32(&requestCount))

}
Output:

Client: Making authenticated request...
Server: Request 1 with Bearer my-token-123 -> 500 Internal Server Error
Server: Request 2 with Bearer my-token-123 -> 500 Internal Server Error
Server: Request 3 with Bearer my-token-123 -> 200 OK
Client: Success! Status=200 OK, Body='Authenticated and retried successfully'
Client: Auth header preserved through 3 retries
Example (WithProxy)

ExampleNewHTTPRetryClient_withProxy demonstrates using a proxy with the retry client.

package main

import (
	"fmt"
	"time"

	"github.com/slashdevops/httpx"
)

func main() {
	// Create a retry client with proxy configuration
	client := httpx.NewHTTPRetryClient(
		httpx.WithProxyRetry("http://proxy.example.com:8080"),
		httpx.WithMaxRetriesRetry(5),
		httpx.WithRetryStrategyRetry(
			httpx.ExponentialBackoff(500*time.Millisecond, 30*time.Second),
		),
	)

	fmt.Printf("Retry client configured with proxy\n")
	_ = client

}
Output:

Retry client configured with proxy

Types

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client is a custom HTTP client with configurable settings and retry strategies. Works transparently with existing request headers. It preserves all headers without requiring explicit configuration.

type ClientBuilder

type ClientBuilder struct {
	// contains filtered or unexported fields
}

ClientBuilder is a builder for creating a custom HTTP client

func NewClientBuilder

func NewClientBuilder() *ClientBuilder

NewClientBuilder creates a new ClientBuilder with default settings and retry strategy

Example

ExampleNewClientBuilder demonstrates creating a basic HTTP client with default settings

package main

import (
	"fmt"
	"log"

	"github.com/slashdevops/httpx"
)

func main() {
	// Create client with default settings
	client := httpx.NewClientBuilder().Build()

	// Use the client for HTTP requests
	resp, err := client.Get("https://api.example.com/health")
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()

	fmt.Printf("Status: %s\n", resp.Status)
	// Output would show: Status: 200 OK
}
Example (AllOptions)

ExampleNewClientBuilder_allOptions demonstrates using all available configuration options

package main

import (
	"fmt"
	"log"
	"time"

	"github.com/slashdevops/httpx"
)

func main() {
	// Comprehensive configuration showing all available options
	client := httpx.NewClientBuilder().
		// HTTP client timeout
		WithTimeout(20 * time.Second).

		// Connection pool settings
		WithMaxIdleConns(150).
		WithMaxIdleConnsPerHost(15).
		WithIdleConnTimeout(60 * time.Second).

		// TLS and protocol settings
		WithTLSHandshakeTimeout(8 * time.Second).
		WithExpectContinueTimeout(2 * time.Second).
		WithDisableKeepAlive(false).

		// Retry configuration
		WithMaxRetries(4).
		WithRetryStrategy(httpx.JitterBackoffStrategy).
		WithRetryBaseDelay(300 * time.Millisecond).
		WithRetryMaxDelay(20 * time.Second).
		Build()

	// Use the fully configured client
	resp, err := client.Get("https://api.example.com/comprehensive")
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()

	fmt.Printf("Request completed with all options configured\n")
}
Example (ConnectionPooling)

ExampleNewClientBuilder_connectionPooling demonstrates configuring connection pooling

package main

import (
	"fmt"
	"log"
	"time"

	"github.com/slashdevops/httpx"
)

func main() {
	// Configure connection pooling for high-throughput scenarios
	client := httpx.NewClientBuilder().
		WithMaxIdleConns(200).
		WithMaxIdleConnsPerHost(20).
		WithIdleConnTimeout(90 * time.Second).
		Build()

	// Reuse connections efficiently across multiple requests
	for i := 0; i < 10; i++ {
		resp, err := client.Get(fmt.Sprintf("https://api.example.com/items/%d", i))
		if err != nil {
			log.Printf("Request %d failed: %v", i, err)
			continue
		}
		resp.Body.Close()
	}

	fmt.Println("Completed batch requests")
}
Example (DisableKeepAlive)

ExampleNewClientBuilder_disableKeepAlive demonstrates disabling connection reuse

package main

import (
	"fmt"
	"log"

	"github.com/slashdevops/httpx"
)

func main() {
	// Disable keep-alive for scenarios requiring fresh connections
	client := httpx.NewClientBuilder().
		WithDisableKeepAlive(true).
		Build()

	// Each request will use a new connection
	resp, err := client.Get("https://api.example.com/status")
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()

	fmt.Printf("Response: %s\n", resp.Status)
}
Example (FixedDelayStrategy)

ExampleNewClientBuilder_fixedDelayStrategy demonstrates using fixed delay retry strategy

package main

import (
	"fmt"
	"log"
	"time"

	"github.com/slashdevops/httpx"
)

func main() {
	// Configure client with fixed delay between retries
	client := httpx.NewClientBuilder().
		WithMaxRetries(3).
		WithRetryStrategyAsString("fixed").
		WithRetryBaseDelay(1 * time.Second).
		Build()

	// Each retry will wait exactly 1 second
	resp, err := client.Get("https://api.example.com/endpoint")
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()

	fmt.Printf("Success: %v\n", resp.StatusCode == 200)
}
Example (JitterBackoffStrategy)

ExampleNewClientBuilder_jitterBackoffStrategy demonstrates using jitter backoff for retry

package main

import (
	"fmt"
	"log"
	"time"

	"github.com/slashdevops/httpx"
)

func main() {
	// Configure client with jitter backoff to prevent thundering herd
	client := httpx.NewClientBuilder().
		WithMaxRetries(4).
		WithRetryStrategy(httpx.JitterBackoffStrategy).
		WithRetryBaseDelay(500 * time.Millisecond).
		WithRetryMaxDelay(10 * time.Second).
		Build()

	// Retries will have randomized delays
	resp, err := client.Get("https://api.example.com/resource")
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()

	fmt.Printf("Completed with status: %d\n", resp.StatusCode)
}
Example (MultipleClients)

ExampleNewClientBuilder_multipleClients demonstrates creating clients with different configurations

package main

import (
	"fmt"
	"time"

	"github.com/slashdevops/httpx"
)

func main() {
	// Fast client for health checks
	healthClient := httpx.NewClientBuilder().
		WithTimeout(2 * time.Second).
		WithMaxRetries(1).
		Build()

	// Standard client for API calls
	apiClient := httpx.NewClientBuilder().
		WithTimeout(15 * time.Second).
		WithMaxRetries(3).
		WithRetryStrategy(httpx.ExponentialBackoffStrategy).
		Build()

	// Heavy client for bulk operations
	bulkClient := httpx.NewClientBuilder().
		WithTimeout(60 * time.Second).
		WithMaxRetries(5).
		WithRetryStrategy(httpx.JitterBackoffStrategy).
		WithMaxIdleConns(200).
		WithMaxIdleConnsPerHost(20).
		Build()

	// Use different clients for different purposes
	_, _ = healthClient.Get("https://api.example.com/health")
	_, _ = apiClient.Get("https://api.example.com/users/123")
	_, _ = bulkClient.Post("https://api.example.com/bulk-import", "application/json", nil)

	fmt.Println("Multiple clients with different configurations")
}
Example (ProductionConfig)

ExampleNewClientBuilder_productionConfig demonstrates a production-ready configuration

package main

import (
	"fmt"
	"io"
	"log"
	"net/http"
	"time"

	"github.com/slashdevops/httpx"
)

func main() {
	// Production-ready client with optimal settings
	client := httpx.NewClientBuilder().
		// Timeouts
		WithTimeout(30 * time.Second).
		WithTLSHandshakeTimeout(10 * time.Second).
		WithExpectContinueTimeout(1 * time.Second).

		// Connection pooling
		WithMaxIdleConns(100).
		WithMaxIdleConnsPerHost(10).
		WithIdleConnTimeout(90 * time.Second).

		// Retry configuration
		WithMaxRetries(5).
		WithRetryStrategy(httpx.ExponentialBackoffStrategy).
		WithRetryBaseDelay(500 * time.Millisecond).
		WithRetryMaxDelay(30 * time.Second).
		Build()

	// Create request with proper headers
	req, err := http.NewRequest("GET", "https://api.example.com/v1/users", nil)
	if err != nil {
		log.Fatal(err)
	}
	req.Header.Set("Authorization", "Bearer prod-token-xyz")
	req.Header.Set("User-Agent", "MyApp/2.0")

	// Execute with automatic retries and connection pooling
	resp, err := client.Do(req)
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	fmt.Printf("Retrieved %d bytes with status %d\n", len(body), resp.StatusCode)
}
Example (TlsConfiguration)

ExampleNewClientBuilder_tlsConfiguration demonstrates TLS timeout settings

package main

import (
	"fmt"
	"log"
	"time"

	"github.com/slashdevops/httpx"
)

func main() {
	// Configure TLS handshake timeout for secure connections
	client := httpx.NewClientBuilder().
		WithTLSHandshakeTimeout(10 * time.Second).
		WithExpectContinueTimeout(2 * time.Second).
		Build()

	resp, err := client.Get("https://secure-api.example.com/data")
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()

	fmt.Printf("Secure request completed: %s\n", resp.Status)
}
Example (Transparent)

ExampleNewClientBuilder_transparent demonstrates using the ClientBuilder for advanced configuration while maintaining transparent behavior.

package main

import (
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"
	"time"

	"github.com/slashdevops/httpx"
)

func main() {
	// Create a simple test server
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// Echo back any custom headers that were sent
		customValue := r.Header.Get("X-Custom-Header")
		if customValue != "" {
			fmt.Printf("Server: Received custom header: %s\n", customValue)
		}
		w.WriteHeader(http.StatusOK)
		if _, err := w.Write([]byte("Custom headers preserved!")); err != nil {
			fmt.Printf("Failed to write response: %v\n", err)
		}
	}))
	defer server.Close()

	// Build client with custom settings - still works transparently
	client := httpx.NewClientBuilder().
		WithMaxRetries(5).
		WithRetryStrategy(httpx.JitterBackoffStrategy).
		WithTimeout(10 * time.Second).
		Build()

	// Create request with custom headers
	req, _ := http.NewRequest("GET", server.URL, nil)
	req.Header.Set("X-Custom-Header", "my-custom-value")
	req.Header.Set("Authorization", "Bearer token-from-somewhere")

	fmt.Println("Client: Making request with custom headers...")
	resp, err := client.Do(req)
	if err != nil {
		fmt.Printf("Client: Request failed: %v\n", err)
		return
	}
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	fmt.Printf("Client: Response: %s\n", string(body))

}
Output:

Client: Making request with custom headers...
Server: Received custom header: my-custom-value
Client: Response: Custom headers preserved!
Example (WithRetryStrategy)

ExampleNewClientBuilder_withRetryStrategy demonstrates configuring retry behavior

package main

import (
	"fmt"
	"log"
	"time"

	"github.com/slashdevops/httpx"
)

func main() {
	// Configure client with exponential backoff retry strategy
	client := httpx.NewClientBuilder().
		WithMaxRetries(5).
		WithRetryStrategy(httpx.ExponentialBackoffStrategy).
		WithRetryBaseDelay(500 * time.Millisecond).
		WithRetryMaxDelay(30 * time.Second).
		Build()

	// The client will automatically retry on transient failures
	resp, err := client.Get("https://api.example.com/data")
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()

	fmt.Printf("Status: %d\n", resp.StatusCode)
}
Example (WithTimeout)

ExampleNewClientBuilder_withTimeout demonstrates configuring a client with custom timeout

package main

import (
	"fmt"
	"io"
	"log"
	"time"

	"github.com/slashdevops/httpx"
)

func main() {
	// Create client with custom timeout
	client := httpx.NewClientBuilder().
		WithTimeout(10 * time.Second).
		Build()

	resp, err := client.Get("https://api.example.com/users")
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	fmt.Printf("Response length: %d bytes\n", len(body))
}

func (*ClientBuilder) Build

func (b *ClientBuilder) Build() *http.Client

Build creates and returns a new HTTP client with the specified settings and retry strategy. The client works transparently, preserving any existing headers in requests without requiring explicit configuration.

func (*ClientBuilder) WithDisableKeepAlive

func (b *ClientBuilder) WithDisableKeepAlive(disableKeepAlive bool) *ClientBuilder

WithDisableKeepAlive sets whether to disable keep-alive and returns the ClientBuilder for method chaining

func (*ClientBuilder) WithExpectContinueTimeout

func (b *ClientBuilder) WithExpectContinueTimeout(expectContinueTimeout time.Duration) *ClientBuilder

WithExpectContinueTimeout sets the expect continue timeout and returns the ClientBuilder for method chaining

func (*ClientBuilder) WithIdleConnTimeout

func (b *ClientBuilder) WithIdleConnTimeout(idleConnTimeout time.Duration) *ClientBuilder

WithIdleConnTimeout sets the idle connection timeout and returns the ClientBuilder for method chaining

func (*ClientBuilder) WithLogger

func (b *ClientBuilder) WithLogger(logger *slog.Logger) *ClientBuilder

WithLogger sets the logger for logging HTTP operations (retries, errors, etc.). Pass nil to disable logging (default behavior). and returns the ClientBuilder for method chaining

func (*ClientBuilder) WithMaxIdleConns

func (b *ClientBuilder) WithMaxIdleConns(maxIdleConns int) *ClientBuilder

WithMaxIdleConns sets the maximum number of idle connections and returns the ClientBuilder for method chaining

func (*ClientBuilder) WithMaxIdleConnsPerHost

func (b *ClientBuilder) WithMaxIdleConnsPerHost(maxIdleConnsPerHost int) *ClientBuilder

WithMaxIdleConnsPerHost sets the maximum number of idle connections per host and returns the ClientBuilder for method chaining

func (*ClientBuilder) WithMaxRetries

func (b *ClientBuilder) WithMaxRetries(maxRetries int) *ClientBuilder

WithMaxRetries sets the maximum number of retry attempts and returns the ClientBuilder for method chaining

func (*ClientBuilder) WithProxy added in v0.0.2

func (b *ClientBuilder) WithProxy(proxyURL string) *ClientBuilder

WithProxy sets the proxy URL for HTTP requests. The proxy URL should be in the format "http://proxy.example.com:8080" or "https://proxy.example.com:8080". Pass an empty string to disable proxy (default behavior). and returns the ClientBuilder for method chaining

Example

ExampleClientBuilder_WithProxy demonstrates how to configure an HTTP client with a proxy.

package main

import (
	"fmt"
	"time"

	"github.com/slashdevops/httpx"
)

func main() {
	// Create an HTTP client with proxy configuration
	client := httpx.NewClientBuilder().
		WithProxy("http://proxy.example.com:8080").
		WithTimeout(10 * time.Second).
		WithMaxRetries(3).
		WithRetryStrategy(httpx.ExponentialBackoffStrategy).
		Build()

	// Client is ready to use with proxy
	fmt.Printf("Client configured with proxy\n")
	_ = client

}
Output:

Client configured with proxy
Example (Authentication)

ExampleClientBuilder_WithProxy_authentication demonstrates using a proxy with authentication.

package main

import (
	"fmt"
	"time"

	"github.com/slashdevops/httpx"
)

func main() {
	// Create an HTTP client with authenticated proxy
	// Proxy credentials are included in the URL
	client := httpx.NewClientBuilder().
		WithProxy("http://username:[email protected]:8080").
		WithTimeout(10 * time.Second).
		Build()

	fmt.Printf("Client configured with authenticated proxy\n")
	_ = client

}
Output:

Client configured with authenticated proxy
Example (Disabling)

ExampleClientBuilder_WithProxy_disabling demonstrates how to disable proxy.

package main

import (
	"fmt"
	"time"

	"github.com/slashdevops/httpx"
)

func main() {
	// Create an HTTP client without proxy (explicit disable)
	client := httpx.NewClientBuilder().
		WithProxy(""). // Empty string disables proxy
		WithTimeout(10 * time.Second).
		Build()

	fmt.Printf("Client configured without proxy\n")
	_ = client

}
Output:

Client configured without proxy
Example (Https)

ExampleClientBuilder_WithProxy_https demonstrates using an HTTPS proxy.

package main

import (
	"fmt"
	"time"

	"github.com/slashdevops/httpx"
)

func main() {
	// Create an HTTP client with HTTPS proxy
	client := httpx.NewClientBuilder().
		WithProxy("https://secure-proxy.example.com:3128").
		WithTimeout(15 * time.Second).
		Build()

	fmt.Printf("Client configured with HTTPS proxy\n")
	_ = client

}
Output:

Client configured with HTTPS proxy

func (*ClientBuilder) WithRetryBaseDelay

func (b *ClientBuilder) WithRetryBaseDelay(baseDelay time.Duration) *ClientBuilder

WithRetryBaseDelay sets the base delay for retry strategies and returns the ClientBuilder for method chaining

func (*ClientBuilder) WithRetryMaxDelay

func (b *ClientBuilder) WithRetryMaxDelay(maxDelay time.Duration) *ClientBuilder

WithRetryMaxDelay sets the maximum delay for retry strategies and returns the ClientBuilder for method chaining

func (*ClientBuilder) WithRetryStrategy

func (b *ClientBuilder) WithRetryStrategy(strategy Strategy) *ClientBuilder

WithRetryStrategy sets the retry strategy type and returns the ClientBuilder for method chaining

func (*ClientBuilder) WithRetryStrategyAsString

func (b *ClientBuilder) WithRetryStrategyAsString(strategy string) *ClientBuilder

WithRetryStrategyAsString sets the retry strategy type from a string and returns the ClientBuilder for method chaining

func (*ClientBuilder) WithTLSHandshakeTimeout

func (b *ClientBuilder) WithTLSHandshakeTimeout(tlsHandshakeTimeout time.Duration) *ClientBuilder

WithTLSHandshakeTimeout sets the TLS handshake timeout and returns the ClientBuilder for method chaining

func (*ClientBuilder) WithTimeout

func (b *ClientBuilder) WithTimeout(timeout time.Duration) *ClientBuilder

WithTimeout sets the timeout for HTTP requests and returns the ClientBuilder for method chaining

type ClientError

type ClientError struct {
	Message string
}

ClientError represents an error that occurs during HTTP client operations

func (*ClientError) Error

func (e *ClientError) Error() string

type ErrorResponse

type ErrorResponse struct {
	Message    string `json:"message,omitempty"`
	ErrorMsg   string `json:"error,omitempty"`
	Details    string `json:"details,omitempty"`
	StatusCode int    `json:"statusCode,omitempty"`
}

ErrorResponse represents an error response from the API.

func (*ErrorResponse) Error

func (e *ErrorResponse) Error() string

Error implements the error interface for ErrorResponse. It returns a human-readable error message that includes the HTTP status code and any available error details from the API response.

type GenericClient

type GenericClient[T any] struct {
	// contains filtered or unexported fields
}

GenericClient is a generic HTTP client that can handle requests and responses with type safety. It wraps an HTTPClient and provides methods for executing typed HTTP requests.

Example (ErrorHandling)

ExampleGenericClient_errorHandling demonstrates error handling

package main

import (
	"fmt"
	"log"

	"github.com/slashdevops/httpx"
)

// User represents a user in the API
type User struct {
	ID    int    `json:"id"`
	Name  string `json:"name"`
	Email string `json:"email"`
}

func main() {
	client := httpx.NewGenericClient[User]()

	response, err := client.Get("https://api.example.com/users/999999")
	if err != nil {
		// Check if it's an ErrorResponse
		if apiErr, ok := err.(*httpx.ErrorResponse); ok {
			fmt.Printf("API Error: Status %d - %s\n", apiErr.StatusCode, apiErr.Message)
			return
		}
		// Other errors (network, parsing, etc.)
		log.Fatal(err)
	}

	fmt.Printf("User: %s\n", response.Data.Name)
}
Example (MultipleClients)

ExampleGenericClient_multipleClients demonstrates using multiple typed clients

package main

import (
	"fmt"
	"log"
	"time"

	"github.com/slashdevops/httpx"
)

// User represents a user in the API
type User struct {
	ID    int    `json:"id"`
	Name  string `json:"name"`
	Email string `json:"email"`
}

// Post represents a blog post
type Post struct {
	ID     int    `json:"id"`
	Title  string `json:"title"`
	Body   string `json:"body"`
	UserID int    `json:"userId"`
}

func main() {
	baseURL := "https://api.example.com"

	// Create a client for User responses with specific configuration
	userClient := httpx.NewGenericClient[User](
		httpx.WithTimeout[User](10*time.Second),
		httpx.WithMaxRetries[User](3),
	)

	// Create a client for Post responses with different configuration
	postClient := httpx.NewGenericClient[Post](
		httpx.WithTimeout[Post](15*time.Second),
		httpx.WithMaxRetries[Post](5),
		httpx.WithRetryStrategy[Post](httpx.JitterBackoffStrategy),
	)

	// Fetch user
	userResp, err := userClient.Get(baseURL + "/users/1")
	if err != nil {
		log.Fatal(err)
	}

	// Fetch posts by that user
	postResp, err := postClient.Get(fmt.Sprintf("%s/users/%d/posts", baseURL, userResp.Data.ID))
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("User %s has %d posts\n", userResp.Data.Name, len(postResp.Data.Title))
}
Example (ProductionConfiguration)

ExampleGenericClient_productionConfiguration demonstrates a production-ready configuration

package main

import (
	"fmt"
	"log"
	"time"

	"github.com/slashdevops/httpx"
)

// User represents a user in the API
type User struct {
	ID    int    `json:"id"`
	Name  string `json:"name"`
	Email string `json:"email"`
}

func main() {
	// Configure client with production-ready settings
	client := httpx.NewGenericClient[User](
		// Aggressive retry for resilience
		httpx.WithMaxRetries[User](5),
		httpx.WithRetryStrategy[User](httpx.ExponentialBackoffStrategy),
		httpx.WithRetryBaseDelay[User](500*time.Millisecond),
		httpx.WithRetryMaxDelay[User](30*time.Second),

		// Reasonable timeout
		httpx.WithTimeout[User](30*time.Second),

		// Connection pooling for performance
		httpx.WithMaxIdleConns[User](100),
		httpx.WithMaxIdleConnsPerHost[User](10),
		httpx.WithIdleConnTimeout[User](90*time.Second),

		// TLS optimization
		httpx.WithTLSHandshakeTimeout[User](10*time.Second),
	)

	// Build request with headers
	req, err := httpx.NewRequestBuilder("https://api.example.com").
		WithMethodGET().
		WithPath("/users/1").
		WithHeader("Authorization", "Bearer prod-token").
		WithHeader("X-Request-ID", "req-123").
		WithUserAgent("MyApp/1.0.0").
		Build()
	if err != nil {
		log.Fatal(err)
	}

	// Execute with automatic retries and error handling
	response, err := client.Execute(req)
	if err != nil {
		if apiErr, ok := err.(*httpx.ErrorResponse); ok {
			log.Printf("API Error: %d - %s", apiErr.StatusCode, apiErr.Message)
			return
		}
		log.Fatal(err)
	}

	fmt.Printf("User: %s (%s)\n", response.Data.Name, response.Data.Email)
}
Example (RetryStrategies)

ExampleGenericClient_retryStrategies demonstrates different retry strategies

package main

import (
	"time"

	"github.com/slashdevops/httpx"
)

// User represents a user in the API
type User struct {
	ID    int    `json:"id"`
	Name  string `json:"name"`
	Email string `json:"email"`
}

func main() {
	// Fixed delay - predictable retry timing
	fixedClient := httpx.NewGenericClient[User](
		httpx.WithRetryStrategyAsString[User]("fixed"),
		httpx.WithMaxRetries[User](3),
		httpx.WithRetryBaseDelay[User](1*time.Second),
	)

	// Exponential backoff - doubles delay each time
	exponentialClient := httpx.NewGenericClient[User](
		httpx.WithRetryStrategy[User](httpx.ExponentialBackoffStrategy),
		httpx.WithMaxRetries[User](5),
		httpx.WithRetryBaseDelay[User](500*time.Millisecond),
		httpx.WithRetryMaxDelay[User](10*time.Second),
	)

	// Jitter backoff - random delays to prevent thundering herd
	jitterClient := httpx.NewGenericClient[User](
		httpx.WithRetryStrategy[User](httpx.JitterBackoffStrategy),
		httpx.WithMaxRetries[User](3),
		httpx.WithRetryBaseDelay[User](500*time.Millisecond),
		httpx.WithRetryMaxDelay[User](5*time.Second),
	)

	// Use different clients based on use case
	_, _ = fixedClient.Get("https://api.example.com/users/1")
	_, _ = exponentialClient.Get("https://api.example.com/users/2")
	_, _ = jitterClient.Get("https://api.example.com/users/3")
}
Example (WithCustomHeaders)

ExampleGenericClient_withCustomHeaders demonstrates adding custom headers per request

package main

import (
	"fmt"
	"log"

	"github.com/slashdevops/httpx"
)

// User represents a user in the API
type User struct {
	ID    int    `json:"id"`
	Name  string `json:"name"`
	Email string `json:"email"`
}

func main() {
	client := httpx.NewGenericClient[User]()

	// Build request with headers using RequestBuilder
	req, err := httpx.NewRequestBuilder("https://api.example.com").
		WithMethodGET().
		WithPath("/users/1").
		WithHeader("Authorization", "Bearer token").
		WithHeader("X-Request-ID", "unique-id-123").
		WithHeader("X-Trace-ID", "trace-456").
		Build()
	if err != nil {
		log.Fatal(err)
	}

	// Execute the request
	response, err := client.Execute(req)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("User: %s\n", response.Data.Name)
}
Example (WithRetry)

ExampleGenericClient_withRetry demonstrates using generic client with retry logic

package main

import (
	"fmt"
	"log"
	"time"

	"github.com/slashdevops/httpx"
)

// User represents a user in the API
type User struct {
	ID    int    `json:"id"`
	Name  string `json:"name"`
	Email string `json:"email"`
}

func main() {
	// Option 1: Use ClientBuilder and pass to GenericClient
	retryClient := httpx.NewClientBuilder().
		WithMaxRetries(3).
		WithRetryStrategy(httpx.ExponentialBackoffStrategy).
		WithRetryBaseDelay(500 * time.Millisecond).
		WithTimeout(30 * time.Second).
		Build()

	client := httpx.NewGenericClient[User](
		httpx.WithHTTPClient[User](retryClient),
	)

	// Option 2: Configure retry directly in GenericClient
	client2 := httpx.NewGenericClient[User](
		httpx.WithMaxRetries[User](3),
		httpx.WithRetryStrategy[User](httpx.ExponentialBackoffStrategy),
		httpx.WithRetryBaseDelay[User](500*time.Millisecond),
		httpx.WithTimeout[User](30*time.Second),
	)

	// Make requests - they will automatically retry on failures
	response, err := client.Get("https://api.example.com/users/1")
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("User: %s\n", response.Data.Name)

	// Using second client
	response2, err := client2.Get("https://api.example.com/users/2")
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("User: %s\n", response2.Data.Name)
}

func NewGenericClient

func NewGenericClient[T any](options ...GenericClientOption[T]) *GenericClient[T]

NewGenericClient creates a new generic HTTP client with the specified type. By default, it builds an HTTP client using ClientBuilder with default settings. Use the provided options to customize the client behavior. If WithHTTPClient is used, it takes precedence over all other configuration options.

Example

ExampleNewGenericClient demonstrates basic usage of the generic client

package main

import (
	"fmt"
	"log"
	"time"

	"github.com/slashdevops/httpx"
)

// User represents a user in the API
type User struct {
	ID    int    `json:"id"`
	Name  string `json:"name"`
	Email string `json:"email"`
}

func main() {
	// Create a typed client for User responses with configuration
	client := httpx.NewGenericClient[User](
		httpx.WithTimeout[User](10*time.Second),
		httpx.WithMaxRetries[User](3),
		httpx.WithRetryStrategy[User](httpx.ExponentialBackoffStrategy),
	)

	// Make a GET request with full URL
	response, err := client.Get("https://api.example.com/users/1")
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("User: %s (%s)\n", response.Data.Name, response.Data.Email)
}
Example (AllOptions)

ExampleNewGenericClient_allOptions demonstrates using all configuration options

package main

import (
	"fmt"
	"log"
	"time"

	"github.com/slashdevops/httpx"
)

// User represents a user in the API
type User struct {
	ID    int    `json:"id"`
	Name  string `json:"name"`
	Email string `json:"email"`
}

func main() {
	// Create a fully configured client
	client := httpx.NewGenericClient[User](
		// Timeout configuration
		httpx.WithTimeout[User](15*time.Second),

		// Retry configuration
		httpx.WithMaxRetries[User](5),
		httpx.WithRetryStrategy[User](httpx.JitterBackoffStrategy),
		httpx.WithRetryBaseDelay[User](500*time.Millisecond),
		httpx.WithRetryMaxDelay[User](10*time.Second),

		// Connection pooling
		httpx.WithMaxIdleConns[User](100),
		httpx.WithMaxIdleConnsPerHost[User](10),
		httpx.WithIdleConnTimeout[User](90*time.Second),

		// TLS and handshake timeouts
		httpx.WithTLSHandshakeTimeout[User](10*time.Second),
		httpx.WithExpectContinueTimeout[User](1*time.Second),

		// Keep-alive
		httpx.WithDisableKeepAlive[User](false),
	)

	response, err := client.Get("https://api.example.com/users/1")
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("User: %s\n", response.Data.Name)
}
Example (WithPreConfiguredClient)

ExampleNewGenericClient_withPreConfiguredClient demonstrates using WithHTTPClient

package main

import (
	"fmt"
	"log"
	"time"

	"github.com/slashdevops/httpx"
)

// User represents a user in the API
type User struct {
	ID    int    `json:"id"`
	Name  string `json:"name"`
	Email string `json:"email"`
}

func main() {
	// Build a custom HTTP client using ClientBuilder
	httpClient := httpx.NewClientBuilder().
		WithMaxRetries(3).
		WithRetryStrategy(httpx.ExponentialBackoffStrategy).
		WithRetryBaseDelay(500 * time.Millisecond).
		WithTimeout(30 * time.Second).
		WithMaxIdleConns(50).
		Build()

	// Use it with GenericClient (takes precedence over other options)
	client := httpx.NewGenericClient[User](
		httpx.WithHTTPClient[User](httpClient),
	)

	response, err := client.Get("https://api.example.com/users/1")
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("User: %s\n", response.Data.Name)
}
Example (WithProxy)

ExampleNewGenericClient_withProxy demonstrates using a proxy with the generic client.

package main

import (
	"fmt"
	"time"

	"github.com/slashdevops/httpx"
)

func main() {
	type APIResponse struct {
		Message string `json:"message"`
		Status  string `json:"status"`
	}

	// Create a generic client with proxy configuration
	client := httpx.NewGenericClient[APIResponse](
		httpx.WithProxy[APIResponse]("http://proxy.example.com:8080"),
		httpx.WithTimeout[APIResponse](10*time.Second),
		httpx.WithMaxRetries[APIResponse](3),
	)

	fmt.Printf("Generic client configured with proxy\n")
	_ = client

}
Output:

Generic client configured with proxy
Example (WithProxy_combined)

ExampleNewGenericClient_withProxy_combined demonstrates combining proxy with other options.

package main

import (
	"fmt"
	"time"

	"github.com/slashdevops/httpx"
)

func main() {
	type User struct {
		ID    int    `json:"id"`
		Name  string `json:"name"`
		Email string `json:"email"`
	}

	// Create a fully configured generic client with proxy
	client := httpx.NewGenericClient[User](
		httpx.WithProxy[User]("http://proxy.example.com:8080"),
		httpx.WithTimeout[User](15*time.Second),
		httpx.WithMaxRetries[User](5),
		httpx.WithRetryStrategy[User](httpx.JitterBackoffStrategy),
		httpx.WithRetryBaseDelay[User](500*time.Millisecond),
		httpx.WithRetryMaxDelay[User](30*time.Second),
	)

	fmt.Printf("Generic client with proxy and retry configuration\n")
	_ = client

}
Output:

Generic client with proxy and retry configuration
Example (WithProxy_portVariations)

ExampleNewGenericClient_withProxy_portVariations demonstrates different proxy port configurations.

package main

import (
	"fmt"

	"github.com/slashdevops/httpx"
)

func main() {
	type Data struct {
		Value string `json:"value"`
	}

	// Example 1: Standard HTTP proxy on port 8080
	client1 := httpx.NewGenericClient[Data](
		httpx.WithProxy[Data]("http://proxy.example.com:8080"),
	)

	// Example 2: HTTPS proxy on port 3128 (common Squid port)
	client2 := httpx.NewGenericClient[Data](
		httpx.WithProxy[Data]("https://proxy.example.com:3128"),
	)

	// Example 3: Custom port
	client3 := httpx.NewGenericClient[Data](
		httpx.WithProxy[Data]("http://proxy.example.com:9090"),
	)

	fmt.Printf("Configured clients with different proxy ports\n")
	_, _, _ = client1, client2, client3

}
Output:

Configured clients with different proxy ports

func (*GenericClient[T]) Delete

func (c *GenericClient[T]) Delete(url string) (*Response[T], error)

Delete performs a DELETE request and returns a typed response.

func (*GenericClient[T]) Do

func (c *GenericClient[T]) Do(req *http.Request) (*Response[T], error)

Do performs an HTTP request and returns a typed response. This method is designed to work seamlessly with the RequestBuilder. It's an alias for Execute but with a more familiar name for those used to http.Client.Do().

func (*GenericClient[T]) Execute

func (c *GenericClient[T]) Execute(req *http.Request) (*Response[T], error)

Execute performs an HTTP request and returns a typed response. It executes the request, reads the response body, and unmarshals the JSON response into the generic type T. Returns an error if the HTTP status code is >= 400.

Example

ExampleGenericClient_Execute demonstrates using Execute with RequestBuilder

package main

import (
	"fmt"
	"log"

	"github.com/slashdevops/httpx"
)

// Post represents a blog post
type Post struct {
	ID     int    `json:"id"`
	Title  string `json:"title"`
	Body   string `json:"body"`
	UserID int    `json:"userId"`
}

func main() {
	// Create a typed client for Post responses
	client := httpx.NewGenericClient[Post]()

	// Build a request with RequestBuilder
	req, err := httpx.NewRequestBuilder("https://jsonplaceholder.typicode.com").
		WithMethodGET().
		WithPath("/posts/1").
		WithAccept("application/json").
		Build()
	if err != nil {
		log.Fatal(err)
	}

	// Execute the request
	response, err := client.Execute(req)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("Post: %s\n", response.Data.Title)
}

func (*GenericClient[T]) ExecuteRaw

func (c *GenericClient[T]) ExecuteRaw(req *http.Request) (*http.Response, error)

ExecuteRaw performs an HTTP request and returns the raw response without unmarshaling. This is useful when you need direct access to the http.Response, such as for streaming or when the response is not JSON.

Example

ExampleGenericClient_ExecuteRaw demonstrates using ExecuteRaw for non-JSON responses

package main

import (
	"fmt"
	"log"
	"net/http"

	"github.com/slashdevops/httpx"
)

// User represents a user in the API
type User struct {
	ID    int    `json:"id"`
	Name  string `json:"name"`
	Email string `json:"email"`
}

func main() {
	client := httpx.NewGenericClient[User]()

	req, err := http.NewRequest(http.MethodGet, "https://example.com/image.png", nil)
	if err != nil {
		log.Fatal(err)
	}

	// Get raw response for binary data
	response, err := client.ExecuteRaw(req)
	if err != nil {
		log.Fatal(err)
	}
	defer response.Body.Close()

	fmt.Printf("Content-Type: %s\n", response.Header.Get("Content-Type"))
	fmt.Printf("Status: %d\n", response.StatusCode)
}

func (*GenericClient[T]) Get

func (c *GenericClient[T]) Get(url string) (*Response[T], error)

Get performs a GET request to the specified URL and returns a typed response.

func (*GenericClient[T]) Patch

func (c *GenericClient[T]) Patch(url string, body io.Reader) (*Response[T], error)

Patch performs a PATCH request with the specified body and returns a typed response.

func (*GenericClient[T]) Post

func (c *GenericClient[T]) Post(url string, body io.Reader) (*Response[T], error)

Post performs a POST request with the specified body and returns a typed response.

Example

ExampleGenericClient_Post demonstrates making a POST request

package main

import (
	"fmt"
	"log"

	"github.com/slashdevops/httpx"
)

// Post represents a blog post
type Post struct {
	ID     int    `json:"id"`
	Title  string `json:"title"`
	Body   string `json:"body"`
	UserID int    `json:"userId"`
}

func main() {
	client := httpx.NewGenericClient[Post]()

	// Create a new post using RequestBuilder
	newPost := Post{
		Title:  "My New Post",
		Body:   "This is the post content",
		UserID: 1,
	}

	req, err := httpx.NewRequestBuilder("https://jsonplaceholder.typicode.com").
		WithMethodPOST().
		WithPath("/posts").
		WithContentType("application/json").
		WithJSONBody(newPost).
		Build()
	if err != nil {
		log.Fatal(err)
	}

	response, err := client.Do(req)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("Created post with ID: %d\n", response.Data.ID)
}

func (*GenericClient[T]) Put

func (c *GenericClient[T]) Put(url string, body io.Reader) (*Response[T], error)

Put performs a PUT request with the specified body and returns a typed response.

type GenericClientOption

type GenericClientOption[T any] func(*GenericClient[T])

GenericClientOption is a function type for configuring the GenericClient.

func WithDisableKeepAlive

func WithDisableKeepAlive[T any](disableKeepAlive bool) GenericClientOption[T]

WithDisableKeepAlive sets whether to disable keep-alive.

func WithExpectContinueTimeout

func WithExpectContinueTimeout[T any](expectContinueTimeout time.Duration) GenericClientOption[T]

WithExpectContinueTimeout sets the expect continue timeout. Uses ClientBuilder validation and defaults if the value is out of range.

func WithHTTPClient

func WithHTTPClient[T any](httpClient HTTPClient) GenericClientOption[T]

WithHTTPClient configures the generic client to use a custom HTTPClient implementation. If httpClient is nil, the option is ignored and the client retains its current HTTPClient. This option takes precedence over all other configuration options. This is useful for using a pre-configured retry client or custom transport.

func WithIdleConnTimeout

func WithIdleConnTimeout[T any](idleConnTimeout time.Duration) GenericClientOption[T]

WithIdleConnTimeout sets the idle connection timeout. Uses ClientBuilder validation and defaults if the value is out of range.

func WithLogger

func WithLogger[T any](logger *slog.Logger) GenericClientOption[T]

WithLogger sets the logger for logging HTTP operations (retries, errors, etc.). Pass nil to disable logging (default behavior).

func WithMaxIdleConns

func WithMaxIdleConns[T any](maxIdleConns int) GenericClientOption[T]

WithMaxIdleConns sets the maximum number of idle connections. Uses ClientBuilder validation and defaults if the value is out of range.

func WithMaxIdleConnsPerHost

func WithMaxIdleConnsPerHost[T any](maxIdleConnsPerHost int) GenericClientOption[T]

WithMaxIdleConnsPerHost sets the maximum number of idle connections per host. Uses ClientBuilder validation and defaults if the value is out of range.

func WithMaxRetries

func WithMaxRetries[T any](maxRetries int) GenericClientOption[T]

WithMaxRetries sets the maximum number of retry attempts. Uses ClientBuilder validation and defaults if the value is out of range.

func WithProxy added in v0.0.2

func WithProxy[T any](proxyURL string) GenericClientOption[T]

WithProxy sets the proxy URL for HTTP requests. The proxy URL should be in the format "http://proxy.example.com:8080" or "https://proxy.example.com:8080". Pass an empty string to disable proxy (default behavior).

func WithRetryBaseDelay

func WithRetryBaseDelay[T any](baseDelay time.Duration) GenericClientOption[T]

WithRetryBaseDelay sets the base delay for retry strategies. Uses ClientBuilder validation and defaults if the value is out of range.

func WithRetryMaxDelay

func WithRetryMaxDelay[T any](maxDelay time.Duration) GenericClientOption[T]

WithRetryMaxDelay sets the maximum delay for retry strategies. Uses ClientBuilder validation and defaults if the value is out of range.

func WithRetryStrategy

func WithRetryStrategy[T any](strategy Strategy) GenericClientOption[T]

WithRetryStrategy sets the retry strategy type (fixed, jitter, or exponential). Uses ClientBuilder validation and defaults if the value is invalid.

func WithRetryStrategyAsString

func WithRetryStrategyAsString[T any](strategy string) GenericClientOption[T]

WithRetryStrategyAsString sets the retry strategy type from a string. Valid values: "fixed", "jitter", "exponential". Uses ClientBuilder validation and defaults if the value is invalid.

func WithTLSHandshakeTimeout

func WithTLSHandshakeTimeout[T any](tlsHandshakeTimeout time.Duration) GenericClientOption[T]

WithTLSHandshakeTimeout sets the TLS handshake timeout. Uses ClientBuilder validation and defaults if the value is out of range.

func WithTimeout

func WithTimeout[T any](timeout time.Duration) GenericClientOption[T]

WithTimeout sets the request timeout for the generic client. Uses ClientBuilder validation and defaults if the value is out of range.

type HTTPClient

type HTTPClient interface {
	Do(req *http.Request) (*http.Response, error)
}

HTTPClient is an interface that defines the methods required for making HTTP requests. This allows for easier testing and mocking of HTTP requests in unit tests.

type RequestBuilder

type RequestBuilder struct {
	// contains filtered or unexported fields
}

RequestBuilder provides a fluent API for building HTTP requests with and without body.

Example (ComplexRequest)

ExampleRequestBuilder_complexRequest demonstrates a complex request with multiple options.

package main

import (
	"fmt"

	"github.com/slashdevops/httpx"
)

func main() {
	type RequestData struct {
		Action string `json:"action"`
		Count  int    `json:"count"`
	}

	data := RequestData{Action: "update", Count: 5}

	req, err := httpx.NewRequestBuilder("https://api.example.com").
		WithMethodPUT().
		WithPath("/resources/123").
		WithQueryParam("force", "true").
		WithHeader("X-Custom-Header", "custom-value").
		WithUserAgent("MyApp/1.0").
		WithBearerAuth("token123").
		WithJSONBody(data).
		Build()
	if err != nil {
		fmt.Println("Error:", err)
		return
	}

	fmt.Println(req.Method)
	fmt.Println(req.URL.Path)
	fmt.Println(req.Header.Get("User-Agent"))
	fmt.Println(req.Header.Get("X-Custom-Header"))
	fmt.Println(req.Header.Get("Content-Type"))

}
Output:

PUT
/resources/123
MyApp/1.0
custom-value
application/json
Example (FullExample)

ExampleRequestBuilder_fullExample demonstrates a complete end-to-end example with a test server.

package main

import (
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"

	"github.com/slashdevops/httpx"
)

func main() {
	// Create a test server
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(http.StatusOK)
		_, _ = w.Write([]byte(`{"message":"success"}`))
	}))
	defer server.Close()

	req, err := httpx.NewRequestBuilder(server.URL).
		WithMethodGET().
		WithPath("/api/test").
		WithHeader("Accept", "application/json").
		Build()
	if err != nil {
		fmt.Println("Error:", err)
		return
	}

	// Execute the request
	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		fmt.Println("Error:", err)
		return
	}
	defer resp.Body.Close()

	// Read the response
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		fmt.Println("Error:", err)
		return
	}

	fmt.Println(resp.StatusCode)
	fmt.Println(string(body))

}
Output:

200
{"message":"success"}
Example (PostWithJSON)

ExampleRequestBuilder_postWithJSON demonstrates how to create a POST request with JSON body.

package main

import (
	"fmt"

	"github.com/slashdevops/httpx"
)

func main() {
	type User struct {
		Name  string `json:"name"`
		Email string `json:"email"`
	}

	user := User{Name: "John Doe", Email: "[email protected]"}

	req, err := httpx.NewRequestBuilder("https://api.example.com").
		WithMethodPOST().
		WithPath("/users").
		WithJSONBody(user).
		Build()
	if err != nil {
		fmt.Println("Error:", err)
		return
	}

	fmt.Println(req.Method)
	fmt.Println(req.Header.Get("Content-Type"))

}
Output:

POST
application/json
Example (SimpleGET)

ExampleRequestBuilder_simpleGET demonstrates how to create a simple GET request.

package main

import (
	"fmt"

	"github.com/slashdevops/httpx"
)

func main() {
	req, err := httpx.NewRequestBuilder("https://api.example.com").
		WithMethodGET().
		WithPath("/users").
		Build()
	if err != nil {
		fmt.Println("Error:", err)
		return
	}

	fmt.Println(req.Method)
	fmt.Println(req.URL.String())

}
Output:

GET
https://api.example.com/users
Example (WithAcceptHeader)

ExampleRequestBuilder_withAcceptHeader demonstrates how to set the Accept header.

package main

import (
	"fmt"

	"github.com/slashdevops/httpx"
)

func main() {
	// Using the WithAccept() convenience method
	req, err := httpx.NewRequestBuilder("https://api.example.com").
		WithMethodGET().
		WithPath("/api/data").
		WithAccept("application/json").
		Build()
	if err != nil {
		fmt.Println("Error:", err)
		return
	}

	fmt.Println(req.Header.Get("Accept"))

}
Output:

application/json
Example (WithBasicAuth)

ExampleRequestBuilder_withBasicAuth demonstrates how to use basic authentication.

package main

import (
	"fmt"

	"github.com/slashdevops/httpx"
)

func main() {
	req, err := httpx.NewRequestBuilder("https://api.example.com").
		WithMethodGET().
		WithPath("/protected").
		WithBasicAuth("username", "password").
		Build()
	if err != nil {
		fmt.Println("Error:", err)
		return
	}

	authHeader := req.Header.Get("Authorization")
	fmt.Println(authHeader[:6]) // Just print "Basic " prefix

}
Output:

Basic
Example (WithBearerAuth)

ExampleRequestBuilder_withBearerAuth demonstrates how to use bearer token authentication.

package main

import (
	"fmt"

	"github.com/slashdevops/httpx"
)

func main() {
	req, err := httpx.NewRequestBuilder("https://api.example.com").
		WithMethodGET().
		WithPath("/api/data").
		WithBearerAuth("your-token-here").
		Build()
	if err != nil {
		fmt.Println("Error:", err)
		return
	}

	authHeader := req.Header.Get("Authorization")
	fmt.Println(authHeader[:7]) // Just print "Bearer " prefix

}
Output:

Bearer
Example (WithContext)

ExampleRequestBuilder_withContext demonstrates how to use a context.

package main

import (
	"context"
	"fmt"

	"github.com/slashdevops/httpx"
)

func main() {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	req, err := httpx.NewRequestBuilder("https://api.example.com").
		WithMethodGET().
		WithPath("/data").
		WithContext(ctx).
		Build()
	if err != nil {
		fmt.Println("Error:", err)
		return
	}

	fmt.Println(req.Context() != nil)

}
Output:

true
Example (WithMethodCONNECT)

ExampleRequestBuilder_withMethodCONNECT demonstrates how to create a CONNECT request.

package main

import (
	"fmt"

	"github.com/slashdevops/httpx"
)

func main() {
	req, err := httpx.NewRequestBuilder("https://proxy.example.com").
		WithMethodCONNECT().
		WithPath("/tunnel").
		Build()
	if err != nil {
		fmt.Println("Error:", err)
		return
	}

	fmt.Println(req.Method)
	fmt.Println(req.URL.Path)

}
Output:

CONNECT
/tunnel
Example (WithMethodHEAD)

ExampleRequestBuilder_withMethodHEAD demonstrates how to create a HEAD request.

package main

import (
	"fmt"

	"github.com/slashdevops/httpx"
)

func main() {
	req, err := httpx.NewRequestBuilder("https://api.example.com").
		WithMethodHEAD().
		WithPath("/resource").
		Build()
	if err != nil {
		fmt.Println("Error:", err)
		return
	}

	fmt.Println(req.Method)
	fmt.Println(req.URL.Path)

}
Output:

HEAD
/resource
Example (WithMethodOPTIONS)

ExampleRequestBuilder_withMethodOPTIONS demonstrates how to create an OPTIONS request.

package main

import (
	"fmt"

	"github.com/slashdevops/httpx"
)

func main() {
	req, err := httpx.NewRequestBuilder("https://api.example.com").
		WithMethodOPTIONS().
		WithPath("/api/users").
		Build()
	if err != nil {
		fmt.Println("Error:", err)
		return
	}

	fmt.Println(req.Method)
	fmt.Println(req.URL.Path)

}
Output:

OPTIONS
/api/users
Example (WithMethodTRACE)

ExampleRequestBuilder_withMethodTRACE demonstrates how to create a TRACE request.

package main

import (
	"fmt"

	"github.com/slashdevops/httpx"
)

func main() {
	req, err := httpx.NewRequestBuilder("https://api.example.com").
		WithMethodTRACE().
		WithPath("/debug").
		Build()
	if err != nil {
		fmt.Println("Error:", err)
		return
	}

	fmt.Println(req.Method)
	fmt.Println(req.URL.Path)

}
Output:

TRACE
/debug
Example (WithMultipleAcceptTypes)

ExampleRequestBuilder_withMultipleAcceptTypes demonstrates setting multiple Accept types with quality values.

package main

import (
	"fmt"

	"github.com/slashdevops/httpx"
)

func main() {
	req, err := httpx.NewRequestBuilder("https://api.example.com").
		WithMethodGET().
		WithPath("/content").
		WithAccept("application/json, application/xml;q=0.9, */*;q=0.8").
		Build()
	if err != nil {
		fmt.Println("Error:", err)
		return
	}

	fmt.Println(req.Header.Get("Accept"))

}
Output:

application/json, application/xml;q=0.9, */*;q=0.8
Example (WithQueryParams)

ExampleRequestBuilder_withQueryParams demonstrates how to add query parameters.

package main

import (
	"fmt"

	"github.com/slashdevops/httpx"
)

func main() {
	req, err := httpx.NewRequestBuilder("https://api.example.com").
		WithMethodGET().
		WithPath("/search").
		WithQueryParam("q", "golang").
		WithQueryParam("limit", "10").
		WithQueryParam("offset", "0").
		Build()
	if err != nil {
		fmt.Println("Error:", err)
		return
	}

	fmt.Println(req.URL.String())

}
Output:

https://api.example.com/search?limit=10&offset=0&q=golang

func NewRequestBuilder

func NewRequestBuilder(baseURL string) *RequestBuilder

NewRequestBuilder creates a new RequestBuilder with the specified base URL.

func (*RequestBuilder) Build

func (rb *RequestBuilder) Build() (*http.Request, error)

Build creates an *http.Request from the builder configuration. Returns an error if any validation fails.

func (*RequestBuilder) GetErrors

func (rb *RequestBuilder) GetErrors() []error

GetErrors returns all accumulated errors during the building process.

func (*RequestBuilder) HasErrors

func (rb *RequestBuilder) HasErrors() bool

HasErrors returns true if there are any accumulated errors.

func (*RequestBuilder) Reset

func (rb *RequestBuilder) Reset() *RequestBuilder

Reset clears all errors and resets the builder to a clean state.

func (*RequestBuilder) WithAccept

func (rb *RequestBuilder) WithAccept(accept string) *RequestBuilder

WithAccept sets the Accept header.

func (*RequestBuilder) WithBasicAuth

func (rb *RequestBuilder) WithBasicAuth(username, password string) *RequestBuilder

WithBasicAuth sets the Authorization header for basic authentication.

func (*RequestBuilder) WithBearerAuth

func (rb *RequestBuilder) WithBearerAuth(token string) *RequestBuilder

WithBearerAuth sets the Authorization header for bearer token authentication.

func (*RequestBuilder) WithBytesBody

func (rb *RequestBuilder) WithBytesBody(body []byte) *RequestBuilder

WithBytesBody sets the request body from a byte slice.

func (*RequestBuilder) WithContentType

func (rb *RequestBuilder) WithContentType(contentType string) *RequestBuilder

WithContentType sets the Content-Type header.

func (*RequestBuilder) WithContext

func (rb *RequestBuilder) WithContext(ctx context.Context) *RequestBuilder

WithContext sets the context for the request.

func (*RequestBuilder) WithHeader

func (rb *RequestBuilder) WithHeader(key, value string) *RequestBuilder

WithHeader sets a single header.

func (*RequestBuilder) WithHeaders

func (rb *RequestBuilder) WithHeaders(headers map[string]string) *RequestBuilder

WithHeaders sets multiple headers from a map.

func (*RequestBuilder) WithJSONBody

func (rb *RequestBuilder) WithJSONBody(body any) *RequestBuilder

WithJSONBody sets the request body as JSON and sets the appropriate Content-Type header.

func (*RequestBuilder) WithMethod

func (rb *RequestBuilder) WithMethod(method string) *RequestBuilder

WithMethod sets the HTTP method to the specified method. The method is normalized to uppercase and validated against standard HTTP methods.

func (*RequestBuilder) WithMethodCONNECT

func (rb *RequestBuilder) WithMethodCONNECT() *RequestBuilder

WithMethodCONNECT sets the HTTP method to CONNECT.

func (*RequestBuilder) WithMethodDELETE

func (rb *RequestBuilder) WithMethodDELETE() *RequestBuilder

WithMethodDELETE sets the HTTP method to DELETE.

func (*RequestBuilder) WithMethodGET

func (rb *RequestBuilder) WithMethodGET() *RequestBuilder

WithMethodGET sets the HTTP method to GET.

func (*RequestBuilder) WithMethodHEAD

func (rb *RequestBuilder) WithMethodHEAD() *RequestBuilder

WithMethodHEAD sets the HTTP method to HEAD.

func (*RequestBuilder) WithMethodOPTIONS

func (rb *RequestBuilder) WithMethodOPTIONS() *RequestBuilder

WithMethodOPTIONS sets the HTTP method to OPTIONS.

func (*RequestBuilder) WithMethodPATCH

func (rb *RequestBuilder) WithMethodPATCH() *RequestBuilder

WithMethodPATCH sets the HTTP method to PATCH.

func (*RequestBuilder) WithMethodPOST

func (rb *RequestBuilder) WithMethodPOST() *RequestBuilder

WithMethodPOST sets the HTTP method to POST.

func (*RequestBuilder) WithMethodPUT

func (rb *RequestBuilder) WithMethodPUT() *RequestBuilder

WithMethodPUT sets the HTTP method to PUT.

func (*RequestBuilder) WithMethodTRACE

func (rb *RequestBuilder) WithMethodTRACE() *RequestBuilder

WithMethodTRACE sets the HTTP method to TRACE.

func (*RequestBuilder) WithPath

func (rb *RequestBuilder) WithPath(path string) *RequestBuilder

WithPath sets the path component of the URL.

func (*RequestBuilder) WithQueryParam

func (rb *RequestBuilder) WithQueryParam(key, value string) *RequestBuilder

WithQueryParam adds a single query parameter.

func (*RequestBuilder) WithQueryParams

func (rb *RequestBuilder) WithQueryParams(params map[string]string) *RequestBuilder

WithQueryParams adds multiple query parameters from a map.

func (*RequestBuilder) WithRawBody

func (rb *RequestBuilder) WithRawBody(body io.Reader) *RequestBuilder

WithRawBody sets the request body from an io.Reader.

func (*RequestBuilder) WithStringBody

func (rb *RequestBuilder) WithStringBody(body string) *RequestBuilder

WithStringBody sets the request body from a string.

func (*RequestBuilder) WithUserAgent

func (rb *RequestBuilder) WithUserAgent(userAgent string) *RequestBuilder

WithUserAgent sets the User-Agent header. The user agent is trimmed and validated to ensure it: - is non-empty after trimming - does not exceed 500 characters - does not contain control characters (\r, \n, \t)

type Response

type Response[T any] struct {
	Data       T
	Headers    http.Header
	RawBody    []byte
	StatusCode int
}

Response represents the response from an HTTP request with generic type support.

type RetryClientOption

type RetryClientOption func(*retryClientConfig)

RetryClientOption is a function type for configuring the retry HTTP client.

func WithBaseTransport

func WithBaseTransport(transport http.RoundTripper) RetryClientOption

WithBaseTransport sets the base HTTP transport for the retry client. If not provided, http.DefaultTransport will be used.

func WithLoggerRetry

func WithLoggerRetry(logger *slog.Logger) RetryClientOption

WithLoggerRetry sets the logger for the retry client. Pass nil to disable logging (default behavior).

func WithMaxRetriesRetry

func WithMaxRetriesRetry(maxRetries int) RetryClientOption

WithMaxRetriesRetry sets the maximum number of retry attempts for the retry client.

func WithProxyRetry added in v0.0.2

func WithProxyRetry(proxyURL string) RetryClientOption

WithProxyRetry sets the proxy URL for the retry client. The proxy URL should be in the format "http://proxy.example.com:8080" or "https://proxy.example.com:8080". Pass an empty string to disable proxy (default behavior).

func WithRetryStrategyRetry

func WithRetryStrategyRetry(strategy RetryStrategy) RetryClientOption

WithRetryStrategyRetry sets the retry strategy for the retry client.

type RetryStrategy

type RetryStrategy func(attempt int) time.Duration

RetryStrategy defines the function signature for different retry strategies

func ExponentialBackoff

func ExponentialBackoff(base, maxDelay time.Duration) RetryStrategy

ExponentialBackoff returns a RetryStrategy that calculates delays growing exponentially with each retry attempt, starting from base and capped at maxDelay.

func FixedDelay

func FixedDelay(delay time.Duration) RetryStrategy

FixedDelay returns a RetryStrategy that provides a constant delay for each retry attempt.

func JitterBackoff

func JitterBackoff(base, maxDelay time.Duration) RetryStrategy

JitterBackoff returns a RetryStrategy that adds a random jitter to the exponential backoff delay calculated using base and maxDelay.

type Strategy

type Strategy string

Strategy defines the type for retry strategies It is a string type to allow for easy conversion from string literals to the defined types

const (
	// FixedDelayStrategy represents a fixed delay retry strategy
	// This strategy waits for a constant amount of time between retries
	// regardless of the number of attempts made
	FixedDelayStrategy Strategy = "fixed"

	// JitterBackoffStrategy represents a jitter backoff retry strategy
	// This strategy adds randomness to the backoff delay to prevent
	// synchronized retries across multiple clients
	JitterBackoffStrategy Strategy = "jitter"

	// ExponentialBackoffStrategy represents an exponential backoff retry strategy
	// This strategy increases the delay exponentially with each retry attempt,
	// up to a maximum delay
	ExponentialBackoffStrategy Strategy = "exponential"
)

func (Strategy) IsValid

func (s Strategy) IsValid() bool

func (Strategy) String

func (s Strategy) String() string

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL