Documentation
¶
Overview ¶
Package middleware provides optional CSRF protection middleware for flash. This middleware uses a double-submit cookie pattern and is suitable for APIs and web apps. Usage: app.Use(mw.CSRF(mw.CSRFConfig{...}))
Package middleware provides comprehensive rate limiting functionality for HTTP applications.
This package implements multiple rate limiting algorithms with a focus on security, performance, and flexibility. It's designed for production use in high-traffic applications and provides extensive configuration options for different use cases.
Features ¶
• Multiple rate limiting algorithms (Token Bucket, Fixed Window, Sliding Window, Leaky Bucket, Adaptive) • Secure client IP extraction with trusted proxy validation • Flexible key extraction (IP, user ID, API key, custom combinations) • Memory-efficient with automatic cleanup of expired entries • Thread-safe with optimized locking strategies • Comprehensive security features to prevent bypass attacks • Extensive configuration options for production deployments
Quick Start ¶
Basic IP-based rate limiting:
import "github.com/goflash/flash/v2/middleware" app := flash.New() app.Use(middleware.RateLimit( middleware.WithStrategy(middleware.NewTokenBucketStrategy(100, time.Minute)), ))
Rate Limiting Strategies ¶
Token Bucket Strategy: Best for API rate limiting. Allows bursts up to bucket capacity, then refills at a steady rate. Good for user-facing applications where occasional bursts are acceptable.
strategy := middleware.NewTokenBucketStrategy(100, time.Minute) // 100 requests per minute
Fixed Window Strategy: Simple and memory efficient. Resets counter at fixed intervals. Can have burst issues at window boundaries but uses minimal memory.
strategy := middleware.NewFixedWindowStrategy(1000, time.Hour) // 1000 requests per hour
Sliding Window Strategy: Smooth rate limiting without boundary burst issues. Uses more memory but provides the most accurate rate limiting behavior.
strategy := middleware.NewSlidingWindowStrategy(100, time.Minute) // 100 requests per minute
Leaky Bucket Strategy: Processes requests at a constant rate, queuing excess requests. Good for protecting downstream services with consistent load.
strategy := middleware.NewLeakyBucketStrategy(10.0, 50) // 10 req/sec, 50 queued max
Adaptive Strategy: Experimental strategy that adjusts rate limits based on client behavior. Can increase limits for well-behaved clients and decrease for problematic ones.
strategy := middleware.NewAdaptiveStrategy(50.0, 10.0, 100.0, time.Minute)
Security Considerations ¶
When deploying behind load balancers, CDNs, or reverse proxies, always configure trusted proxies to prevent rate limit bypassing:
app.Use(middleware.RateLimit(
middleware.WithStrategy(strategy),
middleware.WithTrustedProxies([]string{
"10.0.0.0/8", // Private networks
"172.16.0.0/12",
"192.168.0.0/16",
}),
))
Production Configuration ¶
For high-traffic production deployments:
app.Use(middleware.RateLimit(
middleware.WithStrategy(middleware.NewSlidingWindowStrategy(10000, time.Hour)),
middleware.WithTrustedProxies([]string{"10.0.0.0/8", "172.16.0.0/12"}),
middleware.WithMaxKeyLength(128),
middleware.WithCleanupInterval(5 * time.Minute),
middleware.WithKeyFunc(func(c flash.Ctx) string {
// Custom key extraction logic
return extractClientKey(c)
}),
middleware.WithErrorResponse(func(c flash.Ctx, retryAfter time.Duration) error {
// Custom error response with rate limit headers
return buildRateLimitResponse(c, retryAfter)
}),
))
Performance Tuning ¶
• Choose appropriate strategies based on memory vs accuracy trade-offs • Set cleanup intervals based on traffic patterns (1-5 min for high traffic) • Use efficient key extraction functions • Configure appropriate max key lengths to prevent memory exhaustion • Consider multiple middleware instances for different rate limits
Thread Safety ¶
All rate limiting strategies are thread-safe and optimized for concurrent access. The implementation uses read-write mutexes and atomic operations where appropriate to minimize lock contention in high-concurrency scenarios.
Memory Management ¶
The package includes automatic cleanup mechanisms to prevent memory leaks: • Background cleanup goroutines remove expired entries • Configurable cleanup intervals for different traffic patterns • Memory-efficient data structures for each strategy • Proper resource cleanup when strategies are closed
Package middleware provides comprehensive session management functionality for HTTP applications.
The session middleware offers secure, efficient session handling with support for multiple storage backends, configurable security settings, and production-ready features including automatic cleanup, session regeneration, and timing attack protection.
Features ¶
• Secure session ID generation with cryptographic randomness • Pluggable storage interface supporting memory, database, Redis, etc. • Automatic session expiration and cleanup • Session regeneration for security (prevents session fixation) • Timing attack protection for session lookups • Cookie and header-based session ID transport • Comprehensive security headers and cookie attributes • Memory-efficient operations with optimized copying • Thread-safe operations with fine-grained locking
Quick Start ¶
Basic session usage:
import "github.com/goflash/flash/v2/middleware"
app := flash.New()
app.Use(middleware.Sessions(middleware.SessionConfig{
Store: middleware.NewMemoryStore(),
TTL: 24 * time.Hour,
CookieName: "session_id",
HTTPOnly: true,
Secure: true, // Enable in production with HTTPS
}))
app.GET("/login", func(c flash.Ctx) error {
session := middleware.SessionFromCtx(c)
session.Set("user_id", "123")
session.Set("authenticated", true)
return c.JSON(200, map[string]string{"status": "logged in"})
})
Security Features ¶
Session Regeneration: Prevents session fixation attacks by generating new session IDs after authentication.
session := middleware.SessionFromCtx(c)
session.Regenerate() // Generate new session ID
session.Set("user_id", userID)
Secure Cookie Configuration: Production-ready cookie security settings.
config := middleware.SessionConfig{
Secure: true, // HTTPS only
HTTPOnly: true, // No JavaScript access
SameSite: http.SameSiteStrictMode, // CSRF protection
Domain: ".example.com", // Scope to domain
}
Storage Backends ¶
Memory Store (Development): Built-in memory store with automatic cleanup.
store := middleware.NewMemoryStore() store.StartCleanup(5 * time.Minute) // Clean expired sessions every 5 minutes
Custom Store Implementation: Implement the Store interface for database, Redis, etc.
type RedisStore struct { /* ... */ }
func (r *RedisStore) Get(id string) (map[string]any, bool) { /* ... */ }
func (r *RedisStore) Save(id string, data map[string]any, ttl time.Duration) error { /* ... */ }
func (r *RedisStore) Delete(id string) error { /* ... */ }
Index ¶
- func Buffer(cfgs ...BufferConfig) flash.Middleware
- func CORS(cfg CORSConfig) flash.Middleware
- func CSRF(cfgs ...CSRFConfig) flash.Middleware
- func Logger(options ...LoggerOption) flash.Middleware
- func RateLimit(options ...RateLimitOption) flash.Middleware
- func Recover(cfgs ...RecoverConfig) flash.Middleware
- func RequestID(cfgs ...RequestIDConfig) flash.Middleware
- func RequestIDFromContext(ctx context.Context) (string, bool)
- func RequestSize(cfg RequestSizeConfig) flash.Middleware
- func Sessions(cfg SessionConfig) flash.Middleware
- func Timeout(cfg TimeoutConfig) flash.Middleware
- func WithLoggerAttributes(ctx context.Context, attrs *LoggerAttributes) context.Context
- type AdaptiveStrategy
- type BufferConfig
- type CORSConfig
- type CSRFConfig
- type FixedWindowStrategy
- type LeakyBucketStrategy
- type LoggerAttributeKey
- type LoggerAttributes
- type LoggerConfig
- type LoggerOption
- type MemoryStore
- func (m *MemoryStore) Delete(id string) error
- func (m *MemoryStore) Get(id string) (map[string]any, bool)
- func (m *MemoryStore) Len() int
- func (m *MemoryStore) Save(id string, data map[string]any, ttl time.Duration) error
- func (m *MemoryStore) StartCleanup(interval time.Duration)
- func (m *MemoryStore) StopCleanup()
- type RateLimitConfig
- type RateLimitOption
- func WithCleanupInterval(interval time.Duration) RateLimitOption
- func WithErrorResponse(errorResponse func(c flash.Ctx, retryAfter time.Duration) error) RateLimitOption
- func WithKeyFunc(keyFunc func(c flash.Ctx) string) RateLimitOption
- func WithMaxKeyLength(maxLength int) RateLimitOption
- func WithSkipFunc(skipFunc func(c flash.Ctx) bool) RateLimitOption
- func WithStrategy(strategy RateLimitStrategy) RateLimitOption
- func WithTrustedProxies(proxies []string) RateLimitOption
- type RateLimitStrategy
- type RecoverConfig
- type RequestIDConfig
- type RequestSizeConfig
- type Session
- type SessionConfig
- type SlidingWindowStrategy
- type Store
- type TimeoutConfig
- type TokenBucketStrategy
Examples ¶
- Logger
- Logger (DifferentRouteGroups)
- Logger (WithCustomAttributes)
- Logger (WithCustomMessage)
- Logger (WithExcludeFields)
- Logger (WithMultipleOptions)
- NewAdaptiveStrategy
- NewFixedWindowStrategy
- NewLeakyBucketStrategy
- NewLoggerAttributes
- NewSlidingWindowStrategy
- NewTokenBucketStrategy
- RateLimit
- RateLimit (CombinedOptions)
- RateLimit (DefaultStrategy)
- RateLimit (DifferentStrategiesForDifferentRoutes)
- RateLimit (WithAdaptive)
- RateLimit (WithCustomErrorResponse)
- RateLimit (WithCustomKeyFunc)
- RateLimit (WithFixedWindow)
- RateLimit (WithLeakyBucket)
- RateLimit (WithSkipFunc)
- RateLimit (WithSlidingWindow)
- RateLimit (WithTokenBucket)
- WithLoggerAttributes
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Buffer ¶
func Buffer(cfgs ...BufferConfig) flash.Middleware
Buffer returns middleware that wraps the ResponseWriter with a pooled buffer to reduce syscalls and to set an accurate Content-Length when possible. Not recommended for streaming/SSE. Apply before handlers that generate bounded payloads.
Behavior:
- Buffers writes in-memory up to MaxSize; beyond that, switches to streaming
- Sets Content-Length on close when safe (no Content-Encoding)
- Supports Flush passthrough and zero-allocation HEAD responses
Example:
// Global buffering with defaults (unbounded). Prefer setting MaxSize.
app.Use(middleware.Buffer())
// Per-route configuration
app.GET("/report", handler, middleware.Buffer(middleware.BufferConfig{MaxSize: 2<<20}))
func CORS ¶
func CORS(cfg CORSConfig) flash.Middleware
CORS returns middleware that sets CORS headers and handles preflight requests according to the provided config with enhanced security features.
Security Features:
- Origin validation with wildcard handling
- Prevents credential exposure with wildcard origins
- Validates requested methods and headers against allowed lists
- Adds security headers to prevent content type sniffing
- Proper handling of null and invalid origins
Behavior:
- Sets Access-Control-Allow-Origin, -Credentials, -Expose-Headers on all responses
- For OPTIONS requests with Access-Control-Request-Method header (preflight):
- Validates requested method against allowed methods
- Validates requested headers against allowed headers
- Sets Access-Control-Allow-Methods, -Headers, -Max-Age
- Returns 204 No Content
- For other OPTIONS requests: passes through to handler
- For non-OPTIONS requests: passes through to handler
Performance notes:
- Headers are computed once at middleware creation, not per request
- Origin validation uses efficient string matching
- Preflight responses are cached by browsers according to MaxAge
- No allocations in the hot path for header string joining
Example:
// Simple CORS for API
app.Use(middleware.CORS(middleware.CORSConfig{
Origins: []string{"https://app.example.com"},
Methods: []string{"GET", "POST", "PUT", "DELETE"},
Headers: []string{"Content-Type", "Authorization"},
}))
Example (with credentials and caching):
// CORS for authenticated API
app.Use(middleware.CORS(middleware.CORSConfig{
Origins: []string{"https://app.example.com", "https://admin.example.com"},
Methods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
Headers: []string{"Content-Type", "Authorization", "X-Requested-With"},
Expose: []string{"X-Total-Count", "X-Rate-Limit-Remaining"},
Credentials: true,
MaxAge: 3600, // 1 hour
}))
Security Best Practices:
// Production-ready CORS configuration
app.Use(middleware.CORS(middleware.CORSConfig{
Origins: []string{"https://app.example.com"}, // Specific origins, not "*"
Methods: []string{"GET", "POST", "PUT", "DELETE"}, // Only needed methods
Headers: []string{"Content-Type", "Authorization"}, // Only needed headers
Credentials: true, // Only if needed
MaxAge: 86400, // 24 hours - balance security and performance
}))
func CSRF ¶
func CSRF(cfgs ...CSRFConfig) flash.Middleware
CSRF returns middleware that provides CSRF protection using the double-submit cookie pattern.
Behavior:
- For safe methods (GET, HEAD, OPTIONS): sets CSRF cookie if missing, then continues
- For unsafe methods (POST, PUT, PATCH, DELETE): validates token in both cookie and header
- Returns 403 Forbidden if token is missing or invalid
- Uses constant-time comparison to prevent timing attacks
Performance notes:
- Token generation uses crypto/rand for cryptographic security
- Constant-time comparison prevents timing-based attacks
- Cookie validation only occurs for unsafe methods
- Minimal overhead for safe methods (just cookie setting)
Security features:
- Double-submit pattern prevents CSRF attacks
- Cryptographically secure random tokens
- Constant-time token comparison
- Configurable cookie security attributes
Example (using defaults):
app.Use(middleware.CSRF())
Example (custom configuration):
app.Use(middleware.CSRF(middleware.CSRFConfig{
CookieName: "csrf_token",
HeaderName: "X-CSRF-Header",
TokenLength: 64, // stronger tokens
CookieSecure: true,
CookieHTTPOnly: true,
CookieSameSite: http.SameSiteStrictMode,
TTL: 24 * time.Hour,
}))
Client-side usage:
// JavaScript: read token from cookie and send in header
const token = document.cookie.match('_csrf=([^;]+)')[1];
fetch('/api/data', {
method: 'POST',
headers: { 'X-CSRF-Token': token },
body: JSON.stringify(data)
});
func Logger ¶
func Logger(options ...LoggerOption) flash.Middleware
Logger returns middleware that logs each HTTP request using structured logging (slog).
This middleware automatically captures and logs the following request information:
- HTTP method (GET, POST, PUT, DELETE, etc.)
- Request path (e.g., "/api/users/123")
- Route pattern (e.g., "/api/users/:id")
- HTTP status code (200, 404, 500, etc.)
- Request duration in milliseconds
- Remote client address
- User agent string
- Request ID (if available via RequestID middleware)
- Custom attributes (if provided via context or CustomAttributesFunc)
The logger is retrieved from the request context or application context. If no status code is set by the handler, it defaults to 200 (OK).
Usage Examples:
// Basic usage - add to your app or group
app := flash.New()
app.Use(middleware.Logger())
// With custom routes
app.Get("/users", func(c flash.Ctx) error {
// Your handler logic here
return c.JSON(200, map[string]string{"message": "success"})
})
// Combined with other middleware
app.Use(
middleware.RequestID(),
middleware.Logger(), // Logger will include request_id if available
middleware.Recover(),
)
// On specific route groups
api := app.Group("/api")
api.Use(middleware.Logger())
api.Get("/users", userHandler)
api.Post("/users", createUserHandler)
// With configuration options
app.Use(middleware.Logger(
middleware.WithExcludeFields("user_agent", "remote"),
middleware.WithCustomAttributes(func(c flash.Ctx) []any {
if userID := c.Context().Value("user_id"); userID != nil {
return []any{"user_id", userID}
}
return nil
}),
middleware.WithMessage("http_request"),
))
// Add custom attributes in handlers or middleware
app.Use(func(next flash.Handler) flash.Handler {
return func(c flash.Ctx) error {
// Add custom attributes to context
attrs := middleware.NewLoggerAttributes("middleware", "auth", "version", "v2")
ctx := middleware.WithLoggerAttributes(c.Context(), attrs)
c.SetRequest(c.Request().WithContext(ctx))
return next(c)
}
})
// In handlers, add dynamic attributes
app.Get("/users/:id", func(c flash.Ctx) error {
userID := c.Param("id")
attrs := middleware.NewLoggerAttributes("user_id", userID, "operation", "fetch")
ctx := middleware.WithLoggerAttributes(c.Context(), attrs)
c.SetRequest(c.Request().WithContext(ctx))
// Handler logic...
return c.JSON(200, user)
})
Example log output:
{
"level": "INFO",
"msg": "request",
"method": "GET",
"path": "/api/users/123",
"route": "/api/users/:id",
"status": 200,
"duration_ms": 45.2,
"remote": "192.168.1.100:54321",
"user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36",
"request_id": "req_abc123def456",
"user_id": "123",
"operation": "fetch",
"middleware": "auth",
"version": "v2"
}
Error handling:
// The middleware will log the request even if the handler returns an error
app.Get("/error", func(c flash.Ctx) error {
return flash.NewError(500, "Internal server error")
})
// Log output will show status: 500 and the error will be returned to the client
Performance considerations:
// The middleware has minimal overhead and is safe to use in production // Duration is measured in microseconds and converted to milliseconds for readability // All string operations are efficient and don't allocate unnecessary memory // Custom attributes are efficiently stored and retrieved from context // Excluded fields are handled with minimal performance impact
Example ¶
ExampleLogger demonstrates basic usage of the Logger middleware.
app := flash.New()
app.Use(Logger())
app.GET("/users", func(c flash.Ctx) error {
return c.JSON(map[string]string{"message": "success"})
})
Example (DifferentRouteGroups) ¶
ExampleLogger_differentRouteGroups demonstrates using different logger configurations for different route groups.
app := flash.New()
// API routes with detailed logging
api := app.Group("/api")
api.Use(Logger(
WithCustomAttributes(func(c flash.Ctx) []any {
return []any{"service", "api", "version", "v1"}
}),
WithMessage("api_request"),
))
api.GET("/users", func(c flash.Ctx) error {
return c.JSON(map[string]string{"message": "users"})
})
// Admin routes with minimal logging (exclude sensitive fields)
admin := app.Group("/admin")
admin.Use(Logger(
WithExcludeFields("user_agent", "remote", "request_id"),
WithCustomAttributes(func(c flash.Ctx) []any {
return []any{"service", "admin", "access_level", "admin"}
}),
WithMessage("admin_request"),
))
admin.GET("/stats", func(c flash.Ctx) error {
return c.JSON(map[string]string{"message": "stats"})
})
// Public routes with standard logging
app.Use(Logger(WithMessage("public_request")))
app.GET("/", func(c flash.Ctx) error {
return c.String(200, "Hello World")
})
Example (WithCustomAttributes) ¶
ExampleLogger_withCustomAttributes demonstrates adding custom attributes via function.
app := flash.New()
app.Use(Logger(WithCustomAttributes(func(c flash.Ctx) []any {
// Add user ID from authentication context
if userID := c.Context().Value("user_id"); userID != nil {
return []any{"user_id", userID}
}
return nil
})))
app.GET("/users", func(c flash.Ctx) error {
return c.JSON(map[string]string{"message": "success"})
})
Example (WithCustomMessage) ¶
ExampleLogger_withCustomMessage demonstrates using a custom log message.
app := flash.New()
app.Use(Logger(WithMessage("http_request")))
app.GET("/users", func(c flash.Ctx) error {
return c.JSON(map[string]string{"message": "success"})
})
Example (WithExcludeFields) ¶
ExampleLogger_withExcludeFields demonstrates excluding specific fields from logging.
app := flash.New()
// Exclude user agent and remote address for privacy
app.Use(Logger(WithExcludeFields("user_agent", "remote")))
app.GET("/users", func(c flash.Ctx) error {
return c.JSON(map[string]string{"message": "success"})
})
Example (WithMultipleOptions) ¶
ExampleLogger_withMultipleOptions demonstrates combining multiple configuration options.
app := flash.New()
app.Use(Logger(
WithExcludeFields("user_agent", "remote"),
WithCustomAttributes(func(c flash.Ctx) []any {
if userID := c.Context().Value("user_id"); userID != nil {
return []any{"user_id", userID, "operation", "api_call"}
}
return []any{"operation", "api_call"}
}),
WithMessage("api_request"),
))
app.GET("/users", func(c flash.Ctx) error {
return c.JSON(map[string]string{"message": "success"})
})
func RateLimit ¶
func RateLimit(options ...RateLimitOption) flash.Middleware
RateLimit returns middleware that applies rate limiting to HTTP requests.
This middleware integrates any RateLimitStrategy implementation with the HTTP request flow. It extracts a client identifier (key), checks if the request is allowed by the strategy, and returns an appropriate error response if the rate limit is exceeded.
The middleware is highly configurable and supports various rate limiting patterns:
- Different algorithms (token bucket, sliding window, etc.)
- Custom key extraction (IP, user ID, API key, etc.)
- Custom error responses (JSON, HTML, etc.)
- Request whitelisting and skipping
- Secure proxy handling
- Memory management and cleanup
Basic Usage Examples:
// Simple IP-based rate limiting (100 requests per minute)
app.Use(middleware.RateLimit(
middleware.WithStrategy(middleware.NewTokenBucketStrategy(100, time.Minute)),
))
// API rate limiting with custom error response
app.Use(middleware.RateLimit(
middleware.WithStrategy(middleware.NewTokenBucketStrategy(1000, time.Hour)),
middleware.WithErrorResponse(func(c flash.Ctx, retryAfter time.Duration) error {
return c.Status(429).JSON(map[string]any{
"error": "API rate limit exceeded",
"retry_after_seconds": int(retryAfter.Seconds()),
"limit": 1000,
"window": "1 hour",
})
}),
))
Advanced Usage Examples:
// User-based rate limiting with different limits per user tier
app.Use(middleware.RateLimit(
middleware.WithStrategy(middleware.NewTokenBucketStrategy(100, time.Minute)),
middleware.WithKeyFunc(func(c flash.Ctx) string {
userID := c.Get("user_id")
userTier := c.Get("user_tier")
return fmt.Sprintf("%s:%s", userTier, userID)
}),
middleware.WithSkipFunc(func(c flash.Ctx) bool {
// Skip rate limiting for premium users
return c.Get("user_tier") == "premium"
}),
))
// Multi-layered rate limiting (per-IP and per-user)
app.Use(middleware.RateLimit(
middleware.WithStrategy(middleware.NewTokenBucketStrategy(1000, time.Hour)),
middleware.WithKeyFunc(func(c flash.Ctx) string {
// Global per-IP limit
return "ip:" + clientIP(c.Request())
}),
))
app.Use(middleware.RateLimit(
middleware.WithStrategy(middleware.NewTokenBucketStrategy(100, time.Minute)),
middleware.WithKeyFunc(func(c flash.Ctx) string {
// Per-user limit (more restrictive)
if userID := c.Get("user_id"); userID != nil {
return "user:" + userID.(string)
}
return "anonymous"
}),
))
Production Configuration Examples:
// High-traffic API behind AWS Application Load Balancer
app.Use(middleware.RateLimit(
middleware.WithStrategy(middleware.NewSlidingWindowStrategy(10000, time.Hour)),
middleware.WithTrustedProxies([]string{
"10.0.0.0/8", // Private subnets
"172.16.0.0/12", // Private subnets
"192.168.0.0/16", // Private subnets
}),
middleware.WithMaxKeyLength(128),
middleware.WithCleanupInterval(5 * time.Minute),
middleware.WithKeyFunc(func(c flash.Ctx) string {
// Combine API key and IP for more granular control
apiKey := c.Header("X-API-Key")
if apiKey == "" {
return "no-key:" + clientIP(c.Request())
}
return "api:" + apiKey
}),
middleware.WithErrorResponse(func(c flash.Ctx, retryAfter time.Duration) error {
// Include rate limit headers for API clients
c.Header("X-RateLimit-Limit", "10000")
c.Header("X-RateLimit-Remaining", "0")
c.Header("X-RateLimit-Reset", fmt.Sprintf("%d", time.Now().Add(retryAfter).Unix()))
c.Header("Retry-After", fmt.Sprintf("%.0f", retryAfter.Seconds()))
return c.Status(429).JSON(map[string]any{
"error": "rate_limit_exceeded",
"message": "API rate limit exceeded. Please slow down your requests.",
"retry_after_seconds": int(retryAfter.Seconds()),
"documentation": "https://docs.example.com/api/rate-limits",
})
}),
middleware.WithSkipFunc(func(c flash.Ctx) bool {
// Skip rate limiting for health checks and internal services
path := c.Path()
return path == "/health" ||
path == "/metrics" ||
c.Header("X-Internal-Service") != ""
}),
))
// WebSocket connection rate limiting
app.Use(middleware.RateLimit(
middleware.WithStrategy(middleware.NewLeakyBucketStrategy(1.0, 10)), // 1 conn/sec, max 10 queued
middleware.WithKeyFunc(func(c flash.Ctx) string {
return "ws:" + clientIP(c.Request())
}),
middleware.WithSkipFunc(func(c flash.Ctx) bool {
// Only apply to WebSocket upgrade requests
return c.Header("Upgrade") != "websocket"
}),
))
Strategy Selection Guide:
- TokenBucketStrategy: Best for APIs, allows bursts, good for user-facing applications
- FixedWindowStrategy: Simple and memory efficient, but can have boundary burst issues
- SlidingWindowStrategy: Smooth rate limiting, higher memory usage, best for strict limits
- LeakyBucketStrategy: Constant rate processing, good for resource protection
- AdaptiveStrategy: Experimental, adjusts based on client behavior
Security Considerations:
- Always configure TrustedProxies when behind load balancers/CDNs
- Set appropriate MaxKeyLength to prevent memory exhaustion attacks
- Use secure key extraction to prevent rate limit bypassing
- Consider using HTTPS to prevent header manipulation
- Monitor for suspicious patterns and adjust limits accordingly
Performance Considerations:
- TokenBucket and FixedWindow are most memory efficient
- SlidingWindow uses more memory but provides smoother limiting
- Set appropriate CleanupInterval based on traffic patterns
- Consider using multiple middleware instances for different rate limits
- Use SkipFunc judiciously to avoid unnecessary processing
Example ¶
ExampleRateLimit demonstrates basic usage of the RateLimit middleware.
app := flash.New()
strategy := NewTokenBucketStrategy(100, time.Minute)
app.Use(RateLimit(WithStrategy(strategy)))
app.GET("/users", func(c flash.Ctx) error {
return c.JSON(map[string]string{"message": "success"})
})
Example (CombinedOptions) ¶
ExampleRateLimit_combinedOptions demonstrates combining multiple configuration options.
app := flash.New()
strategy := NewTokenBucketStrategy(50, time.Minute)
// Combine multiple options for sophisticated rate limiting
app.Use(RateLimit(
WithStrategy(strategy),
WithKeyFunc(func(c flash.Ctx) string {
// Rate limit by user ID if available, otherwise by IP
if userID := c.Context().Value("user_id"); userID != nil {
return "user:" + userID.(string)
}
return "ip:" + c.Request().RemoteAddr
}),
WithErrorResponse(func(c flash.Ctx, retryAfter time.Duration) error {
return c.JSON(map[string]interface{}{
"error": "Too many requests",
"retry_after": int(retryAfter.Seconds()),
"limit": 50,
"window": "1 minute",
})
}),
WithSkipFunc(func(c flash.Ctx) bool {
// Skip for internal health checks
return c.Path() == "/health" || c.Context().Value("internal") == true
}),
))
app.GET("/api", func(c flash.Ctx) error {
return c.JSON(map[string]string{"message": "api response"})
})
Example (DefaultStrategy) ¶
ExampleRateLimit_defaultStrategy demonstrates using the default strategy.
app := flash.New()
// Use RateLimit without specifying a strategy (uses default token bucket)
app.Use(RateLimit()) // Default: 100 requests per minute per IP
app.GET("/default", func(c flash.Ctx) error {
return c.JSON(map[string]string{"message": "default response"})
})
Example (DifferentStrategiesForDifferentRoutes) ¶
ExampleRateLimit_differentStrategiesForDifferentRoutes demonstrates using different strategies for different route groups.
app := flash.New()
// Public routes with generous limits
publicStrategy := NewTokenBucketStrategy(1000, time.Hour)
app.Use(RateLimit(WithStrategy(publicStrategy)))
app.GET("/", func(c flash.Ctx) error {
return c.JSON(map[string]string{"message": "public"})
})
// API routes with moderate limits
api := app.Group("/api")
apiStrategy := NewSlidingWindowStrategy(100, time.Minute)
api.Use(RateLimit(WithStrategy(apiStrategy)))
api.GET("/users", func(c flash.Ctx) error {
return c.JSON(map[string]string{"message": "api users"})
})
// Admin routes with strict limits
admin := app.Group("/admin")
adminStrategy := NewFixedWindowStrategy(10, time.Hour)
admin.Use(RateLimit(WithStrategy(adminStrategy)))
admin.GET("/stats", func(c flash.Ctx) error {
return c.JSON(map[string]string{"message": "admin stats"})
})
// Authentication routes with very strict limits
auth := app.Group("/auth")
authStrategy := NewLeakyBucketStrategy(1.0, 5) // 1 request per second
auth.Use(RateLimit(WithStrategy(authStrategy)))
auth.POST("/login", func(c flash.Ctx) error {
return c.JSON(map[string]string{"message": "login"})
})
Example (WithAdaptive) ¶
ExampleRateLimit_withAdaptive demonstrates adaptive strategy.
app := flash.New()
// Adaptive strategy adjusts rate based on client behavior
strategy := NewAdaptiveStrategy(10.0, 1.0, 100.0, time.Minute) // 10-100 req/sec range
app.Use(RateLimit(WithStrategy(strategy)))
app.GET("/adaptive", func(c flash.Ctx) error {
// In your application logic, provide feedback to the strategy
// strategy.UpdateRate(clientKey, true) // Good behavior
// strategy.UpdateRate(clientKey, false) // Bad behavior
return c.JSON(map[string]string{"message": "adaptive response"})
})
Example (WithCustomErrorResponse) ¶
ExampleRateLimit_withCustomErrorResponse demonstrates custom error responses.
app := flash.New()
strategy := NewTokenBucketStrategy(5, time.Minute)
// Custom error response with JSON format
app.Use(RateLimit(
WithStrategy(strategy),
WithErrorResponse(func(c flash.Ctx, retryAfter time.Duration) error {
return c.JSON(map[string]interface{}{
"error": "Rate limit exceeded",
"retry_after": int(retryAfter.Seconds()),
"limit_type": "token_bucket",
})
}),
))
app.GET("/api", func(c flash.Ctx) error {
return c.JSON(map[string]string{"message": "api response"})
})
Example (WithCustomKeyFunc) ¶
ExampleRateLimit_withCustomKeyFunc demonstrates custom key extraction.
app := flash.New()
strategy := NewTokenBucketStrategy(10, time.Minute)
// Rate limit by user ID instead of IP
app.Use(RateLimit(
WithStrategy(strategy),
WithKeyFunc(func(c flash.Ctx) string {
if userID := c.Context().Value("user_id"); userID != nil {
return userID.(string)
}
return "anonymous"
}),
))
app.GET("/user", func(c flash.Ctx) error {
return c.JSON(map[string]string{"message": "user response"})
})
Example (WithFixedWindow) ¶
ExampleRateLimit_withFixedWindow demonstrates fixed window strategy.
app := flash.New()
// Fixed window resets counter at fixed intervals (allows bursts at boundaries)
strategy := NewFixedWindowStrategy(100, time.Hour) // 100 requests per hour window
app.Use(RateLimit(WithStrategy(strategy)))
app.GET("/public", func(c flash.Ctx) error {
return c.JSON(map[string]string{"message": "public response"})
})
Example (WithLeakyBucket) ¶
ExampleRateLimit_withLeakyBucket demonstrates leaky bucket strategy.
app := flash.New()
// Leaky bucket processes requests at fixed rate, queues excess
strategy := NewLeakyBucketStrategy(5.0, 10) // 5 requests per second, capacity for 10 queued
app.Use(RateLimit(WithStrategy(strategy)))
app.GET("/streaming", func(c flash.Ctx) error {
return c.JSON(map[string]string{"message": "streaming response"})
})
Example (WithSkipFunc) ¶
ExampleRateLimit_withSkipFunc demonstrates skipping rate limiting for certain requests.
app := flash.New()
strategy := NewTokenBucketStrategy(10, time.Minute)
// Skip rate limiting for admin users or health checks
app.Use(RateLimit(
WithStrategy(strategy),
WithSkipFunc(func(c flash.Ctx) bool {
// Skip for admin users
if c.Context().Value("admin") == true {
return true
}
// Skip for health checks
if c.Path() == "/health" {
return true
}
return false
}),
))
app.GET("/api", func(c flash.Ctx) error {
return c.JSON(map[string]string{"message": "api response"})
})
app.GET("/health", func(c flash.Ctx) error {
return c.JSON(map[string]string{"status": "healthy"})
})
Example (WithSlidingWindow) ¶
ExampleRateLimit_withSlidingWindow demonstrates sliding window strategy.
app := flash.New()
// Sliding window provides smooth rate limiting without burst issues
strategy := NewSlidingWindowStrategy(10, time.Minute) // 10 requests per 1-minute sliding window
app.Use(RateLimit(WithStrategy(strategy)))
app.GET("/sensitive", func(c flash.Ctx) error {
return c.JSON(map[string]string{"message": "sensitive response"})
})
Example (WithTokenBucket) ¶
ExampleRateLimit_withTokenBucket demonstrates token bucket strategy.
app := flash.New()
// Token bucket allows bursts up to capacity, then refills over time
strategy := NewTokenBucketStrategy(50, time.Minute) // 50 requests per minute with burst support
app.Use(RateLimit(WithStrategy(strategy)))
app.GET("/api", func(c flash.Ctx) error {
return c.JSON(map[string]string{"message": "api response"})
})
func Recover ¶
func Recover(cfgs ...RecoverConfig) flash.Middleware
Recover returns middleware that recovers from panics in HTTP handlers with enhanced security and logging.
Security Features:
- Never exposes stack traces or panic details to clients by default
- Configurable logging to prevent information leakage
- Structured error responses without sensitive information
- Safe handling of panic values that might contain sensitive data
This middleware is essential for production applications as it prevents panics from crashing the entire server. When a panic occurs in any handler, the middleware catches it and returns a generic HTTP 500 error response to the client while allowing the server to continue processing other requests.
The middleware uses Go's built-in recover() mechanism to catch panics and converts them to HTTP errors. It's recommended to use this middleware early in the middleware chain, typically as one of the first middleware applied to your application.
Usage Examples:
// Basic usage - secure defaults
app := flash.New()
app.Use(middleware.Recover())
// With custom logging (production-safe)
app.Use(middleware.Recover(middleware.RecoverConfig{
OnPanic: func(c flash.Ctx, err interface{}) {
logger := ctx.LoggerFromContext(c.Context())
logger.Error("panic recovered",
"error", fmt.Sprintf("%v", err),
"method", c.Method(),
"path", c.Path(),
"user_agent", c.Request().Header.Get("User-Agent"),
)
},
}))
// With custom error response
app.Use(middleware.Recover(middleware.RecoverConfig{
ErrorResponse: func(c flash.Ctx, err interface{}) error {
return c.JSON(http.StatusInternalServerError, map[string]interface{}{
"error": "Service temporarily unavailable",
"code": "SERVICE_ERROR",
"retry_after": 60,
})
},
}))
// Development configuration (with stack traces)
if os.Getenv("ENV") == "development" {
app.Use(middleware.Recover(middleware.RecoverConfig{
EnableStack: true,
OnPanic: func(c flash.Ctx, err interface{}) {
log.Printf("PANIC: %v\nStack: %s", err, debug.Stack())
},
}))
}
// With other essential middleware
app.Use(
middleware.RequestID(),
middleware.Logger(),
middleware.Recover(), // Should be early in the chain
)
// Example handler that might panic
app.Get("/users/:id", func(c flash.Ctx) error {
id := c.Param("id")
if id == "panic" {
panic("intentional panic for testing")
}
// Normal handler logic
return c.JSON(200, map[string]string{"id": id})
})
// On specific route groups
api := app.Group("/api")
api.Use(middleware.Recover())
api.Get("/data", func(c flash.Ctx) error {
// This handler is protected from panics
return c.JSON(200, map[string]string{"data": "protected"})
})
Error Response (default):
// When a panic occurs, the client receives: // HTTP/1.1 500 Internal Server Error // Content-Type: text/plain; charset=utf-8 // X-Content-Type-Options: nosniff // Content-Length: 21 // // Internal Server Error
Panic Scenarios Handled:
// 1. Nil pointer dereference
app.Get("/nil", func(c flash.Ctx) error {
var data *map[string]string
_ = (*data)["key"] // This would panic without Recover middleware
return nil
})
// 2. Array/slice index out of bounds
app.Get("/bounds", func(c flash.Ctx) error {
arr := []int{1, 2, 3}
_ = arr[10] // This would panic without Recover middleware
return nil
})
// 3. Type assertions
app.Get("/assert", func(c flash.Ctx) error {
var i interface{} = "string"
_ = i.(int) // This would panic without Recover middleware
return nil
})
// 4. Division by zero
app.Get("/divide", func(c flash.Ctx) error {
x := 10
y := 0
_ = x / y // This would panic without Recover middleware
return nil
})
// 5. Manual panics
app.Get("/manual", func(c flash.Ctx) error {
panic("something went wrong")
})
Best Practices:
// 1. Always use Recover middleware in production
// 2. Place it early in the middleware chain
// 3. Combine with logging middleware to track panics
// 4. Never expose panic details to clients in production
// 5. Use structured logging for panic analysis
app.Use(
middleware.RequestID(),
middleware.Logger(),
middleware.Recover(), // Early in chain
middleware.CORS(),
middleware.Timeout(30 * time.Second),
)
Performance Impact:
// The Recover middleware has minimal performance overhead // It only adds a defer function that's only executed when panics occur // In normal operation, there's virtually no performance cost
func RequestID ¶
func RequestID(cfgs ...RequestIDConfig) flash.Middleware
RequestID returns middleware that adds a unique request ID to each request/response. The request ID is set in the configured header and made available in the request context.
func RequestIDFromContext ¶
RequestIDFromContext returns the request ID from the context, if available.
func RequestSize ¶
func RequestSize(cfg RequestSizeConfig) flash.Middleware
RequestSize returns middleware that limits the maximum size of request bodies.
Security Features:
- Prevents memory exhaustion attacks through large request bodies
- Early rejection before body parsing to minimize resource usage
- Configurable limits for different application needs
- Secure error responses that don't leak sensitive information
Performance Features:
- Zero-allocation size checking using Content-Length header
- Early rejection prevents unnecessary request processing
- Minimal overhead for requests within size limits
- No impact on request streaming or processing speed
Behavior:
- Checks Content-Length header before processing request body
- Returns 413 Request Entity Too Large for oversized requests
- Allows requests without Content-Length header (e.g., chunked encoding)
- Works with all HTTP methods and content types
Usage Examples:
// Basic usage with 5MB limit
app.Use(middleware.RequestSize(middleware.RequestSizeConfig{
MaxSize: 5 << 20, // 5MB
}))
// API-specific limits
app.Use(middleware.RequestSize(middleware.RequestSizeConfig{
MaxSize: 1 << 20, // 1MB for JSON APIs
}))
// File upload endpoints with higher limits
uploadGroup := app.Group("/upload")
uploadGroup.Use(middleware.RequestSize(middleware.RequestSizeConfig{
MaxSize: 100 << 20, // 100MB for file uploads
}))
// Custom error handling
app.Use(middleware.RequestSize(middleware.RequestSizeConfig{
MaxSize: 10 << 20, // 10MB
ErrorResponse: func(c flash.Ctx, size, limit int64) error {
// Log the attempt
logger := ctx.LoggerFromContext(c.Context())
logger.Warn("request size limit exceeded",
"size", size,
"limit", limit,
"path", c.Path(),
"method", c.Method(),
"remote_addr", c.Request().RemoteAddr,
)
return c.Status(http.StatusRequestEntityTooLarge).JSON(map[string]interface{}{
"error": "Request entity too large",
"code": "REQUEST_TOO_LARGE",
"max_size_bytes": limit,
})
},
}))
Security Best Practices:
// Production configuration
app.Use(middleware.RequestSize(middleware.RequestSizeConfig{
MaxSize: 5 << 20, // Start conservative - 5MB
ErrorResponse: func(c flash.Ctx, size, limit int64) error {
// Log for monitoring but don't expose details to client
logger := ctx.LoggerFromContext(c.Context())
logger.Warn("request size limit exceeded",
"size", size,
"limit", limit,
"user_agent", c.Request().Header.Get("User-Agent"),
"remote_addr", c.Request().RemoteAddr,
)
return c.Status(http.StatusRequestEntityTooLarge).JSON(map[string]interface{}{
"error": "Request entity too large",
})
},
}))
Common Size Limits:
- JSON APIs: 1MB (1 << 20)
- Form submissions: 5MB (5 << 20)
- File uploads: 50-100MB (50 << 20 to 100 << 20)
- Image uploads: 10MB (10 << 20)
- Document uploads: 25MB (25 << 20)
Performance Impact:
- Minimal overhead: only checks Content-Length header
- No memory allocation for size validation
- Early rejection prevents wasted CPU cycles
- Zero impact on legitimate requests within limits
func Sessions ¶
func Sessions(cfg SessionConfig) flash.Middleware
Sessions returns middleware that provides secure session management for HTTP requests.
This middleware handles the complete session lifecycle including loading existing sessions, creating new sessions, managing session data, and persisting changes. It integrates with any Store implementation and provides comprehensive security features.
Key Features:
- Automatic session loading and saving
- Secure session ID generation with cryptographic randomness
- Session regeneration to prevent fixation attacks
- Flexible transport via cookies and/or headers
- Configurable security attributes (Secure, HTTPOnly, SameSite)
- Support for both web applications and APIs
- Efficient change tracking to minimize storage operations
- Integration with the flash.Ctx context system
Basic Usage:
app := flash.New()
app.Use(middleware.Sessions(middleware.SessionConfig{
Store: middleware.NewMemoryStore(),
TTL: 24 * time.Hour,
CookieName: "session_id",
HTTPOnly: true,
Secure: true, // Enable in production
}))
app.GET("/login", func(c flash.Ctx) error {
session := middleware.SessionFromCtx(c)
session.Set("user_id", "12345")
return c.JSON(200, map[string]string{"status": "logged in"})
})
Advanced Usage with Security Features:
store := middleware.NewMemoryStore()
store.StartCleanup(5 * time.Minute) // Prevent memory leaks
defer store.StopCleanup()
app.Use(middleware.Sessions(middleware.SessionConfig{
Store: store,
TTL: 12 * time.Hour,
CookieName: "secure_session",
CookiePath: "/",
Domain: ".example.com",
Secure: true,
HTTPOnly: true,
SameSite: http.SameSiteStrictMode,
HeaderName: "X-Session-ID",
IdleTimeout: 30 * time.Minute,
MaxAge: 7 * 24 * time.Hour,
}))
// Login handler with session regeneration
app.POST("/login", func(c flash.Ctx) error {
// Authenticate user...
if !authenticateUser(username, password) {
return c.Status(401).String("Invalid credentials")
}
session := middleware.SessionFromCtx(c)
session.Regenerate() // Prevent session fixation
session.Set("user_id", userID)
session.Set("authenticated", true)
session.Set("login_time", time.Now())
return c.JSON(200, map[string]string{"status": "logged in"})
})
// Logout handler
app.POST("/logout", func(c flash.Ctx) error {
session := middleware.SessionFromCtx(c)
session.Clear() // Clear all session data
return c.JSON(200, map[string]string{"status": "logged out"})
})
API Usage (Header-based):
app.Use(middleware.Sessions(middleware.SessionConfig{
Store: redisStore,
TTL: 2 * time.Hour,
HeaderName: "X-Session-Token",
// No cookie settings for API-only usage
}))
Security Considerations:
- Always set Secure=true in production with HTTPS
- Use HTTPOnly=true to prevent XSS attacks
- Consider SameSite=Strict for maximum CSRF protection
- Implement proper session cleanup to prevent memory leaks
- Use session regeneration after authentication or privilege changes
- Set appropriate TTL values based on your security requirements
- Consider using IdleTimeout for additional security
Performance Considerations:
- Sessions are only saved when data changes (efficient change tracking)
- Use memory-efficient stores like Redis for high-traffic applications
- Configure appropriate cleanup intervals for memory stores
- Consider session data size impact on storage and network
- Use header-based transport for APIs to avoid cookie overhead
func Timeout ¶
func Timeout(cfg TimeoutConfig) flash.Middleware
Timeout returns middleware that applies a timeout to the request context with enhanced security features.
Security Features:
- Proper context cancellation to prevent goroutine leaks
- Safe timeout handling with race condition prevention
- Protected timeout callbacks to prevent secondary failures
Performance Features:
- Efficient goroutine management with proper cleanup
- Minimal overhead for requests that complete within timeout
- Optimized header handling to reduce allocations
Example Usage:
// Basic timeout
app.Use(middleware.Timeout(middleware.TimeoutConfig{
Duration: 30 * time.Second,
}))
// With longer timeout for slow operations
app.Use(middleware.Timeout(middleware.TimeoutConfig{
Duration: 60 * time.Second, // 1 minute for file processing
}))
// With custom timeout handling
app.Use(middleware.Timeout(middleware.TimeoutConfig{
Duration: 30 * time.Second,
OnTimeout: func(c flash.Ctx) {
// Log timeout (non-blocking)
go func() {
log.Printf("Request timeout: %s %s from %s",
c.Method(), c.Path(), c.Request().RemoteAddr)
}()
},
ErrorResponse: func(c flash.Ctx) error {
return c.JSON(http.StatusGatewayTimeout, map[string]interface{}{
"error": "Request timeout",
"code": "GATEWAY_TIMEOUT",
"timeout": "30s",
})
},
}))
func WithLoggerAttributes ¶
func WithLoggerAttributes(ctx context.Context, attrs *LoggerAttributes) context.Context
WithLoggerAttributes adds custom attributes to the context for logging. These attributes will be included in the request log when using the Logger middleware.
Usage Examples:
// Add custom attributes in middleware or handlers
attrs := middleware.NewLoggerAttributes("user_id", "123", "operation", "create")
ctx := middleware.WithLoggerAttributes(c.Context(), attrs)
c.SetRequest(c.Request().WithContext(ctx))
// Or add attributes directly
ctx := middleware.WithLoggerAttributes(c.Context(),
middleware.NewLoggerAttributes("user_id", "123", "operation", "create"))
c.SetRequest(c.Request().WithContext(ctx))
Example ¶
ExampleWithLoggerAttributes demonstrates adding custom attributes to context.
app := flash.New()
app.Use(Logger())
// Middleware that adds custom attributes
app.Use(func(next flash.Handler) flash.Handler {
return func(c flash.Ctx) error {
// Add custom attributes to context
attrs := NewLoggerAttributes("middleware", "auth", "version", "v2")
ctx := WithLoggerAttributes(c.Context(), attrs)
c.SetRequest(c.Request().WithContext(ctx))
return next(c)
}
})
app.GET("/users/:id", func(c flash.Ctx) error {
// Add dynamic attributes in handler
userID := c.Param("id")
attrs := NewLoggerAttributes("user_id", userID, "operation", "fetch")
ctx := WithLoggerAttributes(c.Context(), attrs)
c.SetRequest(c.Request().WithContext(ctx))
return c.JSON(map[string]string{"id": userID})
})
Types ¶
type AdaptiveStrategy ¶
type AdaptiveStrategy struct {
// contains filtered or unexported fields
}
AdaptiveStrategy implements an adaptive rate limiting algorithm. This strategy adjusts the rate limit based on the client's behavior.
func NewAdaptiveStrategy ¶
func NewAdaptiveStrategy(baseRate, minRate, maxRate float64, window time.Duration) *AdaptiveStrategy
NewAdaptiveStrategy creates a new adaptive rate limiter.
Parameters:
- baseRate: Initial requests per second
- minRate: Minimum requests per second
- maxRate: Maximum requests per second
- window: Window for rate calculation
Usage Examples:
// Adaptive rate limiting with 10-100 requests per second range strategy := middleware.NewAdaptiveStrategy(50.0, 10.0, 100.0, time.Minute) app.Use(middleware.RateLimit(middleware.WithStrategy(strategy)))
Example ¶
ExampleNewAdaptiveStrategy demonstrates creating an adaptive strategy.
// Adaptive rate limiting with 10-100 requests per second range strategy := NewAdaptiveStrategy(50.0, 10.0, 100.0, time.Minute) // Very restrictive adaptive limiting strictStrategy := NewAdaptiveStrategy(1.0, 0.1, 10.0, time.Minute) // Use in middleware app := flash.New() app.Use(RateLimit(WithStrategy(strategy))) _ = strictStrategy
func (*AdaptiveStrategy) Allow ¶
func (as *AdaptiveStrategy) Allow(key string) (bool, time.Duration)
func (*AdaptiveStrategy) Close ¶
func (as *AdaptiveStrategy) Close()
Close stops the cleanup goroutine
func (*AdaptiveStrategy) Name ¶
func (as *AdaptiveStrategy) Name() string
func (*AdaptiveStrategy) UpdateRate ¶
func (as *AdaptiveStrategy) UpdateRate(key string, isGood bool)
UpdateRate updates the rate for a specific client based on their behavior. Call this method from your application logic to provide feedback.
type BufferConfig ¶
type BufferConfig struct {
InitialSize int // preallocated buffer size
MaxSize int // max buffer size before switching to streaming
}
BufferConfig configures the write-buffering middleware.
InitialSize preallocates the internal response buffer capacity to reduce growth reallocations for small/medium payloads. MaxSize limits how many bytes are buffered before switching to streaming mode. When the buffered bytes would exceed MaxSize, the middleware flushes any buffered content without a Content-Length and routes subsequent writes directly to the underlying ResponseWriter.
Notes and recommendations:
- Set MaxSize to a sensible ceiling to avoid unbounded memory use for very large responses (MaxSize=0 means unbounded buffering).
- This middleware is not suitable for server-sent events or long-lived streaming responses. Use it for bounded payloads (JSON, HTML, small files).
- For HEAD responses where no body is written, no buffer is allocated at all.
Example:
app.Use(middleware.Buffer(middleware.BufferConfig{
InitialSize: 8 << 10, // 8KB
MaxSize: 1 << 20, // 1MB
}))
type CORSConfig ¶
type CORSConfig struct {
// Origins specifies allowed origins for cross-origin requests.
// If empty, no Access-Control-Allow-Origin header is set.
// Use "*" to allow all origins (not recommended for production).
Origins []string
// Methods specifies allowed HTTP methods for cross-origin requests.
// If empty, defaults to common methods: GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS.
Methods []string
// Headers specifies allowed request headers for cross-origin requests.
// Common values include: Content-Type, Authorization, X-Requested-With.
Headers []string
// Expose specifies response headers that browsers can access via JavaScript.
// Common values include: X-Total-Count, X-Page-Count, X-Rate-Limit-*.
Expose []string
// Credentials enables sending cookies and authorization headers in cross-origin requests.
// When true, sets Access-Control-Allow-Credentials: true.
// Note: Cannot be used with Origins: ["*"].
Credentials bool
// MaxAge sets the duration (in seconds) that browsers can cache preflight responses.
// This reduces the number of OPTIONS requests for subsequent requests.
// Common values: 86400 (24 hours), 3600 (1 hour), 0 (no cache).
MaxAge int
}
CORSConfig holds configuration for the CORS middleware.
Origins, Methods, and Headers control allowed cross-origin requests. Expose lists headers exposed to the browser. Credentials enables cookies. MaxAge sets preflight cache duration (seconds).
Security considerations:
- Use specific origins rather than "*" when possible
- Only expose headers that are necessary for your application
- Be cautious with Credentials=true as it allows cookies in cross-origin requests
- Set appropriate MaxAge to balance security and performance
Example:
cfg := middleware.CORSConfig{
Origins: []string{"https://app.example.com", "https://admin.example.com"},
Methods: []string{"GET", "POST", "PUT", "DELETE"},
Headers: []string{"Content-Type", "Authorization"},
Expose: []string{"X-Total-Count"},
Credentials: true,
MaxAge: 86400, // 24 hours
}
app.Use(middleware.CORS(cfg))
type CSRFConfig ¶
type CSRFConfig struct {
// CookieName specifies the name of the CSRF cookie.
// Common values: "_csrf", "csrf_token", "XSRF-TOKEN".
CookieName string
// HeaderName specifies the name of the header where the CSRF token is expected.
// Common values: "X-CSRF-Token", "X-XSRF-Token", "X-CSRF-Header".
HeaderName string
// TokenLength sets the length of the generated token in bytes.
// Recommended: 32 bytes (256 bits) for adequate security.
// The actual token string will be longer due to base64 encoding.
TokenLength int
// CookiePath sets the path attribute of the CSRF cookie.
// Use "/" to apply to the entire domain.
CookiePath string
// CookieDomain sets the domain attribute of the CSRF cookie.
// Leave empty for current domain only.
CookieDomain string
// CookieSecure sets the Secure flag on the CSRF cookie.
// Should be true in production (HTTPS required).
CookieSecure bool
// CookieHTTPOnly sets the HttpOnly flag on the CSRF cookie.
// Should be true to prevent XSS attacks from stealing the token.
CookieHTTPOnly bool
// CookieSameSite sets the SameSite policy for the CSRF cookie.
// Recommended: http.SameSiteLaxMode for most applications.
CookieSameSite http.SameSite
// TTL sets the expiration time for the CSRF cookie.
// Balance security (shorter) with user experience (longer).
// Common values: 12 hours, 24 hours, 7 days.
TTL time.Duration
}
CSRFConfig configures the CSRF middleware.
This middleware implements the double-submit cookie pattern for CSRF protection. A cryptographically secure token is generated and stored in both a cookie and expected in a header for unsafe HTTP methods (POST, PUT, PATCH, DELETE).
Security considerations:
- Use HTTPS in production (CookieSecure: true)
- Set appropriate SameSite policy (SameSiteLaxMode recommended)
- Use HttpOnly cookies to prevent XSS token theft
- Ensure TokenLength is sufficient (32 bytes minimum recommended)
- Set reasonable TTL to balance security and user experience
Example:
cfg := middleware.CSRFConfig{
CookieName: "_csrf",
HeaderName: "X-CSRF-Token",
TokenLength: 32,
CookieSecure: true,
CookieHTTPOnly: true,
CookieSameSite: http.SameSiteLaxMode,
TTL: 12 * time.Hour,
}
app.Use(middleware.CSRF(cfg))
func DefaultCSRFConfig ¶
func DefaultCSRFConfig() CSRFConfig
DefaultCSRFConfig returns a safe default configuration for CSRF protection.
The default configuration provides strong security with reasonable usability:
- 32-byte tokens (256 bits of entropy)
- Secure, HttpOnly cookies
- SameSite=Lax policy
- 12-hour expiration
- Standard cookie and header names
Example:
app.Use(middleware.CSRF()) // uses DefaultCSRFConfig()
type FixedWindowStrategy ¶
type FixedWindowStrategy struct {
// contains filtered or unexported fields
}
FixedWindowStrategy implements a fixed window rate limiting algorithm. This strategy resets the counter at fixed intervals, allowing bursts at window boundaries.
func NewFixedWindowStrategy ¶
func NewFixedWindowStrategy(limit int, window time.Duration) *FixedWindowStrategy
NewFixedWindowStrategy creates a new fixed window rate limiter.
Parameters:
- limit: Maximum number of requests per window
- window: Duration of each window
Usage Examples:
// 100 requests per minute window strategy := middleware.NewFixedWindowStrategy(100, time.Minute) app.Use(middleware.RateLimit(middleware.WithStrategy(strategy))) // 1000 requests per hour window strategy := middleware.NewFixedWindowStrategy(1000, time.Hour) app.Use(middleware.RateLimit(middleware.WithStrategy(strategy)))
Example ¶
ExampleNewFixedWindowStrategy demonstrates creating a fixed window strategy.
// 100 requests per hour window strategy := NewFixedWindowStrategy(100, time.Hour) // 10 requests per day window dailyStrategy := NewFixedWindowStrategy(10, 24*time.Hour) // Use in middleware app := flash.New() app.Use(RateLimit(WithStrategy(strategy))) _ = dailyStrategy
func (*FixedWindowStrategy) Allow ¶
func (fw *FixedWindowStrategy) Allow(key string) (bool, time.Duration)
func (*FixedWindowStrategy) Close ¶
func (fw *FixedWindowStrategy) Close()
Close stops the cleanup goroutine
func (*FixedWindowStrategy) Name ¶
func (fw *FixedWindowStrategy) Name() string
type LeakyBucketStrategy ¶
type LeakyBucketStrategy struct {
// contains filtered or unexported fields
}
LeakyBucketStrategy implements a leaky bucket rate limiting algorithm. This strategy processes requests at a fixed rate, queuing excess requests.
func NewLeakyBucketStrategy ¶
func NewLeakyBucketStrategy(rate float64, capacity int) *LeakyBucketStrategy
NewLeakyBucketStrategy creates a new leaky bucket rate limiter.
Parameters:
- rate: Requests per second (can be fractional)
- capacity: Maximum number of requests that can be queued
Usage Examples:
// 10 requests per second with capacity for 50 queued requests strategy := middleware.NewLeakyBucketStrategy(10.0, 50) app.Use(middleware.RateLimit(middleware.WithStrategy(strategy))) // 0.5 requests per second (1 request every 2 seconds) strategy := middleware.NewLeakyBucketStrategy(0.5, 10) app.Use(middleware.RateLimit(middleware.WithStrategy(strategy)))
Example ¶
ExampleNewLeakyBucketStrategy demonstrates creating a leaky bucket strategy.
// 5 requests per second with capacity for 20 queued requests strategy := NewLeakyBucketStrategy(5.0, 20) // 0.1 requests per second (1 request every 10 seconds) slowStrategy := NewLeakyBucketStrategy(0.1, 5) // Use in middleware app := flash.New() app.Use(RateLimit(WithStrategy(strategy))) _ = slowStrategy
func (*LeakyBucketStrategy) Allow ¶
func (lb *LeakyBucketStrategy) Allow(key string) (bool, time.Duration)
func (*LeakyBucketStrategy) Close ¶
func (lb *LeakyBucketStrategy) Close()
Close stops the cleanup goroutine
func (*LeakyBucketStrategy) Name ¶
func (lb *LeakyBucketStrategy) Name() string
type LoggerAttributeKey ¶
type LoggerAttributeKey struct{}
LoggerAttributeKey is the context key for storing custom logger attributes. Use this key to store custom attributes that will be included in request logs.
type LoggerAttributes ¶
type LoggerAttributes struct {
// contains filtered or unexported fields
}
LoggerAttributes represents custom attributes to be included in request logs. This type allows for efficient storage and retrieval of custom attributes.
func LoggerAttributesFromContext ¶
func LoggerAttributesFromContext(ctx context.Context) *LoggerAttributes
LoggerAttributesFromContext retrieves custom logger attributes from the context. Returns nil if no attributes are found.
func NewLoggerAttributes ¶
func NewLoggerAttributes(pairs ...any) *LoggerAttributes
NewLoggerAttributes creates a new LoggerAttributes instance with the given key-value pairs. This function is optimized for zero allocations when possible.
Example ¶
ExampleNewLoggerAttributes demonstrates creating logger attributes.
// Create attributes with key-value pairs
attrs := NewLoggerAttributes("user_id", "123", "operation", "create")
// Add more attributes
attrs.Add("tenant_id", "tenant_456", "environment", "production")
// Use in context
ctx := context.Background()
ctx = WithLoggerAttributes(ctx, attrs)
// The attributes will be included in request logs when using Logger middleware
_ = ctx
func (*LoggerAttributes) Add ¶
func (la *LoggerAttributes) Add(pairs ...any)
Add appends key-value pairs to the attributes. This method is optimized for minimal allocations.
type LoggerConfig ¶
type LoggerConfig struct {
// ExcludeFields specifies which standard fields to exclude from logging.
// Valid values: "method", "path", "route", "status", "duration_ms", "remote", "user_agent", "request_id"
ExcludeFields []string
// CustomAttributesFunc is an optional function that can add custom attributes
// based on the request context. This function is called for each request
// and should return key-value pairs to be included in the log.
CustomAttributesFunc func(c flash.Ctx) []any
// Message is the log message to use. Defaults to "request".
Message string
}
LoggerConfig holds configuration options for the Logger middleware.
type LoggerOption ¶
type LoggerOption func(*LoggerConfig)
LoggerOption is a function that configures the Logger middleware.
func WithCustomAttributes ¶
func WithCustomAttributes(fn func(c flash.Ctx) []any) LoggerOption
WithCustomAttributes adds a function that can provide custom attributes for each request.
Usage Examples:
// Add user ID from authentication middleware
app.Use(middleware.Logger(middleware.WithCustomAttributes(func(c flash.Ctx) []any {
if userID := c.Context().Value("user_id"); userID != nil {
return []any{"user_id", userID}
}
return nil
})))
// Add multiple custom attributes
app.Use(middleware.Logger(middleware.WithCustomAttributes(func(c flash.Ctx) []any {
attrs := make([]any, 0, 4)
if userID := c.Context().Value("user_id"); userID != nil {
attrs = append(attrs, "user_id", userID)
}
if tenantID := c.Context().Value("tenant_id"); tenantID != nil {
attrs = append(attrs, "tenant_id", tenantID)
}
return attrs
})))
func WithExcludeFields ¶
func WithExcludeFields(fields ...string) LoggerOption
WithExcludeFields excludes specific standard fields from logging.
Usage Examples:
// Exclude user agent and remote address for privacy
app.Use(middleware.Logger(middleware.WithExcludeFields("user_agent", "remote")))
// Exclude multiple fields
app.Use(middleware.Logger(middleware.WithExcludeFields("user_agent", "remote", "request_id")))
func WithMessage ¶
func WithMessage(message string) LoggerOption
WithMessage sets a custom log message instead of the default "request".
Usage Examples:
// Use a custom message
app.Use(middleware.Logger(middleware.WithMessage("http_request")))
// Use different messages for different route groups
api.Use(middleware.Logger(middleware.WithMessage("api_request")))
web.Use(middleware.Logger(middleware.WithMessage("web_request")))
type MemoryStore ¶
type MemoryStore struct {
// contains filtered or unexported fields
}
MemoryStore is an in-memory session store with TTL and automatic cleanup. Suitable for development, testing, and single-instance production deployments.
Features:
- Thread-safe operations with optimized read-write locking
- Automatic cleanup of expired sessions via background goroutine
- Timing attack protection for session ID lookups
- Memory-efficient storage with lazy expiration checking
- Configurable cleanup intervals
- Proper resource cleanup on shutdown
Security considerations:
- Uses timing-safe comparison to prevent session enumeration
- Automatically removes expired sessions to prevent memory leaks
- Safe for concurrent access from multiple goroutines
Example usage:
// Create store with automatic cleanup every 5 minutes
store := middleware.NewMemoryStore()
store.StartCleanup(5 * time.Minute)
defer store.StopCleanup()
// Use with session middleware
app.Use(middleware.Sessions(middleware.SessionConfig{
Store: store,
TTL: 24 * time.Hour,
}))
func NewMemoryStore ¶
func NewMemoryStore() *MemoryStore
NewMemoryStore creates a new in-memory session store. Call StartCleanup() to enable automatic cleanup of expired sessions.
Example:
store := middleware.NewMemoryStore() store.StartCleanup(10 * time.Minute) // Clean up every 10 minutes defer store.StopCleanup()
func (*MemoryStore) Delete ¶
func (m *MemoryStore) Delete(id string) error
Delete removes session data by ID. Idempotent operation - no error if the ID doesn't exist.
func (*MemoryStore) Get ¶
func (m *MemoryStore) Get(id string) (map[string]any, bool)
Get retrieves session data by ID with timing attack protection. Returns a copy of the session data to prevent external modification.
func (*MemoryStore) Len ¶
func (m *MemoryStore) Len() int
Len returns the current number of sessions in the store. Useful for monitoring and debugging.
func (*MemoryStore) Save ¶
Save persists session data with the given ID and TTL. Creates a deep copy of the data to prevent external modification.
func (*MemoryStore) StartCleanup ¶
func (m *MemoryStore) StartCleanup(interval time.Duration)
StartCleanup starts a background goroutine that periodically removes expired sessions. This prevents memory leaks in long-running applications.
Example:
store := middleware.NewMemoryStore() store.StartCleanup(5 * time.Minute) // Clean up every 5 minutes defer store.StopCleanup()
func (*MemoryStore) StopCleanup ¶
func (m *MemoryStore) StopCleanup()
StopCleanup stops the background cleanup goroutine. Should be called when the store is no longer needed to prevent goroutine leaks.
type RateLimitConfig ¶
type RateLimitConfig struct {
// Strategy is the rate limiting strategy to use.
// This determines the algorithm and behavior of rate limiting.
// If nil, defaults to TokenBucketStrategy(100, time.Minute).
//
// Available strategies:
// - TokenBucketStrategy: Allows bursts, good for API rate limiting
// - FixedWindowStrategy: Simple, can have burst issues at boundaries
// - SlidingWindowStrategy: Smooth rate limiting, higher memory usage
// - LeakyBucketStrategy: Constant rate processing, queues excess
// - AdaptiveStrategy: Adjusts based on client behavior
Strategy RateLimitStrategy
// KeyFunc is a function that extracts the key for rate limiting from the request.
// The key identifies different clients/users for separate rate limiting.
// If nil, defaults to secure client IP extraction.
//
// Common key extraction patterns:
// - IP-based: func(c flash.Ctx) string { return clientIP(c.Request()) }
// - User-based: func(c flash.Ctx) string { return c.Get("user_id").(string) }
// - API key-based: func(c flash.Ctx) string { return c.Header("X-API-Key") }
// - Combined: func(c flash.Ctx) string { return userID + ":" + clientIP }
KeyFunc func(c flash.Ctx) string
// ErrorResponse is a function that generates a custom error response when rate limited.
// If nil, defaults to HTTP 429 with "Too Many Requests" message and Retry-After header.
//
// The retryAfter parameter indicates when the client should retry the request.
// This is automatically set as the Retry-After header in the default implementation.
//
// Example custom responses:
// - JSON: return c.Status(429).JSON(map[string]any{"error": "rate_limited"})
// - HTML: return c.Status(429).String("Rate limited. Try again later.")
// - Custom headers: c.Header("X-RateLimit-Remaining", "0"); return c.Status(429)
ErrorResponse func(c flash.Ctx, retryAfter time.Duration) error
// SkipFunc is a function that determines if rate limiting should be skipped for this request.
// If nil, no requests are skipped. If returns true, the request bypasses rate limiting entirely.
//
// Common skip patterns:
// - Admin users: func(c flash.Ctx) bool { return c.Get("is_admin") == true }
// - Internal requests: func(c flash.Ctx) bool { return c.Header("X-Internal") != "" }
// - Health checks: func(c flash.Ctx) bool { return c.Path() == "/health" }
// - Whitelisted IPs: func(c flash.Ctx) bool { return isWhitelisted(clientIP(c.Request())) }
SkipFunc func(c flash.Ctx) bool
// TrustedProxies is a list of trusted proxy IP ranges for X-Forwarded-For header validation.
// This is critical for security when behind load balancers or CDNs.
// If empty, all X-Forwarded-For headers are trusted (less secure).
// Use CIDR notation for IP ranges.
//
// Common configurations:
// - AWS ALB: []string{"10.0.0.0/8", "172.16.0.0/12"}
// - Cloudflare: []string{"103.21.244.0/22", "103.22.200.0/22", ...}
// - Internal network: []string{"192.168.0.0/16", "10.0.0.0/8"}
// - Kubernetes: []string{"10.244.0.0/16"} // pod CIDR
TrustedProxies []string
// MaxKeyLength is the maximum allowed length for rate limiting keys.
// This prevents memory exhaustion attacks through excessively long keys.
// If 0, defaults to 256 characters.
//
// Recommended values:
// - IP-based keys: 45 (IPv6 max length)
// - User ID keys: 64-128 (depending on ID format)
// - API key-based: 128-256 (depending on key format)
// - Combined keys: 256-512
MaxKeyLength int
// CleanupInterval is how often to clean up expired entries from memory.
// This prevents memory leaks from accumulating stale rate limiting data.
// If 0, defaults to 5 minutes. Set to -1 to disable cleanup.
//
// Tuning guidelines:
// - High traffic: 1-5 minutes (frequent cleanup)
// - Low traffic: 10-30 minutes (less frequent cleanup)
// - Memory constrained: 1-2 minutes (aggressive cleanup)
// - Performance critical: 10+ minutes (less CPU overhead)
CleanupInterval time.Duration
}
RateLimitConfig holds configuration for the RateLimit middleware. It provides comprehensive options to customize rate limiting behavior, security settings, and performance characteristics.
Example basic configuration:
config := &RateLimitConfig{
Strategy: middleware.NewTokenBucketStrategy(100, time.Minute),
}
Example advanced configuration:
config := &RateLimitConfig{
Strategy: middleware.NewTokenBucketStrategy(1000, time.Hour),
KeyFunc: func(c flash.Ctx) string {
// Rate limit by user ID instead of IP
if userID := c.Get("user_id"); userID != nil {
return fmt.Sprintf("user:%v", userID)
}
return "anonymous"
},
ErrorResponse: func(c flash.Ctx, retryAfter time.Duration) error {
return c.Status(429).JSON(map[string]any{
"error": "Rate limit exceeded",
"retry_after_seconds": int(retryAfter.Seconds()),
"message": "Please slow down your requests",
})
},
SkipFunc: func(c flash.Ctx) bool {
// Skip rate limiting for admin users
return c.Get("is_admin") == true
},
TrustedProxies: []string{"10.0.0.0/8", "172.16.0.0/12"},
MaxKeyLength: 128,
CleanupInterval: 10 * time.Minute,
}
type RateLimitOption ¶
type RateLimitOption func(*RateLimitConfig)
RateLimitOption is a function that configures the RateLimit middleware. Options follow the functional options pattern for flexible configuration.
Example usage:
app.Use(middleware.RateLimit(
middleware.WithStrategy(middleware.NewTokenBucketStrategy(100, time.Minute)),
middleware.WithKeyFunc(func(c flash.Ctx) string {
return c.Get("user_id").(string)
}),
middleware.WithTrustedProxies([]string{"10.0.0.0/8"}),
))
func WithCleanupInterval ¶
func WithCleanupInterval(interval time.Duration) RateLimitOption
WithCleanupInterval sets how often to clean up expired entries from memory. This prevents memory leaks from accumulating stale rate limiting data. Set to -1 to disable automatic cleanup (not recommended for production).
Tuning guidelines:
- High traffic applications: 1-5 minutes (frequent cleanup)
- Low traffic applications: 10-30 minutes (less frequent cleanup)
- Memory-constrained environments: 1-2 minutes (aggressive cleanup)
- Performance-critical applications: 10+ minutes (less CPU overhead)
Examples:
// High-traffic API with frequent cleanup middleware.WithCleanupInterval(2 * time.Minute) // Low-traffic application with less frequent cleanup middleware.WithCleanupInterval(15 * time.Minute) // Memory-constrained environment middleware.WithCleanupInterval(1 * time.Minute) // Disable cleanup (not recommended for production) middleware.WithCleanupInterval(-1)
Note: Each rate limiting strategy runs its own cleanup goroutine. The cleanup process is lightweight and runs in the background.
func WithErrorResponse ¶
func WithErrorResponse(errorResponse func(c flash.Ctx, retryAfter time.Duration) error) RateLimitOption
WithErrorResponse sets a custom error response function. This allows customizing the response when rate limiting is triggered. If not set, defaults to HTTP 429 with "Too Many Requests" message.
Examples:
// JSON error response with additional information
middleware.WithErrorResponse(func(c flash.Ctx, retryAfter time.Duration) error {
return c.Status(429).JSON(map[string]any{
"error": "rate_limit_exceeded",
"message": "Too many requests. Please slow down.",
"retry_after_seconds": int(retryAfter.Seconds()),
"documentation": "https://api.example.com/docs/rate-limits",
})
})
// Custom HTML error page
middleware.WithErrorResponse(func(c flash.Ctx, retryAfter time.Duration) error {
c.Header("Retry-After", fmt.Sprintf("%.0f", retryAfter.Seconds()))
return c.Status(429).String(`
<h1>Rate Limited</h1>
<p>You've made too many requests. Please wait %d seconds.</p>
`, int(retryAfter.Seconds()))
})
// Custom headers with default response
middleware.WithErrorResponse(func(c flash.Ctx, retryAfter time.Duration) error {
c.Header("X-RateLimit-Remaining", "0")
c.Header("X-RateLimit-Reset", fmt.Sprintf("%d", time.Now().Add(retryAfter).Unix()))
return c.Status(429).String("Rate limit exceeded")
})
func WithKeyFunc ¶
func WithKeyFunc(keyFunc func(c flash.Ctx) string) RateLimitOption
WithKeyFunc sets a custom key extraction function. The key identifies different clients for separate rate limiting. If not set, defaults to secure client IP extraction.
Common patterns:
// Rate limit by authenticated user ID
middleware.WithKeyFunc(func(c flash.Ctx) string {
if userID := c.Get("user_id"); userID != nil {
return fmt.Sprintf("user:%v", userID)
}
return "anonymous"
})
// Rate limit by API key
middleware.WithKeyFunc(func(c flash.Ctx) string {
apiKey := c.Header("X-API-Key")
if apiKey == "" {
return "no-key"
}
return "api:" + apiKey
})
// Combined user + IP rate limiting
middleware.WithKeyFunc(func(c flash.Ctx) string {
userID := c.Get("user_id")
clientIP := clientIP(c.Request())
return fmt.Sprintf("%v:%s", userID, clientIP)
})
// Rate limit by request path (global per-endpoint limiting)
middleware.WithKeyFunc(func(c flash.Ctx) string {
return c.Route() // e.g., "/api/users/:id"
})
func WithMaxKeyLength ¶
func WithMaxKeyLength(maxLength int) RateLimitOption
WithMaxKeyLength sets the maximum allowed length for rate limiting keys. This prevents memory exhaustion attacks through excessively long keys. Keys longer than this limit will be truncated.
Recommended values based on key type:
- IP addresses: 45 characters (IPv6 max length)
- User IDs: 64-128 characters (depending on your ID format)
- API keys: 128-256 characters (depending on key format)
- Combined keys: 256-512 characters
Examples:
// For IP-based rate limiting middleware.WithMaxKeyLength(45) // For user ID-based rate limiting middleware.WithMaxKeyLength(128) // For API key-based rate limiting middleware.WithMaxKeyLength(256) // For combined keys (user:ip:endpoint) middleware.WithMaxKeyLength(512)
Security note: Setting this too high can allow memory exhaustion attacks. Setting this too low may cause key collisions. Choose based on your key format.
func WithSkipFunc ¶
func WithSkipFunc(skipFunc func(c flash.Ctx) bool) RateLimitOption
WithSkipFunc sets a function that determines if rate limiting should be skipped. If the function returns true, the request bypasses rate limiting entirely. This is useful for whitelisting certain requests or users.
Examples:
// Skip rate limiting for admin users
middleware.WithSkipFunc(func(c flash.Ctx) bool {
return c.Get("is_admin") == true
})
// Skip rate limiting for internal services
middleware.WithSkipFunc(func(c flash.Ctx) bool {
return c.Header("X-Internal-Service") != ""
})
// Skip rate limiting for health check endpoints
middleware.WithSkipFunc(func(c flash.Ctx) bool {
path := c.Path()
return path == "/health" || path == "/metrics" || path == "/ready"
})
// Skip rate limiting for whitelisted IP addresses
middleware.WithSkipFunc(func(c flash.Ctx) bool {
clientIP := clientIP(c.Request())
whitelist := []string{"192.168.1.100", "10.0.0.5"}
for _, ip := range whitelist {
if clientIP == ip {
return true
}
}
return false
})
// Skip rate limiting during maintenance windows
middleware.WithSkipFunc(func(c flash.Ctx) bool {
return c.Header("X-Maintenance-Mode") == "true"
})
func WithStrategy ¶
func WithStrategy(strategy RateLimitStrategy) RateLimitOption
WithStrategy sets the rate limiting strategy. This determines the algorithm used for rate limiting.
Available built-in strategies:
- TokenBucketStrategy: Best for API rate limiting, allows bursts
- FixedWindowStrategy: Simple and memory efficient
- SlidingWindowStrategy: Smooth rate limiting, no boundary bursts
- LeakyBucketStrategy: Constant rate processing
- AdaptiveStrategy: Adjusts based on client behavior
Example:
// Token bucket allowing 100 requests per minute with burst capability app.Use(middleware.RateLimit( middleware.WithStrategy(middleware.NewTokenBucketStrategy(100, time.Minute)), )) // Fixed window allowing 1000 requests per hour app.Use(middleware.RateLimit( middleware.WithStrategy(middleware.NewFixedWindowStrategy(1000, time.Hour)), ))
func WithTrustedProxies ¶
func WithTrustedProxies(proxies []string) RateLimitOption
WithTrustedProxies sets trusted proxy IP ranges for secure X-Forwarded-For header processing. This is critical for security when your application is behind load balancers, CDNs, or reverse proxies. Only X-Forwarded-For headers from these trusted sources will be used for client IP extraction.
Use CIDR notation for IP ranges. If empty, all X-Forwarded-For headers are trusted (less secure).
Common configurations:
// AWS Application Load Balancer (private subnets)
middleware.WithTrustedProxies([]string{
"10.0.0.0/8", // Private class A
"172.16.0.0/12", // Private class B
"192.168.0.0/16", // Private class C
})
// Cloudflare IP ranges (partial list - check Cloudflare docs for complete list)
middleware.WithTrustedProxies([]string{
"103.21.244.0/22",
"103.22.200.0/22",
"103.31.4.0/22",
"104.16.0.0/13",
// ... more Cloudflare ranges
})
// Kubernetes cluster (pod and service CIDRs)
middleware.WithTrustedProxies([]string{
"10.244.0.0/16", // Pod CIDR
"10.96.0.0/12", // Service CIDR
})
// Google Cloud Load Balancer
middleware.WithTrustedProxies([]string{
"130.211.0.0/22",
"35.191.0.0/16",
})
// Single trusted proxy
middleware.WithTrustedProxies([]string{"192.168.1.100/32"})
type RateLimitStrategy ¶
type RateLimitStrategy interface {
// Allow checks if a request is allowed for the given key and returns:
// - allowed: true if the request should proceed, false if it should be blocked
// - retryAfter: duration to wait before retrying (only meaningful when allowed is false)
//
// The key parameter is typically a client identifier (IP address, user ID, API key, etc.)
// extracted by the KeyFunc. Strategies use this key to maintain separate rate limiting
// state for different clients.
//
// When a request is blocked (allowed=false), the retryAfter duration indicates
// when the client should retry. This value is used to set the Retry-After HTTP header.
Allow(key string) (allowed bool, retryAfter time.Duration)
// Name returns the name of the strategy for identification and debugging.
// This is used in logs, metrics, and error messages to identify which
// rate limiting strategy is being used.
Name() string
}
RateLimitStrategy defines the interface for different rate limiting strategies. All strategies must implement this interface to be used with the RateLimit middleware.
The interface provides a unified way to implement various rate limiting algorithms such as token bucket, fixed window, sliding window, leaky bucket, and adaptive strategies. Each strategy can have different characteristics in terms of burst handling, memory usage, and rate limiting precision.
Example implementation:
type CustomStrategy struct {
// strategy-specific fields
}
func (cs *CustomStrategy) Allow(key string) (bool, time.Duration) {
// Implement rate limiting logic
// Return true if request is allowed, false if blocked
// Return retry duration when blocked
return allowed, retryAfter
}
func (cs *CustomStrategy) Name() string {
return "custom_strategy"
}
Usage with middleware:
strategy := &CustomStrategy{}
app.Use(middleware.RateLimit(middleware.WithStrategy(strategy)))
type RecoverConfig ¶
type RecoverConfig struct {
EnableStack bool // whether to log stack traces (disable in production)
OnPanic func(flash.Ctx, interface{}) // optional callback when panic occurs
ErrorResponse func(flash.Ctx, interface{}) error // optional custom error response
}
RecoverConfig configures the panic recovery middleware.
EnableStack controls whether stack traces are logged (disabled in production for security). OnPanic is called when a panic occurs, useful for custom logging or alerting. ErrorResponse allows customizing the error response sent to clients.
Security considerations:
- Never expose stack traces to clients in production
- Log panic details for debugging but sanitize client responses
- Use structured logging to avoid log injection attacks
- Consider rate limiting panic notifications to prevent spam
Example:
cfg := middleware.RecoverConfig{
EnableStack: false, // Disable in production
OnPanic: func(c flash.Ctx, err interface{}) {
logger := ctx.LoggerFromContext(c.Context())
logger.Error("panic recovered",
"error", err,
"method", c.Method(),
"path", c.Path(),
"remote_addr", c.Request().RemoteAddr,
)
},
ErrorResponse: func(c flash.Ctx, err interface{}) error {
return c.JSON(http.StatusInternalServerError, map[string]interface{}{
"error": "Internal server error",
"code": "INTERNAL_ERROR",
})
},
}
app.Use(middleware.Recover(cfg))
type RequestIDConfig ¶
type RequestIDConfig struct {
Header string // response header name, default: X-Request-ID
}
RequestIDConfig configures the RequestID middleware. Header sets the response header name (default: X-Request-ID).
type RequestSizeConfig ¶
type RequestSizeConfig struct {
// MaxSize is the maximum allowed request body size in bytes.
// If 0 or negative, no limit is enforced (not recommended for production).
MaxSize int64
// ErrorResponse allows customizing the error response when size limit is exceeded.
// If nil, a default JSON error response is returned.
ErrorResponse func(flash.Ctx, int64, int64) error
}
RequestSizeConfig configures the request size limiting middleware.
MaxSize sets the maximum allowed request body size in bytes. When a request exceeds this limit, the middleware returns a 413 Request Entity Too Large response before the request body is fully read, preventing memory exhaustion.
Security considerations:
- Set MaxSize based on your application's actual needs
- Consider different limits for different endpoints (use route-specific middleware)
- Monitor for potential DoS attacks through large request bodies
- Balance security with legitimate large file uploads
Performance considerations:
- Check is performed before reading the request body (minimal overhead)
- Uses Content-Length header for efficient size checking
- No memory allocation for size validation
- Early rejection prevents unnecessary processing
Example:
// Global request size limit
app.Use(middleware.RequestSize(middleware.RequestSizeConfig{
MaxSize: 10 << 20, // 10MB
}))
// Different limits for different endpoints
api := app.Group("/api")
api.Use(middleware.RequestSize(middleware.RequestSizeConfig{
MaxSize: 1 << 20, // 1MB for API
}))
upload := app.Group("/upload")
upload.Use(middleware.RequestSize(middleware.RequestSizeConfig{
MaxSize: 100 << 20, // 100MB for file uploads
}))
// Custom error response
app.Use(middleware.RequestSize(middleware.RequestSizeConfig{
MaxSize: 5 << 20, // 5MB
ErrorResponse: func(c flash.Ctx, size, limit int64) error {
return c.JSON(http.StatusRequestEntityTooLarge, map[string]interface{}{
"error": "Request too large",
"code": "REQUEST_TOO_LARGE",
"size": size,
"limit": limit,
})
},
}))
type Session ¶
type Session struct {
ID string // Current session ID
Values map[string]any // Session data
// contains filtered or unexported fields
}
Session represents the per-request view of a session with security features. Provides methods for safely managing session data with automatic change tracking and session regeneration capabilities to prevent session fixation attacks.
Example usage:
session := middleware.SessionFromCtx(c)
// Set session data
session.Set("user_id", "12345")
session.Set("authenticated", true)
// Get session data
if userID, ok := session.Get("user_id"); ok {
// Use userID
}
// Regenerate session ID after authentication (security best practice)
session.Regenerate()
// Clear sensitive data
session.Delete("temp_token")
// Clear all session data
session.Clear()
func SessionFromCtx ¶
SessionFromCtx retrieves the Session previously loaded by Sessions middleware. Returns a session instance that can be safely used even if Sessions middleware is not present (returns empty session in that case).
Example usage:
func handler(c flash.Ctx) error {
session := middleware.SessionFromCtx(c)
// Check if user is authenticated
if userID, ok := session.Get("user_id"); ok {
// User is authenticated
return c.JSON(200, map[string]any{"user_id": userID})
}
// User is not authenticated
return c.Status(401).String("Not authenticated")
}
The returned session is safe to use even without Sessions middleware:
session := middleware.SessionFromCtx(c)
session.Set("key", "value") // Safe, but won't be persisted without middleware
Security note: Always check session validity in security-sensitive operations.
func (*Session) Clear ¶
func (s *Session) Clear()
Clear removes all values from the session. Useful for logout operations or when starting fresh.
Example:
// Logout - clear all session data session.Clear()
func (*Session) Delete ¶
Delete removes a value from the session by key. Marks the session as changed, triggering a save operation.
Example:
session.Delete("temp_token")
session.Delete("csrf_token")
func (*Session) Get ¶
Get retrieves a value from the session by key. Returns the value and true if found, nil and false if not found.
Example:
if userID, ok := session.Get("user_id"); ok {
// userID exists and can be used
fmt.Printf("User ID: %v", userID)
}
func (*Session) IsRegenerated ¶
IsRegenerated returns true if the session ID has been regenerated.
func (*Session) Regenerate ¶
func (s *Session) Regenerate()
Regenerate generates a new session ID while preserving session data. This is a critical security measure to prevent session fixation attacks. Should be called after authentication, privilege escalation, or other security-sensitive operations.
Example:
// After successful login
if authenticateUser(username, password) {
session := middleware.SessionFromCtx(c)
session.Regenerate() // Prevent session fixation
session.Set("user_id", userID)
session.Set("authenticated", true)
}
Security note: The old session ID will be automatically cleaned up from the store when the session is saved.
type SessionConfig ¶
type SessionConfig struct {
// Store is the session storage backend.
// If nil, defaults to NewMemoryStore().
Store Store
// TTL is the session time-to-live duration.
// If 0, defaults to 24 hours.
// This affects both cookie expiration and store TTL.
TTL time.Duration
// CookieName is the name of the session cookie.
// If empty, defaults to "flash.sid".
// Set to empty string to disable cookie-based sessions.
CookieName string
// CookiePath sets the Path attribute of the session cookie.
// If empty, defaults to "/".
// Controls which paths the cookie is sent for.
CookiePath string
// Domain sets the Domain attribute of the session cookie.
// If empty, cookie is only sent to the current domain.
// Use ".example.com" to include subdomains.
Domain string
// Secure sets the Secure attribute of the session cookie.
// When true, cookie is only sent over HTTPS connections.
// Should be true in production environments.
Secure bool
// HTTPOnly sets the HttpOnly attribute of the session cookie.
// When true, cookie cannot be accessed via JavaScript.
// Recommended to be true for security (prevents XSS attacks).
HTTPOnly bool
// SameSite sets the SameSite attribute of the session cookie.
// Controls when cookies are sent with cross-site requests.
// Options: http.SameSiteDefaultMode, http.SameSiteLaxMode, http.SameSiteStrictMode, http.SameSiteNoneMode
// Defaults to http.SameSiteLaxMode for balance of security and usability.
SameSite http.SameSite
// HeaderName specifies an HTTP header for session ID transport.
// If set, session ID is read from and written to this header.
// Useful for API clients that don't support cookies.
// Common values: "X-Session-ID", "Authorization", "X-Auth-Token"
HeaderName string
// IdleTimeout is the maximum time a session can be idle before expiring.
// If 0, sessions don't have idle timeout (only absolute TTL applies).
// Helps prevent session hijacking by limiting inactive session lifetime.
IdleTimeout time.Duration
// MaxAge is the absolute maximum lifetime of a session.
// If 0, no absolute maximum is enforced (only TTL applies).
// Forces session regeneration after this duration regardless of activity.
MaxAge time.Duration
// RegenerateOnAuth automatically regenerates session ID on authentication.
// When true, calls session.Regenerate() when certain conditions are met.
// Helps prevent session fixation attacks.
RegenerateOnAuth bool
}
SessionConfig configures the session middleware with comprehensive security and performance options. Provides fine-grained control over session behavior, cookie attributes, and security features.
Example basic configuration:
config := middleware.SessionConfig{
Store: middleware.NewMemoryStore(),
TTL: 24 * time.Hour,
CookieName: "session_id",
HTTPOnly: true,
Secure: true, // Enable in production with HTTPS
}
Example production configuration:
store := middleware.NewMemoryStore()
store.StartCleanup(10 * time.Minute)
config := middleware.SessionConfig{
Store: store,
TTL: 12 * time.Hour,
CookieName: "secure_session",
CookiePath: "/",
Domain: ".example.com",
Secure: true,
HTTPOnly: true,
SameSite: http.SameSiteStrictMode,
HeaderName: "X-Session-ID", // For API clients
IdleTimeout: 30 * time.Minute,
MaxAge: 7 * 24 * time.Hour,
RegenerateOnAuth: true,
}
Example API-only configuration:
config := middleware.SessionConfig{
Store: redisStore,
TTL: 2 * time.Hour,
HeaderName: "Authorization", // Use Authorization header
// No cookie settings for API-only
}
type SlidingWindowStrategy ¶
type SlidingWindowStrategy struct {
// contains filtered or unexported fields
}
SlidingWindowStrategy implements a sliding window rate limiting algorithm. This strategy provides smooth rate limiting without burst issues at window boundaries.
func NewSlidingWindowStrategy ¶
func NewSlidingWindowStrategy(limit int, window time.Duration) *SlidingWindowStrategy
NewSlidingWindowStrategy creates a new sliding window rate limiter.
Parameters:
- limit: Maximum number of requests per window
- window: Duration of the sliding window
Usage Examples:
// 100 requests per 1-minute sliding window strategy := middleware.NewSlidingWindowStrategy(100, time.Minute) app.Use(middleware.RateLimit(middleware.WithStrategy(strategy))) // 10 requests per 10-second sliding window strategy := middleware.NewSlidingWindowStrategy(10, 10*time.Second) app.Use(middleware.RateLimit(middleware.WithStrategy(strategy)))
Example ¶
ExampleNewSlidingWindowStrategy demonstrates creating a sliding window strategy.
// 50 requests per 5-minute sliding window strategy := NewSlidingWindowStrategy(50, 5*time.Minute) // 1000 requests per hour sliding window hourlyStrategy := NewSlidingWindowStrategy(1000, time.Hour) // Use in middleware app := flash.New() app.Use(RateLimit(WithStrategy(strategy))) _ = hourlyStrategy
func (*SlidingWindowStrategy) Allow ¶
func (sw *SlidingWindowStrategy) Allow(key string) (bool, time.Duration)
func (*SlidingWindowStrategy) Close ¶
func (sw *SlidingWindowStrategy) Close()
Close stops the cleanup goroutine
func (*SlidingWindowStrategy) Name ¶
func (sw *SlidingWindowStrategy) Name() string
type Store ¶
type Store interface {
// Get retrieves session data by ID.
// Returns the session data and true if found, nil and false if not found or expired.
// Implementations should handle expiration checking internally.
Get(id string) (map[string]any, bool)
// Save persists session data with the given ID and TTL.
// If TTL is 0, the session should not expire (or use store default).
// Returns error if the save operation fails.
Save(id string, data map[string]any, ttl time.Duration) error
// Delete removes session data by ID.
// Should be idempotent - no error if the ID doesn't exist.
// Returns error only if the delete operation fails.
Delete(id string) error
}
Store abstracts session persistence for the session middleware. Implementations must provide thread-safe Get, Save, and Delete operations for session data by ID.
The interface is designed to support various storage backends including:
- In-memory stores (for development and testing)
- Database stores (PostgreSQL, MySQL, SQLite)
- Key-value stores (Redis, Memcached, etcd)
- File-based stores (for single-instance deployments)
- Distributed stores (for multi-region deployments)
Example implementation:
type CustomStore struct {
// store-specific fields
}
func (cs *CustomStore) Get(id string) (map[string]any, bool) {
// Retrieve session data by ID
// Return data and true if found, nil and false if not found
// Should handle expiration internally if supported
return data, found
}
func (cs *CustomStore) Save(id string, data map[string]any, ttl time.Duration) error {
// Persist session data with optional TTL
// Return error if save operation fails
// Should be atomic to prevent partial saves
return nil
}
func (cs *CustomStore) Delete(id string) error {
// Remove session data by ID
// Return error if delete operation fails
// Should be idempotent (no error if ID doesn't exist)
return nil
}
Security considerations for implementations:
- Use timing-safe comparison for session ID lookups to prevent timing attacks
- Implement proper cleanup of expired sessions to prevent memory leaks
- Consider encryption at rest for sensitive session data
- Use connection pooling and proper error handling for network-based stores
- Implement proper logging for security auditing
type TimeoutConfig ¶
type TimeoutConfig struct {
Duration time.Duration // request timeout duration (default: 5s)
OnTimeout func(flash.Ctx) // optional callback on timeout (should be non-blocking)
ErrorResponse func(flash.Ctx) error // optional custom error response
}
TimeoutConfig configures the timeout middleware.
Security and performance considerations:
- Duration: Set reasonable timeouts to prevent resource exhaustion (default: 5s)
- OnTimeout: Use for logging and cleanup, avoid blocking operations
- ErrorResponse: Custom timeout responses, should not leak sensitive information
Note: For request size limiting, use the dedicated RequestSize middleware instead.
Example:
cfg := middleware.TimeoutConfig{
Duration: 30 * time.Second,
OnTimeout: func(c flash.Ctx) {
log.Printf("Request timeout: %s %s", c.Method(), c.Path())
},
ErrorResponse: func(c flash.Ctx) error {
return c.JSON(http.StatusGatewayTimeout, map[string]string{
"error": "Request timeout",
"code": "TIMEOUT",
})
},
}
app.Use(middleware.Timeout(cfg))
type TokenBucketStrategy ¶
type TokenBucketStrategy struct {
// contains filtered or unexported fields
}
TokenBucketStrategy implements a token bucket rate limiting algorithm. This strategy allows bursts up to the bucket capacity and refills tokens over time.
func NewTokenBucketStrategy ¶
func NewTokenBucketStrategy(capacity int, refill time.Duration) *TokenBucketStrategy
NewTokenBucketStrategy creates a new token bucket rate limiter.
Parameters:
- capacity: Maximum number of tokens in the bucket
- refill: Duration after which the bucket refills completely
Usage Examples:
// 100 requests per minute with burst support strategy := middleware.NewTokenBucketStrategy(100, time.Minute) app.Use(middleware.RateLimit(middleware.WithStrategy(strategy))) // 10 requests per second (very restrictive) strategy := middleware.NewTokenBucketStrategy(10, time.Second) app.Use(middleware.RateLimit(middleware.WithStrategy(strategy)))
Example ¶
ExampleNewTokenBucketStrategy demonstrates creating a token bucket strategy.
// 100 requests per minute with burst support strategy := NewTokenBucketStrategy(100, time.Minute) // 10 requests per second (very restrictive) strictStrategy := NewTokenBucketStrategy(10, time.Second) // 1000 requests per hour hourlyStrategy := NewTokenBucketStrategy(1000, time.Hour) // Use in middleware app := flash.New() app.Use(RateLimit(WithStrategy(strategy))) _ = strictStrategy _ = hourlyStrategy
func (*TokenBucketStrategy) Allow ¶
func (tb *TokenBucketStrategy) Allow(key string) (bool, time.Duration)
func (*TokenBucketStrategy) Close ¶
func (tb *TokenBucketStrategy) Close()
Close stops the cleanup goroutine
func (*TokenBucketStrategy) Name ¶
func (tb *TokenBucketStrategy) Name() string