ratelimiter is a flexible and extensible rate limiter library for Go, built as a wrapper around the standard golang.org/x/time/rate package. It utilizes the token bucket algorithm to control the rate of operations, making it suitable for scenarios like API request limiting, resource access control, and preventing abuse.
The library allows creating rate limiters specific to different keys (e.g., user IDs, IP addresses) that can be safely shared across multiple goroutines. It includes an in-memory storage backend by default and features automatic cleanup of limiters that haven't been used for a configurable duration.
For advanced use cases, you can provide your own storage backend by implementing the Storage interface or even define custom rate-limiting logic by implementing the Limiter interface.
- Token Bucket Algorithm: Based on the robust
golang.org/x/time/rateimplementation. - Key-Based Limiting: Create and manage separate limiters for different identifiers (e.g., IP addresses, user IDs).
- Goroutine-Safe: Designed for concurrent use.
- In-Memory Storage: Comes with a built-in
InMemoryStoragefor easy setup. - Automatic Cleanup: Unused limiters are automatically removed from storage after a specified duration to conserve resources.
- Extensible:
- Implement the
Storageinterface for custom persistence (e.g., Redis, Memcached). - Implement the
Limiterinterface for custom rate-limiting strategies.
- Implement the
- Middleware Example: Includes a practical example for integrating with
net/httphandlers.
go get github.com/slashdevops/ratelimiterLimiterInterface: Defines the basic contract for any rate limiter, primarily theAllow()method. The standardgolang.org/x/time/rate.Limitersatisfies this interface.StorageInterface: Defines methods (Store,Load,Delete) for storing and retrieving limiter instances associated with keys.InMemoryStorage: A thread-safe, map-based implementation of theStorageinterface.BucketLimiter: The main orchestrator. It uses aLimiter(likerate.Limiter) as a template, aStoragebackend, and manages the creation, retrieval, and automatic cleanup of key-specific limiters.
package main
import (
"fmt"
"time"
"github.com/slashdevops/ratelimiter"
"golang.org/x/time/rate"
)
func main() {
// 1. Create a storage system (in-memory is provided)
storage := ratelimiter.NewInMemoryStorage()
// 2. Define the base rate limit (e.g., 5 requests per second with a burst of 10)
baseRate := rate.Limit(5)
burstSize := 10
baseLimiter := rate.NewLimiter(baseRate, burstSize)
// 3. Configure the BucketLimiter
// Limiters unused for 1 minute will be automatically removed.
cleanupInterval := 1 * time.Minute
bucketLimiter := ratelimiter.NewBucketLimiter(baseLimiter, cleanupInterval, storage)
// 4. Get or create a limiter for a specific key (e.g., user ID or IP)
userID := "user-123"
userLimiter := bucketLimiter.GetOrAdd(userID)
// 5. Check if the operation is allowed
if userLimiter.Allow() {
fmt.Println("Operation allowed for", userID)
// ... perform the rate-limited action ...
} else {
fmt.Println("Rate limit exceeded for", userID)
}
// Example: Simulate multiple requests
for i := 0; i < 15; i++ {
if bucketLimiter.GetOrAdd(userID).Allow() {
fmt.Printf("Request %d allowed\n", i+1)
} else {
fmt.Printf("Request %d blocked\n", i+1)
}
time.Sleep(100 * time.Millisecond) // Simulate some delay
}
}See examples for more usage examples, including the HTTP middleware.
Middleware example:
package main
import (
"flag"
"fmt"
"net/http"
"strings"
"time"
"github.com/slashdevops/ratelimiter"
"golang.org/x/time/rate"
)
// Middleware is a function that wraps an http.Handler to provide additional functionality
type Middleware func(http.Handler) http.Handler
// ThenFunc wraps an http.HandlerFunc with a middleware
func (m Middleware) ThenFunc(h http.HandlerFunc) http.Handler {
return m(http.HandlerFunc(h))
}
// IPRateLimiter is a middleware that limits the number of requests from a single IP address
func IPRateLimiter(limiter *ratelimiter.BucketLimiter) Middleware {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ip := strings.Split(r.RemoteAddr, ":")[0]
lim := limiter.GetOrAdd(ip)
if !lim.Allow() {
http.Error(w, fmt.Sprintf("too many requests from ip address %s", ip), http.StatusTooManyRequests)
return
}
next.ServeHTTP(w, r)
})
}
}
func main() {
limit := flag.Int("limit", 2, "Rate limit (requests per second)")
burst := flag.Int("burst", 1, "Burst size")
deleteAfter := flag.Duration("deleteAfter", 5*time.Second, "Duration after which the limiter will be deleted if not used")
numberOfRequests := flag.Int("numberOfRequests", 30, "Number of requests to send")
waitBetweenRequests := flag.Duration("waitBetweenRequests", 100*time.Millisecond, "Wait time between requests")
flag.Parse()
// Create a storage system
storage := ratelimiter.NewInMemoryStorage()
// Create a base rate limiter
baseLimiter := rate.NewLimiter(rate.Limit(float64(*limit)), *burst)
// Create a bucket limiter with a deleteAfter duration of 5 seconds
bucketLimiter := ratelimiter.NewBucketLimiter(baseLimiter, *deleteAfter, storage)
// Create a new HTTP server
mux := http.NewServeMux()
// Register the rate limiting middleware
mux.Handle("GET /", IPRateLimiter(bucketLimiter).ThenFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello, world without limit!")
}))
// go routine doing request
go func() {
// wait until the server is up
fmt.Println("Waiting for server to start...")
time.Sleep(2 * time.Second)
for range *numberOfRequests {
resp, err := http.Get("http://localhost:8080/")
if err != nil {
fmt.Println("Error:", err)
continue
}
resp.Body.Close()
if resp.StatusCode == http.StatusOK {
fmt.Println("Request allowed")
} else {
fmt.Println("Request rejected:", resp.Status)
}
time.Sleep(*waitBetweenRequests) // Wait between requests
}
fmt.Println()
fmt.Println("Finished sending requests, press Ctrl+C to stop the server")
}()
// Start the server
fmt.Println("Starting server on :8080...")
if err := http.ListenAndServe(":8080", mux); err != nil {
fmt.Println("Error starting server:", err)
}
}This project is licensed under the Apache License 2.0. See the LICENSE file for details.