Description
Is there an existing issue for this?
- I have searched the existing issues
Is your feature request related to a problem? Please describe the problem.
The ASP.NET Core rate limiting middleware is great, but "limited" in terms of what you an communicate with your users. Let's start with some code that you can write today in .NET 7:
builder.Services.AddRateLimiter(options =>
{
options.OnRejected = async (context, token) =>
{
context.HttpContext.Response.StatusCode = 429;
if (context.Lease.TryGetMetadata(MetadataName.RetryAfter, out var retryAfter))
{
await context.HttpContext.Response.WriteAsync(
$"Too many requests. Please try again after {retryAfter.TotalMinutes} minute(s). " +
$"Read more about our rate limits at https://example.org/docs/ratelimiting.", cancellationToken: token);
}
else
{
await context.HttpContext.Response.WriteAsync(
"Too many requests. Please try again later. " +
"Read more about our rate limits at https://example.org/docs/ratelimiting.", cancellationToken: token);
}
};
// ...
});
When rate limits are triggered, a response is returned that tells the user they are rate limited, where to find more information, and (if the MetadataName.RetryAfter
data is available), when to retry.
This is quite limited. There's no access to which rate limiter fired, and what its statistics are.
Additionally, the current RateLimitLease
is only accessible when rate limiting is fired - not for every request. If you would want to return statistics about your limits (e.g. like GitHub does), you'll find it impossible to get statistics about the current lease in a custom middleware that can write out these additional headers.
Describe the solution you'd like
Here's a middleware that has access to some of data that I'd want to access:
public class RateLimitStatisticsMiddleware
{
private readonly RequestDelegate _next;
private readonly IOptions<RateLimiterOptions> _options;
public RateLimitStatisticsMiddleware(RequestDelegate next, IOptions<RateLimiterOptions> options)
{
_next = next;
_options = options;
}
public Task Invoke(HttpContext context)
{
// Note: This should also work on endpoint limiters, but those are not available.
// There is no current "rate limit context" of sorts. Including the policy name etc.
var globalLimiter = _options.Value.GlobalLimiter;
if (globalLimiter != null)
{
var statistics = globalLimiter.GetStatistics(context);
if (statistics != null)
{
// Note: It would be great to be able to get the TokenLimit from the "current rate limiter context"
context.Response.Headers.Add("X-Rate-Limit-Limit", "20");
// Note: It would be great to be able to get the Window from the "current rate limiter context"
context.Response.Headers.Add("X-Rate-Limit-Reset", DateTimeOffset.UtcNow.ToString("O"));
// This one is there today
context.Response.Headers.Add("X-Rate-Limit-Remaining", statistics.CurrentAvailablePermits.ToString());
}
}
return _next(context);
}
}
The dream scenario for a better ASP.NET Core rate limiter middleware would be to:
- Have access to more statistics about the rate limiter (which policy fired, what partition, what are the limiter's options so I can emit a message that says "you can only make 10 requests per minute", ...) in the rejected callbacks.
- Have a feature on the current
HttpContext
that gives access to the current rate limit context, so these details can also be returned on successful requests, or added to telemetry.
Additional context
Most rate limiters in System.Threading.RateLimiting
don't provide additional statistics. This feature will need changes in both ASP.NET Core and the .NET framework.