httpclient

package
v0.7.4 Latest Latest
Warning

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

Go to latest
Published: Jun 13, 2025 License: Apache-2.0 Imports: 10 Imported by: 0

README

ajan/httpclient

Overview

httpclient is a resilient HTTP client that is 100% compatible with the standard net/http interfaces while providing additional features for improved reliability and fault tolerance.

Key Features

  • Drop-in replacement for net/http.Client
  • Circuit breaker pattern implementation
  • Exponential backoff retry mechanism with jitter
  • Context-aware request handling
  • Configurable failure thresholds and timeouts
  • Support for HTTP request body retries (when GetBody is implemented)

Usage

The circuit breaker and retry strategy can be configured in four independent modes:

1. Both Enabled (Default)
client := httpclient.NewClient(
    httpclient.WithConfig(&httpclient.Config{
        CircuitBreaker: httpclient.CircuitBreakerConfig{
            Enabled:               true,
            FailureThreshold:      5,
            ResetTimeout:          10 * time.Second,
            HalfOpenSuccessNeeded: 2,
        },
        RetryStrategy: httpclient.RetryStrategyConfig{
            Enabled:         true,
            MaxAttempts:     3,
            InitialInterval: 100 * time.Millisecond,
            MaxInterval:     10 * time.Second,
            Multiplier:      2.0,
            RandomFactor:    0.1,
        },
        ServerErrorThreshold: 500,
    }),
)

Behavior: Requests are retried up to MaxAttempts times. If failures reach FailureThreshold, the circuit breaker opens and subsequent requests fail immediately with ErrCircuitOpen.

2. Circuit Breaker Only (Retry Disabled)
client := httpclient.NewClient(
    httpclient.WithConfig(&httpclient.Config{
        CircuitBreaker: httpclient.CircuitBreakerConfig{
            Enabled:               true,
            FailureThreshold:      3,
            ResetTimeout:          1 * time.Second,
            HalfOpenSuccessNeeded: 1,
        },
        RetryStrategy: httpclient.RetryStrategyConfig{
            Enabled: false, // Retry disabled
        },
        ServerErrorThreshold: 500,
    }),
)

Behavior: No retries are performed. After FailureThreshold failures, the circuit breaker opens. Server error responses (5xx) are returned directly until the circuit opens.

3. Retry Only (Circuit Breaker Disabled)
client := httpclient.NewClient(
    httpclient.WithConfig(&httpclient.Config{
        CircuitBreaker: httpclient.CircuitBreakerConfig{
            Enabled: false, // Circuit breaker disabled
        },
        RetryStrategy: httpclient.RetryStrategyConfig{
            Enabled:         true,
            MaxAttempts:     3,
            InitialInterval: time.Millisecond,
            MaxInterval:     time.Second,
            Multiplier:      1.0,
            RandomFactor:    0,
        },
        ServerErrorThreshold: 500,
    }),
)

Behavior: Requests are retried up to MaxAttempts times with exponential backoff. No circuit breaking occurs - retries continue regardless of failure patterns.

4. Neither Enabled (Basic HTTP Client)
client := httpclient.NewClient(
    httpclient.WithConfig(&httpclient.Config{
        CircuitBreaker: httpclient.CircuitBreakerConfig{
            Enabled: false, // Circuit breaker disabled
        },
        RetryStrategy: httpclient.RetryStrategyConfig{
            Enabled: false, // Retry disabled
        },
        ServerErrorThreshold: 500,
    }),
)

Behavior: Behaves like a standard HTTP client. Requests are made once with no retries or circuit breaking. Server errors are returned directly.

Error Types

The client returns specific errors based on the failure mode:

  • ErrCircuitOpen: Circuit breaker is open (circuit breaker feature)
  • ErrMaxRetries: Retry attempts exhausted (retry feature)
  • ErrAllRetryAttemptsFailed: All retry attempts failed with transport errors
  • ErrTransportError: Underlying transport failure
  • ErrRequestBodyNotRetriable: Request body cannot be retried

Usage Examples

Example 1: High-Availability Service (Both Enabled)
// For critical services that need both retry resilience and circuit breaking
client := httpclient.NewClient(
    httpclient.WithConfig(&httpclient.Config{
        CircuitBreaker: httpclient.CircuitBreakerConfig{
            Enabled:               true,
            FailureThreshold:      5,  // Open after 5 consecutive failures
            ResetTimeout:          30 * time.Second,
            HalfOpenSuccessNeeded: 3,  // Need 3 successes to close
        },
        RetryStrategy: httpclient.RetryStrategyConfig{
            Enabled:         true,
            MaxAttempts:     3,        // Retry up to 3 times
            InitialInterval: 200 * time.Millisecond,
            MaxInterval:     5 * time.Second,
            Multiplier:      2.0,
            RandomFactor:    0.1,
        },
        ServerErrorThreshold: 500,
    }),
)

