dotnet / dotnet/aspnetcore

Rate limiter context HttpContext feature API proposal

Open
#45,658 39 comments 1 reaction 1 assignee Claimed by @BrennanConroy View on GitHub
api-needs-work area-middleware feature-rate-limit
Dominant language
C#
Stars
38.4k
Forks
10.9k
Avg merge
2d 10h
Merged PRs (30d)
281

Description

## Background and Motivation

In https://github.com/dotnet/aspnetcore/issues/44140 issue, one of The dream scenario for a better ASP.NET Core rate limiter middleware as @maartenba says, would be able 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.. This API proposal addresses this concern.

## Proposed API

Below API supposes, that we have `RateLimiterMiddleware` in our app, which responsibility is to create `IRateLimiterContextFeature` for each request:

```csharp
var app = builder.Build();

app.UseRateLimiter();
```

Now we can access to this interface in any custom middleware or controller:

```csharp
namespace Microsoft.AspNetCore.RateLimiting.Features;

public interface IRateLimiterContextFeature
{
HttpContext HttpContext { get; set; }

RateLimitLease Lease { get; set; }

PartitionedRateLimiter? GlobalLimiter { get; set; }

PartitionedRateLimiter EndpointLimiter { get; set; }
}
```

## Usage Examples

### Scenario 1: get statistics from limiters in custom telemetry middleware:

```csharp
public Task Invoke(HttpContext context)
{
var rlContext = context.Features.Get();

var globalStatistics = rlContext.GlobalLimiter.GetStatistics(context);
var endpointStatistics = rlContext.EndpointLimiter.GetStatistics(context);

_someTelemetryService.PushStatisticsToPrometheus(globalStatistics);
_someTelemetryService.PushStatisticsToPrometheus(endpointStatistics);
}
```

### Scenario 2: Get metadata from successful RateLimiterLease for this request

```csharp
public Task Invoke(HttpContext context)
{
var rlContext = context.Features.Get();

if (rlContext.Lease.TryGetMetadata("SOME_METADATA", out var metadata)
{
// Do some additional stuff, depends on metadata
}
}
```

## Alternative Design - 1

As @Tratcher said, there is a risk, that `RateLimitLease` would be disposed early. To prevent that, we can introduce facade-like class to wrap Lease with undisposable entity:

```cs

public interface IRateLimiterContextFeature
{
HttpContext HttpContext { get; }

RateLimitLeaseInfo LeaseInfo { get; }

PartitionedRateLimiter? GlobalLimiter { get; }

PartitionedRateLimiter EndpointLimiter { get; }
}

public abstract class RateLimitLeaseInfo
{
// Same props as RateLimitLease, but without Dispose()
}
```

## Alternative Design - 2

Also, Instead of using `HttpContext` Features, we can use `HttpContext.Items` and Extension methods, like its done in `Microsoft.AspNetCore.Authentication` extention methods (`HttpContext.SingIn`, `HttpContext.ChallangeAsync`, etc).
So, we use the Alternative Design - 1 approach with Facade-like api, but implemented with extension methods

```cs
// very simple example of implementation (for only 1 method) just to show the API
public static class HttpContextRateLimiterExtentions
{
public static RateLimiterStatistics GetRateLimiterStatistics(this HttpContext context)
{
// just for example - the code is not correct
var limiter = (PartitionedRateLimiter)context.Items["SomeGlobalLimiter"];
var stats = limiter?.GetStatistics(context);
return stats;
}
}

// and then in some middleware:
var statistics = HttpContext.GetRateLimiterStatistics();
```

## Risks

In Design 1:
- would anyone use this to dispose of the lease early? Would that have any strange side-effects, especially if it got double-disposed by the middleware later?

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.