feat: added retryMax middleware
Add RetryMax Middleware for Automatic Request Retries
Description
Implements a new RetryMax middleware that provides automatic retry functionality for failed requests, addressing the need for production-ready retry mechanisms in Echo applications.
Feature request #2507
Features
Core Functionality
- Configurable retry attempts: Set maximum number of retry attempts per request
- Flexible backoff strategies: Exponential backoff (with jitter) and linear backoff options
- Smart retry logic: Configurable conditions for determining which errors should trigger retries
- Timeout control: Configurable minimum and maximum delays between retry attempts
Advanced Features
- Pluggable storage: Interface-based storage system supporting in-memory and external stores
- Per-identifier tracking: Track retry attempts per IP, user ID, or custom identifier
- Memory management: Built-in cleanup mechanisms prevent memory leaks in long-running applications
- Context awareness: Proper cancellation support and timeout handling
- Observability hooks: Callbacks for monitoring retry attempts and debugging
Configuration Options
type RetryMaxConfig struct {
// Core settings
MaxAttempts int // Maximum retry attempts (default: 5)
MinTimeout time.Duration // Minimum delay between retries (default: 1s)
MaxTimeout time.Duration // Maximum delay between retries (default: 5s)
// Advanced options
BackoffStrategy BackoffStrategy // Retry delay calculation
RetryableChecker RetryableChecker // Determines if error is retryable
IdentifierExtractor Extractor // Extract identifier for tracking
Store RetryMaxStore // Optional storage backend
// Hooks and handlers
OnRetry func(...) // Called before each retry
ErrorHandler func(...) // Handle extraction errors
DenyHandler func(...) // Handle max attempts exceeded
}
Usage Examples
Basic Usage
// Simple retry with defaults
e.Use(middleware.RetryMaxWithConfig(middleware.RetryMaxConfig{
MaxAttempts: 3,
MinTimeout: 100 * time.Millisecond,
MaxTimeout: 1 * time.Second,
}))
Advanced Configuration
// Custom retry logic with observability
e.Use(middleware.RetryMaxWithConfig(middleware.RetryMaxConfig{
MaxAttempts: 5,
MinTimeout: 500 * time.Millisecond,
MaxTimeout: 30 * time.Second,
BackoffStrategy: middleware.ExponentialBackoff,
RetryableChecker: func(err error) bool {
// Only retry server errors and timeouts
if httpErr, ok := err.(*echo.HTTPError); ok {
return httpErr.Code >= 500 || httpErr.Code == 408
}
return true
},
OnRetry: func(c echo.Context, identifier string, attempt int, err error) {
log.Printf("Retry %d for %s: %v", attempt, identifier, err)
},
}))
With External Store
// Using persistent storage for retry tracking
store := middleware.NewRetryMaxMemoryStore(10 * time.Minute)
e.Use(middleware.RetryMax(store))
Implementation Details
Retry Flow
- Execute handler function
- On success: reset retry counter and return
- On error: check if error is retryable
- If retryable and under max attempts: wait (backoff) and retry
- If max attempts reached: return original error
Storage Interface
The middleware supports pluggable storage through the RetryMaxStore interface:
type RetryMaxStore interface {
AllowAttempt(identifier string) (bool, error)
ResetAttempts(identifier string) error
}
Default Behavior
- No store: Retries happen within single request scope
- Memory store: Tracks attempts across requests with expiration
- Custom store: Implement interface for Redis, database, etc.
Error Handling
- Retryable errors: Server errors (5xx), timeouts, network issues
- Non-retryable errors: Client errors (4xx), validation failures
- Custom logic: Configurable via
RetryableCheckerfunction
Testing
Comprehensive test suite covering:
- Success on first attempt
- Retry until success
- Max attempts exhaustion
- Skipper functionality
- Timeout respect and backoff timing
- Store integration
- Error handling scenarios
Migration from Custom Implementation
For users with existing retry implementations:
// Before (custom implementation)
app.use(customRetryMax({
maxAttempts: 5,
minTimeout: 1000,
maxTimeout: 5000
}));
// After (Echo middleware)
e.Use(middleware.RetryMaxWithConfig(middleware.RetryMaxConfig{
MaxAttempts: 5,
MinTimeout: 1000 * time.Millisecond,
MaxTimeout: 5000 * time.Millisecond,
}))
Performance Considerations
- Memory efficient: Automatic cleanup prevents unbounded growth
- Lock optimization: Read/write locks minimize contention
- Jitter support: Prevents thundering herd problems
- Context cancellation: Respects request timeouts and cancellation
Breaking Changes
None - this is a new middleware addition with no impact on existing code.
is not the retry something that API consumer should do and not the API itself?
I am little bit reluctant to accept this as this is quite complex and experience from timeout middleware shows that there are times that people add core middlewares to their application without understanding in which use-case they actually need it (without considering if they have transactions etc complex functionality withing their application business layer).
is not the retry something that API consumer should do and not the API itself?
I am little bit reluctant to accept this as this is quite complex and experience from timeout middleware shows that there are times that people add core middlewares to their application without understanding in which use-case they actually need it (without considering if they have transactions etc complex functionality withing their application business layer).
Thanks for sharing your experience! I get that retries are usually handled on the client side, and adding them on the server can get tricky—especially when there’s complex logic or transactions involved.
The idea of this middleware is just to make retries optional and easy for developers who want them—kind of like Express’s retryMax. It doesn’t force retries; it’s just a helper you can choose to use.
I’m happy to discuss adding some warnings in the docs or tweaking the approach to make it safer if you think that’s needed.