resp, err := client.Get("https://critical-service.com/api")
if errors.Is(err, httpclient.ErrCircuitOpen) {
    // Circuit breaker is open - service is likely down
    log.Warn("Circuit breaker open for critical service")
} else if errors.Is(err, httpclient.ErrMaxRetries) {
    // Retries exhausted but circuit breaker still allows requests
    log.Error("All retries failed for critical service")
}
Example 2: Fast-Failing Service (Circuit Breaker Only)
// For services where you want immediate failure detection
client := httpclient.NewClient(
    httpclient.WithConfig(&httpclient.Config{
        CircuitBreaker: httpclient.CircuitBreakerConfig{
            Enabled:               true,
            FailureThreshold:      2,  // Fail fast after 2 failures
            ResetTimeout:          5 * time.Second,
            HalfOpenSuccessNeeded: 1,
        },
        RetryStrategy: httpclient.RetryStrategyConfig{
            Enabled: false, // No retries - fail fast
        },
    }),
)
Example 3: Transient Error Recovery (Retry Only)
// For services with transient errors but no need for circuit breaking
client := httpclient.NewClient(
    httpclient.WithConfig(&httpclient.Config{
        CircuitBreaker: httpclient.CircuitBreakerConfig{
            Enabled: false, // No circuit breaking
        },
        RetryStrategy: httpclient.RetryStrategyConfig{
            Enabled:         true,
            MaxAttempts:     5,        // Aggressive retry
            InitialInterval: 50 * time.Millisecond,
            MaxInterval:     2 * time.Second,
            Multiplier:      1.5,
            RandomFactor:    0.2,      // Add jitter
        },
    }),
)
Example 4: Simple HTTP Client (Neither Enabled)
// For simple use cases or when you want to handle failures manually
client := httpclient.NewClient(
    httpclient.WithConfig(&httpclient.Config{
        CircuitBreaker: httpclient.CircuitBreakerConfig{Enabled: false},
        RetryStrategy:  httpclient.RetryStrategyConfig{Enabled: false},
    }),
)

// Handle errors manually
resp, err := client.Get("https://api.example.com")
if err != nil {
    // Handle transport errors
}
if resp.StatusCode >= 500 {
    // Handle server errors
}
Example 5: Context Support
client := httpclient.NewClient()

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.example.com", nil)
if err != nil {
  // Handle error
}

resp, err := client.Do(req)
if err != nil {
  // Handle error
}
defer resp.Body.Close()

Configuration Details

Circuit Breaker Configuration
type CircuitBreakerConfig struct {
    Enabled               bool          // Enable/disable circuit breaker
    FailureThreshold      uint          // Number of failures to open circuit
    ResetTimeout          time.Duration // Time before trying half-open
    HalfOpenSuccessNeeded uint          // Successes needed to close circuit
}
Retry Strategy Configuration
type RetryStrategyConfig struct {
    Enabled         bool          // Enable/disable retry mechanism
    MaxAttempts     uint          // Maximum number of attempts (including initial)
    InitialInterval time.Duration // Initial retry delay
    MaxInterval     time.Duration // Maximum retry delay
    Multiplier      float64       // Backoff multiplier
    RandomFactor    float64       // Jitter factor (0.0 to 1.0)
}

Testing

The package includes comprehensive tests covering all four independent operation modes:

  • TestClientCircuitBreakerOnly: Circuit breaker enabled, retry disabled
  • TestClientRetryOnly: Retry enabled, circuit breaker disabled
  • TestClientCircuitBreakerAndRetryBoth: Both features enabled with retry exhaustion
  • TestClientCircuitBreakerOpensBeforeRetryExhaustion: Circuit breaker opens before retries exhaust
  • TestClientNoResilienceFeatures: Both features disabled

Run tests with:

go test -v ./httpclient

Best Practices

  1. Use Both for Critical Services: Enable both circuit breaker and retry for mission-critical external services
  2. Circuit Breaker for Unstable Services: Use circuit breaker only for services known to have stability issues
  3. Retry for Transient Errors: Use retry only for services with temporary network issues
  4. Monitor Metrics: Track circuit breaker state and retry counts for observability
  5. Configure Timeouts: Always set appropriate request timeouts alongside these features

Best Practices

  1. Always use context for request timeouts
  2. Close response bodies
  3. Implement GetBody for POST/PUT requests that need retry support
  4. Configure circuit breaker thresholds based on your service's characteristics
  5. Use appropriate retry settings to avoid overwhelming downstream services

Thread Safety

The client is safe for concurrent use by multiple goroutines.

Documentation

Index

Constants

View Source
const (
	DefaultMaxAttempts     = 3
	DefaultInitialInterval = 100 * time.Millisecond
	DefaultMaxInterval     = 10 * time.Second
	DefaultMultiplier      = 2.0
	DefaultRandomFactor    = 0.1
)
View Source
const (
	DefaultServerErrorThreshold = 500

	DefaultFailureThreshold = 5
	DefaultResetTimeout     = 10 * time.Second
	DefaultHalfOpenSuccess  = 2
)

