Skip to content

Instantly share code, notes, and snippets.

@joloiuy
Created August 26, 2023 11:42
Show Gist options
  • Select an option

  • Save joloiuy/921b0d3bc5f7ab5e8ed63b7c7e1517a4 to your computer and use it in GitHub Desktop.

Select an option

Save joloiuy/921b0d3bc5f7ab5e8ed63b7c7e1517a4 to your computer and use it in GitHub Desktop.
[Medium] Rate Limiting in Go: Harnessing Gin Framework with Limiter Middleware
package main
import (
"encoding/json"
"fmt"
"github.com/gin-gonic/gin"
"github.com/ulule/limiter/v3"
"github.com/ulule/limiter/v3/drivers/middleware/gin"
"github.com/ulule/limiter/v3/drivers/store/memory"
"strings"
"time"
)
// globalRate defines the default rate limit for routes that don't have a specific configuration.
var globalRate = limiter.Rate{
Period: 1 * time.Hour,
Limit: 1000,
}
// routeNameMap maps API routes to route names for configuration lookup.
var routeNameMap = map[string]string{
"/api/users": "users",
"/api/items": "items",
}
// rateMapJSON contains predefined rate configurations in JSON format.
var rateMapJSON = `{
"default:users": "1000-M",
"strict:users": "10-S",
"default:items": "1000-M",
"strict:items": "10-S"
}`
// RateConfig is a mapping of route names to rate limit strings.
type RateConfig map[string]string
// getRateConfigFromDB simulates fetching rate configurations from a database.
func getRateConfigFromDB(mode, routeName string) (string, error) {
// Replace this with your actual logic to fetch rate configuration from a database.
// For this example, we'll use the predefined rateMapJSON.
return rateMapJSON, nil
}
// parseRate converts a rate limit string to a limiter.Rate struct.
func parseRate(rateStr string) (limiter.Rate, error) {
parts := strings.Split(rateStr, "-")
if len(parts) != 2 {
return limiter.Rate{}, fmt.Errorf("invalid rate format: %s", rateStr)
}
limit, err := limiter.NewRateFromFormatted(rateStr)
if err != nil {
return limiter.Rate{}, err
}
return limit, nil
}
// retrieveRateConfig fetches the rate configuration for a route.
func retrieveRateConfig(mode, routeName string) (limiter.Rate, error) {
rateConfigJSON, err := getRateConfigFromDB(mode, routeName)
if err != nil {
// Return an error along with an empty rate limit.
return limiter.Rate{}, err
}
rateConfig := make(RateConfig)
err = json.Unmarshal([]byte(rateConfigJSON), &rateConfig)
if err != nil {
// Return an error along with an empty rate limit.
return limiter.Rate{}, err
}
rateStr, exists := rateConfig[mode+":"+routeNameMap[routeName]]
if !exists {
// Return an error along with an empty rate limit.
return limiter.Rate{}, fmt.Errorf("rate configuration not found for mode: %s, routeName: %s", mode, routeName)
}
return parseRate(rateStr)
}
// RateControl is a middleware to handle rate limiting.
func RateControl(c *gin.Context) {
// Determine the route name dynamically.
routeName := c.FullPath()
mode := "default" // Replace this with your actual mode retrieval logic.
// Retrieve the rate configuration or use a global rate as fallback.
rate, err := retrieveRateConfig(mode, routeName)
if err != nil {
rate = globalRate
}
// Create a rate limiter based on the route name and mode.
storeWithPrefix := memory.NewStoreWithOptions(
&memory.Options{
Prefix: mode + ":" + routeName + ":",
MaxRetry: 3,
},
)
rateLimiter := limiter.New(storeWithPrefix, rate)
// Apply the rate limiter middleware.
limiter_gin.RateLimiter(rateLimiter).Middleware(c)
}
func main() {
r := gin.Default()
// Use RateControl middleware globally for all routes.
r.Use(RateControl)
// Define your routes
r.GET("/api/users", func(c *gin.Context) {
c.JSON(200, gin.H{"message": "Users route"})
})
r.GET("/api/items", func(c *gin.Context) {
c.JSON(200, gin.H{"message": "Items route"})
})
r.Run(":8080")
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment