dotnet / dotnet/aspnetcore

Rate limiting middleware - Statistics about rate limiters

Open
#44,140 10 comments 81 reactions 1 assignee Claimed by @JamesNK View on GitHub
area-middleware feature-rate-limit
Dominant language
C#
Stars
38.4k
Forks
10.9k
Avg merge
2d 10h
Merged PRs (30d)
281

Description

### Is there an existing issue for this?

- [X] 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:

```csharp
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](https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limit-http-headers)), 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:

```csharp
public class RateLimitStatisticsMiddleware
{
private readonly RequestDelegate _next;
private readonly IOptions _options;

public RateLimitStatisticsMiddleware(RequestDelegate next, IOptions 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.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.