Variables

View Source
var (
	// ErrCircuitOpen is returned when the circuit breaker is open.
	ErrCircuitOpen = errors.New("circuit breaker is open")
	// ErrMaxRetries is returned when max retries are exceeded.
	ErrMaxRetries = errors.New("max retries exceeded")
	// ErrRequestBodyNotRetriable is returned when request body cannot be retried.
	ErrRequestBodyNotRetriable = errors.New(
		"request body cannot be retried, implement GetBody to enable retries",
	)
	// ErrAllRetryAttemptsFailed is returned when all retry attempts fail.
	ErrAllRetryAttemptsFailed = errors.New("all retry attempts failed")
	// ErrTransportError is returned when the underlying transport fails.
	ErrTransportError = errors.New("transport error")
	// ErrRequestContextError is returned when request context is cancelled.
	ErrRequestContextError = errors.New("request context error")
)

Functions

This section is empty.

Types

type CircuitBreaker

type CircuitBreaker struct {
	Config *CircuitBreakerConfig
	// contains filtered or unexported fields
}

func NewCircuitBreaker

func NewCircuitBreaker(config *CircuitBreakerConfig) *CircuitBreaker

func (*CircuitBreaker) IsAllowed

func (cb *CircuitBreaker) IsAllowed() bool

func (*CircuitBreaker) OnFailure

func (cb *CircuitBreaker) OnFailure()

func (*CircuitBreaker) OnSuccess

func (cb *CircuitBreaker) OnSuccess()

func (*CircuitBreaker) State

func (cb *CircuitBreaker) State() CircuitState

type CircuitBreakerConfig

type CircuitBreakerConfig struct {
	Enabled               bool          `conf:"enabled"                  default:"true"`
	FailureThreshold      uint          `conf:"failure_threshold"        default:"5"`
	ResetTimeout          time.Duration `conf:"reset_timeout"            default:"10s"`
	HalfOpenSuccessNeeded uint          `conf:"half_open_success_needed" default:"2"`
}

type CircuitState

type CircuitState int
const (
	StateClosed CircuitState = iota
	StateHalfOpen
	StateOpen
)

func (CircuitState) String added in v0.7.0

func (i CircuitState) String() string

type Client

type Client struct {
	*http.Client

	Config          *Config
	Transport       *ResilientTransport
	TLSClientConfig *tls.Config
}

Client is a drop-in replacement for http.Client with built-in circuit breaker and retry mechanisms.

func NewClient

func NewClient(options ...NewClientOption) *Client

NewClient creates a new http client with the specified circuit breaker and retry strategy.

type Config

type Config struct {
	CircuitBreaker CircuitBreakerConfig `conf:"circuit_breaker"`
	RetryStrategy  RetryStrategyConfig  `conf:"retry_strategy"`

	ServerErrorThreshold int `conf:"server_error_threshold" default:"500"`
}

type NewClientOption added in v0.7.2

type NewClientOption func(*Client)

func WithConfig added in v0.7.2

func WithConfig(config *Config) NewClientOption

func WithTLSClientConfig added in v0.7.4

func WithTLSClientConfig(tlsConfig *tls.Config) NewClientOption

type ResilientTransport

type ResilientTransport struct {
	Transport http.RoundTripper
	Config    *Config

	CircuitBreaker *CircuitBreaker
	RetryStrategy  *RetryStrategy
}

func NewResilientTransport

func NewResilientTransport(
	transport *http.Transport,
	config *Config,
) *ResilientTransport

func (*ResilientTransport) CancelRequest

func (t *ResilientTransport) CancelRequest(req *http.Request)

CancelRequest implements the optional CancelRequest method for http.RoundTripper.

func (*ResilientTransport) RoundTrip

func (t *ResilientTransport) RoundTrip(
	req *http.Request,
) (*http.Response, error)

type RetryStrategy

type RetryStrategy struct {
	Config *RetryStrategyConfig
}

func NewRetryStrategy

func NewRetryStrategy(config *RetryStrategyConfig) *RetryStrategy

NewRetryStrategy creates a new retry strategy with the specified parameters.

func (*RetryStrategy) NextBackoff

func (r *RetryStrategy) NextBackoff(attempt uint) time.Duration

type RetryStrategyConfig added in v0.7.2

type RetryStrategyConfig struct {
	Enabled         bool          `conf:"enabled"          default:"true"`
	MaxAttempts     uint          `conf:"max_attempts"     default:"3"`
	InitialInterval time.Duration `conf:"initial_interval" default:"100ms"`
	MaxInterval     time.Duration `conf:"max_interval"     default:"10s"`
	Multiplier      float64       `conf:"multiplier"       default:"2"`
	RandomFactor    float64       `conf:"random_factor"    default:"0.1"`
}

Jump to

Keyboard shortcuts

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