-
Notifications
You must be signed in to change notification settings - Fork 9.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Make RateLimiterMiddleware endpoint-aware #41667
Comments
Is
If not a default policy would be useful. Also there could be a NoRateLimiting method on endpoint too to skip the middleware completely.
|
There's not currently a way w/ the runtime APIs to mix endpoint-specific & truly global limiters - if you want a truly global limiter, you can
Good call, just added an extension method for |
Notes -
Only 1 policy metadata will be resolved on each endpoint - last one wins (imagine applying policy to whole group, then different policy to 1 endpoint in group). Check the endpoint's metadata, see if we have any policy in our last matching that. Follow-up - allow user to set feature on context that has policy name. Call to |
Thank you for submitting this for API review. This will be reviewed by @dotnet/aspnet-api-review at the next meeting of the ASP.NET Core API Review group. Please ensure you take a look at the API review process documentation and ensure that:
|
API Review Notes:
|
@BrennanConroy @wtgodbe Do you have any idea about what kind of rate limiter chaining APIs we would like to propose in the runtime repo? |
namespace Microsoft.AspNetCore.RateLimiting
{
+ public interface IRateLimiterPolicy<TKey>
+ {
+ public int CustomRejectionStatusCode { get; }
+ public Func<HttpContext, RateLimitLease, Task> CustomOnRejected { get; }
+ public RateLimitPartition<TKey> GetPartition(HttpContext httpContext);
+ }
public sealed class RateLimiterOptions
{
public PartitionedRateLimiter<HttpContext> Limiter { get; set; }
public Func<HttpContext, RateLimitLease, Task> OnRejected { get; set; }
public int DefaultRejectionStatusCode{ get; set; }
+ public RateLimiterOptions AddPolicy<TKey>(string policyName, Func<HttpContext, RateLimitPartition<TKey>> partitioner)
+ public RateLimiterOptions AddPolicy<TKey, TPolicy>(string policyName) where TPolicy : IRateLimiterPolicy<TKey>
}
- public static class RateLimitingApplicationBuilderExtensions
+ public static class RateLimiterApplicationBuilderExtensions
{
public static IApplicationBuilder UseRateLimiter(this IApplicationBuilder app)
public static IApplicationBuilder UseRateLimiter(this IApplicationBuilder app, RateLimiterOptions options)
}
+ public static class RateLimiterEndpointConventionBuilderExtensions
+ {
+ public static TBuilder RequireRateLimiting<TBuilder>(this TBuilder builder, String policyName) where TBuilder : IEndpointConventionBuilder
+ }
+ public static class RateLimiterOptionsExtensions
+ {
+ public static RateLimiterOptions AddTokenBucketRateLimiter(this RateLimiterOptions options, string policyName, TokenBucketRateLimiterOptions tokenBucketRateLimiterOptions)
+ public static RateLimiterOptions AddFixedWindowRateLimiter(this RateLimiterOptions options, string policyName, FixedWindowRateLimiterOptions fixedWindowRateLimiterOptions)
+ public static RateLimiterOptions AddSlidingWindowRateLimiter(this RateLimiterOptions options, string policyName, SlidingWindowRateLimiterOptions slidingWindowRateLimiterOptions)
+ public static RateLimiterOptions AddConcurrencyLimiter(this RateLimiterOptions options, string policyName, ConcurrencyLimiterOptions concurrencyLimiterOptions)
+ public static RateLimiterOptions AddNoLimiter(this RateLimiterOptions options, string policyName)
+ }
} |
Sample: var echoPolicyName = "echoPolicy";
var echo2PolicyName = "echoPolicy2";
var builder = WebApplication.CreateBuilder(args);
builder.UseRateLimiter(options =>
{
options.AddConcurrencyLimiter(echoPolicyName,
new ConcurrencyLimiterOptions(2, QueueProcessingOrder.OldestFirst, 3));
options.AddPolicy<string>(echo2PolicyName, context =>
if (context.User.Identity.Name == "admin")
{
return RateLimitPartition.CreateTokenBucketLimiter("AdminLimiter",
_ => new TokenBucketRateLimiterOptions(5, QueueProcessingOrder.OldestFirst, 5, new TimeSpan(0, 1, 0), 3));
}
else
{
return RateLimitPartition.CreateNoLimiter("DefaultLimiter");
});
});
var app = builder.Build();
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/echo",
context => context.Response.WriteAsync("echo"))
.RequireRateLimiting(echoPolicyName);
endpoints.MapGet("/echo2",
context => context.Response.WriteAsync("echo2"))
.RequireRateLimiting(echo2PolicyName);
endpoints.MapRazorPages();
});
app.Run(); |
@wtgodbe Can you show a controller as well? |
That would require an attribute. We could make it generic! I'm personally okay doing that in a later PR, but we should know it's possible at least.
I like how it's the admin that gets rate limited in this sample! 😆 |
Thank you for submitting this for API review. This will be reviewed by @dotnet/aspnet-api-review at the next meeting of the ASP.NET Core API Review group. Please ensure you take a look at the API review process documentation and ensure that:
|
And a sample with |
Sample: public class MyRateLimiterPolicy<string> : IRateLimiterPolicy<string>
{
public Func<HttpContext, RateLimitLease, Task> CustomOnRejected
{
get => (context, lease) =>
{
return Task.CompletedTask;
};
}
public int CustomRejectionStatusCode
{
get => 529;
}
public RateLimitPartition<string> GetPartition(HttpContext context)
{
if (context.User.Identity.Name == "admin")
{
return RateLimitPartition.CreateTokenBucketLimiter("AdminLimiter",
_ => new TokenBucketRateLimiterOptions(5, QueueProcessingOrder.OldestFirst, 5, new TimeSpan(0, 1, 0), 3));
}
else
{
return RateLimitPartition.CreateNoLimiter("DefaultLimiter");
});
}
} var echoPolicyName = "echoPolicy";
var builder = WebApplication.CreateBuilder(args);
builder.UseRateLimiter(options =>
{
options.AddPolicy<string, MyRateLimiterPolicy<string>>(echoPolicyName);
});
var app = builder.Build();
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/echo",
context => context.Response.WriteAsync("echo"))
.RequireRateLimiting(echoPolicyName);
endpoints.MapRazorPages();
});
app.Run(); |
API Review Notes:
namespace Microsoft.AspNetCore.RateLimiting
{
- public interface IRateLimitMetadata
- {
- }
-
+ public interface IRateLimiterPolicy<TPartitionKey>
+ {
+ public Func<OnRejectedContext, CancellationToken, ValueTask>? OnRejected { get; }
+ public RateLimitPartition<TPartitionKey> GetPartition(HttpContext httpContext);
+ }
public sealed class RateLimiterOptions
{
- public PartitionedRateLimiter<HttpContext> Limiter { get; set; }
+ public PartitionedRateLimiter<HttpContext>? GlobalLimiter { get; set; }
- public Func<HttpContext, RateLimitLease, Task> OnRejected { get; set; }
+ public Func<OnRejectedContext, CancellationToken, ValueTask>? OnRejected { get; set; }
- public int DefaultRejectionStatusCode { get; set; }
+ public int RejectionStatusCode { get; set; }
+ public RateLimiterOptions AddPolicy<TPartitionKey>(string policyName, Func<HttpContext, RateLimitPartition<TPartitionKey>> partitioner)
+ public RateLimiterOptions AddPolicy<TPartitionKey, TPolicy>(string policyName) where TPolicy : IRateLimiterPolicy<TPartitionKey>
+ public RateLimiterOptions AddPolicy<TPartitionKey>(string policyName, IRateLimiterPolicy<TPartitionKey> policy);
}
+ // We could add the policy name to this in the future if we want.
+ public sealed class OnRejectedContext
+ {
+ public HttpContext HttpContext { get; required init; }
+ public RateLimitLease Lease { get; required init; }
+ }
+
+ public static class RateLimiterOptionsExtensions
+ {
+ public static RateLimiterOptions AddTokenBucketLimiter(this RateLimiterOptions options, string policyName, TokenBucketRateLimiterOptions tokenBucketRateLimiterOptions)
+ public static RateLimiterOptions AddFixedWindowLimiter(this RateLimiterOptions options, string policyName, FixedWindowRateLimiterOptions fixedWindowRateLimiterOptions)
+ public static RateLimiterOptions AddSlidingWindowLimiter(this RateLimiterOptions options, string policyName, SlidingWindowRateLimiterOptions slidingWindowRateLimiterOptions)
+ public static RateLimiterOptions AddConcurrencyLimiter(this RateLimiterOptions options, string policyName, ConcurrencyLimiterOptions concurrencyLimiterOptions)
+ public static RateLimiterOptions AddNoLimiter(this RateLimiterOptions options, string policyName)
+ }
- public static class RateLimitingApplicationBuilderExtensions
- {
- //...
- }
}
+ namespace Microsoft.AspNetCore.Builder
+ {
+ public static class RateLimitingApplicationBuilderExtensions
+ {
+ //...
+ }
+
+ public static class RateLimiterEndpointConventionBuilderExtensions
+ {
+ public static TBuilder RequireRateLimiting<TBuilder>(this TBuilder builder, string policyName) where TBuilder : IEndpointConventionBuilder
+ }
} API Approved! |
How about a + public static class RateLimiterEndpointConventionBuilderExtensions
+ {
+ public static TBuilder SkipRateLimiting<TBuilder>(this TBuilder builder) where TBuilder : IEndpointConventionBuilder
+ } This way when executing endpoints marked with |
This is done |
Allow users to piecemeal add named
RateLimiters
that apply to specific endpoints - will achieve this w/IEndpointConventionBuilder
. Some discussion starting at #41655 (comment)The text was updated successfully, but these errors were encountered: