AspNetCoreRateLimit
AspNetCoreRateLimit copied to clipboard
Extract rate limit logic to own service?
I'd like to be able to use the "should I limit this request?" logic separately in my application, but not as a piece of middleware.
Would it be workable to refactor the core blocking mechanism in such a way as to consume it separately from the middleware?
For example, I'd like to block requests based on specific request parameters and context, not just IP or client ID, and be able to do so as part of existing application logic called via my controllers, rather than as middleware in the overall request pipeline.
Maybe this helps you?
https://github.com/stefanprodan/AspNetCoreRateLimit/wiki/Resolve-Contributors
It looks like that lets me change the "key" for rate limiting to be arbitrary, but still leaves the core of the decision logic inside the middleware?
This allows you to customize what it is that you consider to be a clientId: the value of a http header, the value of a certain query string parameter, etc.
You can define and configure something similar to this implementation: https://github.com/stefanprodan/AspNetCoreRateLimit/blob/master/src/AspNetCoreRateLimit/Resolvers/ClientHeaderResolveContributor.cs
If you can give me a specific example, maybe we can work something out.
The rate limit middleware orchestrates all the calls to decide if things are enabled, determine the identity, check for if blocking should be applied by evaluating the rules and returning this response (code).
What I'm suggesting is moving all of that into another service which the middleware itself would delegate the decisions to before generating the response.
This would then allow the rate-limiting logic to be used elsewhere easily. For example in sort-of-pseudo-code, the middleware becomes something like this:
public class RateLimitResult
{
public bool IsExceeded { get; }
public TimeSpan RetryAfter { get; }
public long RequestsRemaining { get; }
}
public interface IRateLimiter
{
RateLimitResult ShouldBlockRequest(HttpContext context, object userState);
// Or:
RateLimitResult ShouldBlockRequest<T>(HttpContext context, T userState);
}
public async Task Invoke(HttpContext context)
{
var rateLimitResponse = _rateLimitService.ShouldBlockRequest(context);
if (rateLimitResponse.IsExceeded)
{
LogBlockedRequest(context, rateLimitResponse);
await ReturnQuotaExceededResponse(context, rateLimitResponse);
return;
}
context.Response.OnStarting(SetRateLimitHeaders, rateLimitResponse);
await _next.Invoke(context);
}
And an MVC controller could do something like this:
public async Task IndexAsync([FromRoute] string id)
{
var rateLimitResponse = _rateLimitService.ShouldBlockRequest(context, id);
if (rateLimitResponse.IsExceeded)
{
ViewBag["exceeded"] = "Sorry, you can't do that again yet!"
}
return View();
}
As the code exists today, everything is middleware based, so without copying a lot of the implementation elsewhere, you can't easily reuse the core of the rate-limit code in non-middleware scenarios.
Instead of relying on inheritance within the middleware, a service was injected then you'd probably get the flexibility you want here.
In general it's usually best practice to choose composition over inheritance.
One small question, I tried writing my own ResolveClientAsync()
and adding the config in the Startup.cs, it does hit this ResolveClientAsync()
for me and return different string for different users as expected but while rate-limiting, it does cumulative rate limiting for both the users on the same IP address. Am I missing something here?
figured it out, seems like I was still using app.UseIpRateLimiting()
instead of app.UseClientRateLimiting()