Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Asp.NET Rate Limiter middleware support #618

Open
GallaFrancesco opened this issue Oct 2, 2024 · 9 comments
Open

Asp.NET Rate Limiter middleware support #618

GallaFrancesco opened this issue Oct 2, 2024 · 9 comments
Labels
feature request Request to add new functionality

Comments

@GallaFrancesco
Copy link

https://learn.microsoft.com/en-us/aspnet/core/performance/rate-limit?view=aspnetcore-8.0 is a rate limiting middleware implementation and I'm struggling to make this work in Giraffe. Two main reasons:

  1. I couldn't find a way to enable the rate limiter on specific endpoints using RequireRateLimiting() in a similar way as this sample Asp.NET project: https://github.com/dotnet/AspNetCore.Docs.Samples/blob/23fbafb68257b59bf6f3775fe12c51afc2f242e3/fundamentals/middleware/rate-limit/WebRateLimitAuth/Program.cs#L163

It would be ideal to support a middleware such as:

route "/limited" >=> text "This is rate limited" >=> requireRateLimiting policy

Though I could not find any example of this.

  1. I find it difficult to configure the middleware from F#, but I might be doing something wrong or excessively complicated. AddTokenBucketLimiter is failing when trying to call it from F#, with error FS0193: Type constraint mismatch. The type 'RateLimiterOptions' is not compatible with type 'unit', see the following snippet.
type SampleRateLimitOptions =
    { ReplenishmentPeriod: int
      QueueProcessingOrder: QueueProcessingOrder
      QueueLimit: int 
      TokenLimit: int 
      TokensPerPeriod: int 
      AutoReplenishment: bool }

    static member Default : SampleRateLimitOptions = // times in seconds
        { ReplenishmentPeriod = 1
          QueueProcessingOrder = QueueProcessingOrder.OldestFirst
          QueueLimit = 10
          TokenLimit = 10
          TokensPerPeriod = 4
          AutoReplenishment = true }

let configureRateLimiter (builder: WebApplicationBuilder) =
    let rateLimitOptions = SampleRateLimitOptions.Default 

    let loadRateLimiterOptions (options: TokenBucketRateLimiterOptions) =
        options.TokenLimit <- rateLimitOptions.TokenLimit
        options.QueueProcessingOrder <- QueueProcessingOrder.OldestFirst
        options.QueueLimit <- rateLimitOptions.QueueLimit
        options.ReplenishmentPeriod <- TimeSpan.FromSeconds(rateLimitOptions.ReplenishmentPeriod)
        options.TokensPerPeriod <- rateLimitOptions.TokensPerPeriod
        options.AutoReplenishment <- rateLimitOptions.AutoReplenishment

    builder
       .Services
       .AddRateLimiter(fun (r: RateLimiterOptions) -> 
           r.AddTokenBucketLimiter("token", Action<TokenBucketRateLimiterOptions> loadRateLimiterOptions)) // This does not build
@64J0
Copy link
Member

64J0 commented Oct 2, 2024

Hello @GallaFrancesco, thanks for opening this issue.

I'll add a new sample to this project showing how one can use this ASP.NET rate limiter middleware with Giraffe either this week or the next.

@GallaFrancesco
Copy link
Author

Sounds great, looking forward to it. Thank you!

@64J0
Copy link
Member

64J0 commented Oct 5, 2024

I created this PR right now -> #619. It adds a global rate limiting middleware sample.

I'm not sure if we can use the per-endpoint rate limiting configuration. I can take a proper look at this in the future, when I have more free time.

@64J0
Copy link
Member

64J0 commented Oct 5, 2024

It may be worth tweaking this function -> https://github.com/giraffe-fsharp/Giraffe/blob/master/src/Giraffe/EndpointRouting.fs#L278.

We can eventually pattern match to the case where the user wants to add the rate limiting middleware on specific endpoints only.

@GallaFrancesco
Copy link
Author

Thank you @64J0 that's really handy.

I can look into modifying the Map* functions to be able to rate limit specific endpoints - the global rate limiter can be useful but having per-endpoint would allow Giraffe to support the same level of granularity allowed by Asp.NET.

@64J0
Copy link
Member

64J0 commented Oct 8, 2024

I was considering other approaches for this implementation, and maybe we can start using custom attributes on top of the handlers, like what we have for ASP.NET. So, based on these attributes (or the lack of them), we can map to the appropriate ASP.NET route configuration.

Got this idea after reading this documentation for output caching on .NET 8: https://learn.microsoft.com/en-us/aspnet/core/performance/caching/output?view=aspnetcore-8.0#configure-one-endpoint-or-page.

This code could give some ideas: https://github.com/haf/expecto/blob/main/Expecto/Model.fs#L108.

@64J0 64J0 added the feature request Request to add new functionality label Oct 8, 2024
@GallaFrancesco
Copy link
Author

I (personally) find that using attributes might be less consistent with the Giraffe approach to middlewares, while the API for rate limiting might be similar to Giraffe's authentication support: https://github.com/giraffe-fsharp/Giraffe/blob/master/DOCUMENTATION.md#requiresauthentication

I realized though that the feature request in the original issue description is wrong: the user-defined HTTP handler should always be the last in the middleware pipeline, while the rate limiter could be applied directly to the request handlers, e.g.:

let webApp =
    choose [
        route "/"     >=> text "Hello World"
        route "/limited" >=>
            requiresRateLimiting policy >=>
                choose [
                    GET  >=> getHandler
                    POST >=> submitHandler
                ]
    ]

@64J0
Copy link
Member

64J0 commented Oct 11, 2024

I wish it was that simple. In this case, since we're aiming to use the ASP.NET feature, we would need to rewrite the Giraffe routing mechanism to account for scenarios that we want to use something like app.Map(...).CacheOutput("Expire20"), or considering the rate limiting app.Map(...).RequireRateLimiting("fixed"), eventually combining those.

@64J0
Copy link
Member

64J0 commented Oct 22, 2024

I updated the PR code to add a mechanism that lets you configure these ASP.NET extensions passing a list. Please let me know what you think.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature request Request to add new functionality
Projects
None yet
Development

No branches or pull requests

2 participants