Complete API reference for the Go Rate Limiter library.
Configuration structure for creating a rate limiter with advanced options.
type Config struct {
Limit int // Maximum number of requests allowed in the window
Window time.Duration // Time window for the rate limit
BurstSize int // Additional tokens for burst traffic (optional)
SkipPaths []string // HTTP paths to skip rate limiting (optional)
TrustedIPs []string // IP addresses to skip rate limiting (optional)
}Fields:
Limit: The maximum number of requests allowed within the specified windowWindow: Duration of the rate limiting window (e.g.,time.Minute,time.Hour)BurstSize: Additional tokens added to handle traffic bursts (default: 0)SkipPaths: Slice of URL paths that bypass rate limitingTrustedIPs: Slice of IP addresses that bypass rate limiting
The main rate limiter struct that implements the token bucket algorithm.
type Limiter struct {
// Callback functions
OnLimitReached func(w http.ResponseWriter, r *http.Request)
OnAllow func(r *http.Request, remainingTokens float64)
// ... other internal fields
}Public Fields:
OnLimitReached: Called when rate limit is exceeded (HTTP requests only)OnAllow: Called when a request is allowed (HTTP requests only)
Statistics tracking for the rate limiter.
type Stats struct {
TotalRequests int64
AllowedRequests int64
BlockedRequests int64
// ... internal mutex
}Creates a new rate limiter with basic configuration.
func New(limit int, window time.Duration) *LimiterParameters:
limit: Maximum number of requests allowedwindow: Time window duration
Returns: *Limiter
Example:
// 100 requests per minute
limiter := ratelimit.New(100, time.Minute)Creates a new rate limiter with advanced configuration options.
func NewWithConfig(config Config) *LimiterParameters:
config: Configuration struct with all options
Returns: *Limiter
Example:
config := ratelimit.Config{
Limit: 1000,
Window: time.Hour,
BurstSize: 100,
SkipPaths: []string{"/health", "/ping"},
TrustedIPs: []string{"127.0.0.1", "10.0.0.0/8"},
}
limiter := ratelimit.NewWithConfig(config)Checks if a single request should be allowed.
func (l *Limiter) Allow() boolReturns: bool - true if request is allowed, false if rate limited
Example:
if limiter.Allow() {
// Process the request
processRequest()
} else {
// Handle rate limit exceeded
handleRateLimit()
}Checks if N requests should be allowed simultaneously.
func (l *Limiter) AllowN(n int) boolParameters:
n: Number of requests to check
Returns: bool - true if all N requests are allowed
Example:
// Check if we can process a batch of 5 requests
if limiter.AllowN(5) {
processBatchRequests(5)
} else {
handleRateLimit()
}Blocks until a request can be allowed or the context is cancelled.
func (l *Limiter) Wait(ctx context.Context) errorParameters:
ctx: Context for cancellation and timeout
Returns: error - Context error if cancelled/timed out, nil on success
Example:
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := limiter.Wait(ctx); err != nil {
if err == context.DeadlineExceeded {
handleTimeout()
} else {
handleCancellation()
}
} else {
// Request is now allowed
processRequest()
}Returns the current number of available tokens.
func (l *Limiter) RemainingTokens() float64Returns: float64 - Number of available tokens (can be fractional)
Example:
remaining := limiter.RemainingTokens()
fmt.Printf("Available tokens: %.2f\n", remaining)
// Check if we have enough tokens before making a request
if remaining >= 5 {
processBatchRequests(5)
}Resets the limiter to its initial state with maximum tokens.
func (l *Limiter) Reset()Example:
// Reset limiter (useful for testing or administrative actions)
limiter.Reset()Returns current statistics for the rate limiter.
func (l *Limiter) GetStats() (total, allowed, blocked int64)Returns:
total: Total number of requests processedallowed: Number of requests that were allowedblocked: Number of requests that were blocked
Example:
total, allowed, blocked := limiter.GetStats()
fmt.Printf("Stats - Total: %d, Allowed: %d, Blocked: %d\n",
total, allowed, blocked)
// Calculate success rate
if total > 0 {
successRate := float64(allowed) / float64(total) * 100
fmt.Printf("Success rate: %.2f%%\n", successRate)
}Returns HTTP middleware that applies rate limiting.
func (l *Limiter) Wrap(next http.Handler) http.HandlerParameters:
next: The HTTP handler to wrap
Returns: http.Handler - Wrapped handler with rate limiting
Example:
mux := http.NewServeMux()
mux.HandleFunc("/api", apiHandler)
// Apply rate limiting to all routes
http.ListenAndServe(":8080", limiter.Wrap(mux))Returns HTTP middleware as a function for use with HandlerFunc.
func (l *Limiter) WrapFunc(next http.HandlerFunc) http.HandlerFuncParameters:
next: The HTTP handler function to wrap
Returns: http.HandlerFunc - Wrapped handler function
Example:
http.HandleFunc("/api", limiter.WrapFunc(apiHandler))Updates the paths that should skip rate limiting.
func (l *Limiter) SetSkipPaths(paths []string)Parameters:
paths: Slice of URL paths to skip
Example:
// Update skip paths at runtime
limiter.SetSkipPaths([]string{"/health", "/metrics", "/admin/status"})Updates the IP addresses that should skip rate limiting.
func (l *Limiter) SetTrustedIPs(ips []string)Parameters:
ips: Slice of IP addresses to trust
Example:
// Update trusted IPs at runtime
limiter.SetTrustedIPs([]string{"127.0.0.1", "10.0.0.1", "192.168.1.100"})package main
import (
"fmt"
"net/http"
"time"
"github.com/yourusername/ratelimit"
)
func main() {
// Create rate limiter: 60 requests per minute
limiter := ratelimit.New(60, time.Minute)
// API handler
apiHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
fmt.Fprintf(w, `{"message": "API response", "timestamp": "%s"}`,
time.Now().Format(time.RFC3339))
})
// Apply rate limiting
http.Handle("/api", limiter.Wrap(apiHandler))
fmt.Println("Server starting on :8080 (60 requests/minute limit)")
http.ListenAndServe(":8080", nil)
}package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"time"
"github.com/yourusername/ratelimit"
)
func main() {
// Advanced configuration
config := ratelimit.Config{
Limit: 100, // 100 requests
Window: time.Minute, // per minute
BurstSize: 20, // allow bursts of 20
SkipPaths: []string{"/health", "/ping"},
TrustedIPs: []string{"127.0.0.1"},
}
limiter := ratelimit.NewWithConfig(config)
// Custom rate limit exceeded handler
limiter.OnLimitReached = func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusTooManyRequests)
response := map[string]interface{}{
"error": "Rate limit exceeded",
"message": "Please slow down your requests",
"retry_after": 60,
}
json.NewEncoder(w).Encode(response)
}
// Custom allow callback for logging
limiter.OnAllow = func(r *http.Request, remaining float64) {
log.Printf("Request from %s allowed, remaining: %.0f",
r.RemoteAddr, remaining)
}
// Handlers
http.HandleFunc("/api", limiter.WrapFunc(apiHandler))
http.HandleFunc("/health", healthHandler) // This will skip rate limiting
http.HandleFunc("/stats", limiter.WrapFunc(statsHandler))
log.Println("Server starting on :8080")
http.ListenAndServe(":8080", nil)
}
func apiHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{
"message": "Hello from API",
"time": time.Now().Format(time.RFC3339),
})
}
func healthHandler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("OK"))
}
func statsHandler(w http.ResponseWriter, r *http.Request) {
// This would need access to the limiter instance
// Implementation depends on your application structure
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{
"status": "Stats endpoint",
})
}package main
import (
"context"
"fmt"
"sync"
"time"
"github.com/yourusername/ratelimit"
)
func main() {
// Create a rate limiter: 5 requests per second
limiter := ratelimit.New(5, time.Second)
// Simulate multiple goroutines making requests
var wg sync.WaitGroup
// Start 10 workers
for i := 0; i < 10; i++ {
wg.Add(1)
go func(workerID int) {
defer wg.Done()
for j := 0; j < 5; j++ {
if limiter.Allow() {
fmt.Printf("Worker %d: Request %d allowed\n", workerID, j+1)
// Simulate work
time.Sleep(100 * time.Millisecond)
} else {
fmt.Printf("Worker %d: Request %d rate limited\n", workerID, j+1)
// Wait for permission with timeout
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
if err := limiter.Wait(ctx); err != nil {
fmt.Printf("Worker %d: Failed to wait: %v\n", workerID, err)
} else {
fmt.Printf("Worker %d: Request %d allowed after waiting\n", workerID, j+1)
}
cancel()
}
}
}(i)
}
// Monitor stats
go func() {
ticker := time.NewTicker(2 * time.Second)
defer ticker.Stop()
for range ticker.C {
total, allowed, blocked := limiter.GetStats()
remaining := limiter.RemainingTokens()
fmt.Printf("\nStats: Total=%d, Allowed=%d, Blocked=%d, Remaining=%.2f\n\n",
total, allowed, blocked, remaining)
if total >= 50 { // Stop monitoring after 50 total requests
return
}
}
}()
wg.Wait()
// Final stats
total, allowed, blocked := limiter.GetStats()
fmt.Printf("\nFinal Stats: Total=%d, Allowed=%d, Blocked=%d\n",
total, allowed, blocked)
}- API Endpoints: Start with 1000 requests/hour for general API usage
- Authentication: Use stricter limits (e.g., 5 attempts/minute) for login endpoints
- Heavy Operations: Limit expensive operations to 10-100 requests/hour
- Burst Traffic: Set
BurstSizeto 10-20% of your limit for handling spikes
- Per-User Limiting: Create separate limiter instances for different users/clients
- Memory Usage: Consider using a limiter pool for high-traffic applications
- Statistics: Only call
GetStats()when needed, as it requires a lock
// Always handle context cancellation properly
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := limiter.Wait(ctx); err != nil {
switch err {
case context.DeadlineExceeded:
// Handle timeout
return fmt.Errorf("rate limiter timeout")
case context.Canceled:
// Handle cancellation
return fmt.Errorf("request canceled")
default:
return fmt.Errorf("unexpected error: %w", err)
}
}When using the HTTP middleware, the following headers are automatically set:
X-RateLimit-Limit: Maximum number of requests allowedX-RateLimit-Remaining: Number of requests remaining in the current windowRetry-After: Seconds to wait before retrying (only when rate limited)
The rate limiter is fully thread-safe and can be used concurrently from multiple goroutines. All methods use appropriate locking mechanisms.
// Reset limiter between tests
func TestRateLimiter(t *testing.T) {
limiter := ratelimit.New(5, time.Second)
// Test allowing requests
for i := 0; i < 5; i++ {
if !limiter.Allow() {
t.Errorf("Request %d should be allowed", i)
}
}
// This should be rate limited
if limiter.Allow() {
t.Error("Request should be rate limited")
}
// Reset for next test
limiter.Reset()
}