-
-
Save joloiuy/921b0d3bc5f7ab5e8ed63b7c7e1517a4 to your computer and use it in GitHub Desktop.
[Medium] Rate Limiting in Go: Harnessing Gin Framework with Limiter Middleware
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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