Asp.NET Rate Limiter middleware support
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:
- 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.
- I find it difficult to configure the middleware from F#, but I might be doing something wrong or excessively complicated.
AddTokenBucketLimiteris failing when trying to call it from F#, witherror 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
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.
Sounds great, looking forward to it. Thank you!
I created this PR right now -> https://github.com/giraffe-fsharp/Giraffe/pull/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.
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.
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.
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.
- Related issue: https://github.com/giraffe-fsharp/Giraffe/issues/555#issue-1873648993
- Related issue: https://github.com/dotnet/fsharp/issues/17858
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
]
]
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.
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.
Hey @GallaFrancesco, with the new alpha release you can have per-route rate limiting leveraging ASP.NET native middleware. Check this sample for more details: https://github.com/giraffe-fsharp/Giraffe/blob/master/samples/RateLimiting/Program.fs. There's a README explaining how to run it locally too.
Since there were no more interactions, and considering the new routing functions ...WithExtensions which enables fine-grained ASP.NET built-in middleware usage, I'm going to close this issue for now.
Check this sample for an example on how to use this ...WithExtensions to leverage the ASP.NET built-in rate limiting feature.
Feel free to open a new issue for further discussions.