Skip to content

🤗 [Question]: Is it feasible to add ExpirationFunc for dynamic expiration in Limiter middleware? #3749

@ilxqx

Description

@ilxqx

Question Description

Background

In my application, I use a custom "Unified Action" API specification instead of traditional RESTful style. All API endpoints use a single POST /api route, and requests are differentiated by the following structure:

{
  "resource": "sys/user",
  "action": "create", 
  "version": "v1",
  "params": {
     // ...
  }
}

The combination of resource, action, and version serves as the unique identifier for each API endpoint.

Current Challenge

This architecture creates a challenge when using the rate limiter middleware. Since there's only one middleware instance but I need to apply different rate limiting configurations for different APIs, I currently can only dynamically configure the MaxFunc but not the Expiration.

Here's my current rate limiter configuration:

limiter.New(limiter.Config{
    LimiterMiddleware: limiter.SlidingWindow{},
    MaxFunc: func(ctx fiber.Ctx) int {
        request := contextx.APIRequest(ctx)
        definition := manager.Lookup(request.Identifier)
        return lo.Ternary(definition.HasRateLimit(), definition.RateLimit, 10)
    },
    Expiration: 30 * time.Second,
    
    // The following is what I need but doesn't exist yet
    // ExpirationFunc: func(ctx fiber.Ctx) time.Duration {
    //     request := contextx.APIRequest(ctx)
    //     definition := manager.Lookup(request.Identifier)
    //     return lo.Ternary(definition.HasRateLimit(), definition.RateExpiration, 30*time.Second)
    // },
    
    SkipFailedRequests:     false,
    SkipSuccessfulRequests: false,
    KeyGenerator: func(ctx fiber.Ctx) string {
        request := contextx.APIRequest(ctx)
        var sb strings.Builder
        _, _ = sb.WriteString(request.Resource)
        _ = sb.WriteByte(constants.ByteColon)
        _, _ = sb.WriteString(request.Version)
        _ = sb.WriteByte(constants.ByteColon)
        _, _ = sb.WriteString(request.Action)
        _ = sb.WriteByte(constants.ByteColon)
        _, _ = sb.WriteString(utils.GetIP(ctx))
        _ = sb.WriteByte(constants.ByteColon)

        principal := contextx.Principal(ctx)
        if principal == nil {
            principal = security.PrincipalAnonymous
        }
        _, _ = sb.WriteString(principal.Id)

        return sb.String()
    },
    LimitReached: func(ctx fiber.Ctx) error {
        r := &result.Result{
            Code:    result.ErrCodeTooManyRequests,
            Message: i18n.T(result.ErrMessageTooManyRequests),
        }
        return r.ResponseWithStatus(ctx, fiber.StatusTooManyRequests)
    },
})

What I'm Considering

I'm wondering if it would be possible to add an ExpirationFunc configuration option, similar to how MaxFunc works for rate limits.

From my understanding of the current implementation, supporting both dynamic MaxFunc and ExpirationFunc might be challenging. I'm thinking the ExpirationFunc could potentially be called periodically to apply different expiration configurations, though this would introduce some delay.

I'd appreciate any insights or guidance on this approach before potentially submitting a formal feature request.

Thank you!

Code Snippet (optional)

package main

import "github.com/gofiber/fiber/v3"
import "log"

func main() {
  app := fiber.New()

  // An example to describe the question

  log.Fatal(app.Listen(":3000"))
}

Checklist:

  • I agree to follow Fiber's Code of Conduct.
  • I have checked for existing issues that describe my questions prior to opening this one.
  • I understand that improperly formatted questions may be closed without explanation.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Status

    No status

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions