Skip to content

Commit 0438e7e

Browse files
authored
RateLimitingMiddleware updates (#43053)
* RateLimitingMiddleware updates * Feedback * Feedback, API * Fix sample * Feedback * Add tests * Fixup * Fixup2 * disambiguate * Feedback
1 parent d3b7623 commit 0438e7e

15 files changed

+464
-118
lines changed

src/Middleware/RateLimiting/samples/RateLimitingSample/Program.cs

Lines changed: 26 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -16,35 +16,38 @@
1616
// Inject an ILogger<SampleRateLimiterPolicy>
1717
builder.Services.AddLogging();
1818

19-
var app = builder.Build();
20-
2119
var todoName = "todoPolicy";
2220
var completeName = "completePolicy";
2321
var helloName = "helloPolicy";
2422

25-
// Define endpoint limiters and a global limiter.
26-
var options = new RateLimiterOptions()
27-
.AddTokenBucketLimiter(todoName, new TokenBucketRateLimiterOptions
28-
{
29-
TokenLimit = 1,
30-
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
31-
QueueLimit = 1,
32-
ReplenishmentPeriod = TimeSpan.FromSeconds(10),
33-
TokensPerPeriod = 1
34-
})
35-
.AddPolicy<string>(completeName, new SampleRateLimiterPolicy(NullLogger<SampleRateLimiterPolicy>.Instance))
36-
.AddPolicy<string, SampleRateLimiterPolicy>(helloName);
37-
// The global limiter will be a concurrency limiter with a max permit count of 10 and a queue depth of 5.
38-
options.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, string>(context =>
23+
builder.Services.AddRateLimiter(options =>
24+
{
25+
// Define endpoint limiters and a global limiter.
26+
options.AddTokenBucketLimiter(todoName, options =>
27+
{
28+
options.TokenLimit = 1;
29+
options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
30+
options.QueueLimit = 1;
31+
options.ReplenishmentPeriod = TimeSpan.FromSeconds(10);
32+
options.TokensPerPeriod = 1;
33+
})
34+
.AddPolicy<string>(completeName, new SampleRateLimiterPolicy(NullLogger<SampleRateLimiterPolicy>.Instance))
35+
.AddPolicy<string, SampleRateLimiterPolicy>(helloName);
36+
// The global limiter will be a concurrency limiter with a max permit count of 10 and a queue depth of 5.
37+
options.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, string>(context =>
38+
{
39+
return RateLimitPartition.GetConcurrencyLimiter<string>("globalLimiter", key => new ConcurrencyLimiterOptions
3940
{
40-
return RateLimitPartition.GetConcurrencyLimiter<string>("globalLimiter", key => new ConcurrencyLimiterOptions
41-
{
42-
PermitLimit = 10,
43-
QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
44-
QueueLimit = 5
45-
});
41+
PermitLimit = 10,
42+
QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
43+
QueueLimit = 5
4644
});
47-
app.UseRateLimiter(options);
45+
});
46+
});
47+
48+
var app = builder.Build();
49+
50+
app.UseRateLimiter();
4851

4952
// The limiter on this endpoint allows 1 request every 5 seconds
5053
app.MapGet("/", () => "Hello World!").RequireRateLimiting(helloName);

src/Middleware/RateLimiting/src/DefaultKeyType.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@ namespace Microsoft.AspNetCore.RateLimiting;
55

66
internal struct DefaultKeyType
77
{
8-
public DefaultKeyType(string policyName, object? key, object? factory = null)
8+
public DefaultKeyType(string? policyName, object? key, object? factory = null)
99
{
1010
PolicyName = policyName;
1111
Key = key;
1212
Factory = factory;
1313
}
1414

15-
public string PolicyName { get; }
15+
public string? PolicyName { get; }
1616

1717
public object? Key { get; }
1818

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace Microsoft.AspNetCore.RateLimiting;
5+
6+
/// <summary>
7+
/// Metadata that disables request rate limiting on an endpoint.
8+
/// </summary>
9+
/// <remarks>
10+
/// Completely disables the rate limiting middleware from applying to this endpoint.
11+
/// </remarks>
12+
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
13+
public sealed class DisableRateLimitingAttribute : Attribute
14+
{
15+
internal static DisableRateLimitingAttribute Instance { get; } = new DisableRateLimitingAttribute();
16+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace Microsoft.AspNetCore.RateLimiting;
5+
6+
/// <summary>
7+
/// Metadata that provides endpoint-specific request rate limiting.
8+
/// </summary>
9+
/// <remarks>
10+
/// Replaces any policies currently applied to the endpoint.
11+
/// The global limiter will still run on endpoints with this attribute applied.
12+
/// </remarks>
13+
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
14+
public sealed class EnableRateLimitingAttribute : Attribute
15+
{
16+
/// <summary>
17+
/// Creates a new instance of <see cref="EnableRateLimitingAttribute"/> using the specified policy.
18+
/// </summary>
19+
/// <param name="policyName">The name of the policy which needs to be applied.</param>
20+
public EnableRateLimitingAttribute(string policyName)
21+
{
22+
ArgumentNullException.ThrowIfNull(policyName);
23+
24+
PolicyName = policyName;
25+
}
26+
27+
internal EnableRateLimitingAttribute(DefaultRateLimiterPolicy policy)
28+
{
29+
Policy = policy;
30+
}
31+
32+
/// <summary>
33+
/// The name of the policy which needs to be applied.
34+
/// </summary>
35+
public string? PolicyName { get; }
36+
37+
/// <summary>
38+
/// The policy which needs to be applied, if present.
39+
/// </summary>
40+
internal DefaultRateLimiterPolicy? Policy { get; }
41+
}

src/Middleware/RateLimiting/src/IRateLimiterMetadata.cs

Lines changed: 0 additions & 15 deletions
This file was deleted.

src/Middleware/RateLimiting/src/Microsoft.AspNetCore.RateLimiting.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,7 @@
1818
<Compile Include="$(SharedSourceRoot)ValueStopwatch\*.cs" />
1919
</ItemGroup>
2020

21+
<ItemGroup>
22+
<InternalsVisibleTo Include="Microsoft.AspNetCore.RateLimiting.Tests" />
23+
</ItemGroup>
2124
</Project>

src/Middleware/RateLimiting/src/PublicAPI.Unshipped.txt

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
Microsoft.AspNetCore.Builder.RateLimiterApplicationBuilderExtensions
22
Microsoft.AspNetCore.Builder.RateLimiterEndpointConventionBuilderExtensions
3+
Microsoft.AspNetCore.Builder.RateLimiterServiceCollectionExtensions
4+
Microsoft.AspNetCore.RateLimiting.DisableRateLimitingAttribute
5+
Microsoft.AspNetCore.RateLimiting.DisableRateLimitingAttribute.DisableRateLimitingAttribute() -> void
6+
Microsoft.AspNetCore.RateLimiting.EnableRateLimitingAttribute
7+
Microsoft.AspNetCore.RateLimiting.EnableRateLimitingAttribute.EnableRateLimitingAttribute(string! policyName) -> void
8+
Microsoft.AspNetCore.RateLimiting.EnableRateLimitingAttribute.PolicyName.get -> string?
39
Microsoft.AspNetCore.RateLimiting.IRateLimiterPolicy<TPartitionKey>
410
Microsoft.AspNetCore.RateLimiting.IRateLimiterPolicy<TPartitionKey>.GetPartition(Microsoft.AspNetCore.Http.HttpContext! httpContext) -> System.Threading.RateLimiting.RateLimitPartition<TPartitionKey>
511
Microsoft.AspNetCore.RateLimiting.IRateLimiterPolicy<TPartitionKey>.OnRejected.get -> System.Func<Microsoft.AspNetCore.RateLimiting.OnRejectedContext!, System.Threading.CancellationToken, System.Threading.Tasks.ValueTask>?
@@ -23,9 +29,11 @@ Microsoft.AspNetCore.RateLimiting.RateLimiterOptions.RejectionStatusCode.set ->
2329
Microsoft.AspNetCore.RateLimiting.RateLimiterOptionsExtensions
2430
static Microsoft.AspNetCore.Builder.RateLimiterApplicationBuilderExtensions.UseRateLimiter(this Microsoft.AspNetCore.Builder.IApplicationBuilder! app) -> Microsoft.AspNetCore.Builder.IApplicationBuilder!
2531
static Microsoft.AspNetCore.Builder.RateLimiterApplicationBuilderExtensions.UseRateLimiter(this Microsoft.AspNetCore.Builder.IApplicationBuilder! app, Microsoft.AspNetCore.RateLimiting.RateLimiterOptions! options) -> Microsoft.AspNetCore.Builder.IApplicationBuilder!
32+
static Microsoft.AspNetCore.Builder.RateLimiterEndpointConventionBuilderExtensions.DisableRateLimiting<TBuilder>(this TBuilder builder) -> TBuilder
33+
static Microsoft.AspNetCore.Builder.RateLimiterEndpointConventionBuilderExtensions.RequireRateLimiting<TBuilder, TPartitionKey>(this TBuilder builder, Microsoft.AspNetCore.RateLimiting.IRateLimiterPolicy<TPartitionKey>! policy) -> TBuilder
2634
static Microsoft.AspNetCore.Builder.RateLimiterEndpointConventionBuilderExtensions.RequireRateLimiting<TBuilder>(this TBuilder builder, string! policyName) -> TBuilder
27-
static Microsoft.AspNetCore.RateLimiting.RateLimiterOptionsExtensions.AddConcurrencyLimiter(this Microsoft.AspNetCore.RateLimiting.RateLimiterOptions! options, string! policyName, System.Threading.RateLimiting.ConcurrencyLimiterOptions! concurrencyLimiterOptions) -> Microsoft.AspNetCore.RateLimiting.RateLimiterOptions!
28-
static Microsoft.AspNetCore.RateLimiting.RateLimiterOptionsExtensions.AddFixedWindowLimiter(this Microsoft.AspNetCore.RateLimiting.RateLimiterOptions! options, string! policyName, System.Threading.RateLimiting.FixedWindowRateLimiterOptions! fixedWindowRateLimiterOptions) -> Microsoft.AspNetCore.RateLimiting.RateLimiterOptions!
29-
static Microsoft.AspNetCore.RateLimiting.RateLimiterOptionsExtensions.AddNoLimiter(this Microsoft.AspNetCore.RateLimiting.RateLimiterOptions! options, string! policyName) -> Microsoft.AspNetCore.RateLimiting.RateLimiterOptions!
30-
static Microsoft.AspNetCore.RateLimiting.RateLimiterOptionsExtensions.AddSlidingWindowLimiter(this Microsoft.AspNetCore.RateLimiting.RateLimiterOptions! options, string! policyName, System.Threading.RateLimiting.SlidingWindowRateLimiterOptions! slidingWindowRateLimiterOptions) -> Microsoft.AspNetCore.RateLimiting.RateLimiterOptions!
31-
static Microsoft.AspNetCore.RateLimiting.RateLimiterOptionsExtensions.AddTokenBucketLimiter(this Microsoft.AspNetCore.RateLimiting.RateLimiterOptions! options, string! policyName, System.Threading.RateLimiting.TokenBucketRateLimiterOptions! tokenBucketRateLimiterOptions) -> Microsoft.AspNetCore.RateLimiting.RateLimiterOptions!
35+
static Microsoft.AspNetCore.Builder.RateLimiterServiceCollectionExtensions.AddRateLimiter(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services, System.Action<Microsoft.AspNetCore.RateLimiting.RateLimiterOptions!>! configureOptions) -> Microsoft.Extensions.DependencyInjection.IServiceCollection!
36+
static Microsoft.AspNetCore.RateLimiting.RateLimiterOptionsExtensions.AddConcurrencyLimiter(this Microsoft.AspNetCore.RateLimiting.RateLimiterOptions! options, string! policyName, System.Action<System.Threading.RateLimiting.ConcurrencyLimiterOptions!>! configureOptions) -> Microsoft.AspNetCore.RateLimiting.RateLimiterOptions!
37+
static Microsoft.AspNetCore.RateLimiting.RateLimiterOptionsExtensions.AddFixedWindowLimiter(this Microsoft.AspNetCore.RateLimiting.RateLimiterOptions! options, string! policyName, System.Action<System.Threading.RateLimiting.FixedWindowRateLimiterOptions!>! configureOptions) -> Microsoft.AspNetCore.RateLimiting.RateLimiterOptions!
38+
static Microsoft.AspNetCore.RateLimiting.RateLimiterOptionsExtensions.AddSlidingWindowLimiter(this Microsoft.AspNetCore.RateLimiting.RateLimiterOptions! options, string! policyName, System.Action<System.Threading.RateLimiting.SlidingWindowRateLimiterOptions!>! configureOptions) -> Microsoft.AspNetCore.RateLimiting.RateLimiterOptions!
39+
static Microsoft.AspNetCore.RateLimiting.RateLimiterOptionsExtensions.AddTokenBucketLimiter(this Microsoft.AspNetCore.RateLimiting.RateLimiterOptions! options, string! policyName, System.Action<System.Threading.RateLimiting.TokenBucketRateLimiterOptions!>! configureOptions) -> Microsoft.AspNetCore.RateLimiting.RateLimiterOptions!

src/Middleware/RateLimiting/src/RateLimiterEndpointConventionBuilderExtensions.cs

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,44 @@ public static TBuilder RequireRateLimiting<TBuilder>(this TBuilder builder, stri
2424

2525
builder.Add(endpointBuilder =>
2626
{
27-
endpointBuilder.Metadata.Add(new RateLimiterMetadata(policyName));
27+
endpointBuilder.Metadata.Add(new EnableRateLimitingAttribute(policyName));
28+
});
29+
30+
return builder;
31+
}
32+
33+
/// <summary>
34+
/// Adds the specified rate limiting policy to the endpoint(s).
35+
/// </summary>
36+
/// <param name="builder">The endpoint convention builder.</param>
37+
/// <param name="policy">The rate limiting policy to add to the endpoint.</param>
38+
/// <returns>The original convention builder parameter.</returns>
39+
public static TBuilder RequireRateLimiting<TBuilder, TPartitionKey>(this TBuilder builder, IRateLimiterPolicy<TPartitionKey> policy) where TBuilder : IEndpointConventionBuilder
40+
{
41+
ArgumentNullException.ThrowIfNull(builder);
42+
43+
ArgumentNullException.ThrowIfNull(policy);
44+
45+
builder.Add(endpointBuilder =>
46+
{
47+
endpointBuilder.Metadata.Add(new EnableRateLimitingAttribute(new DefaultRateLimiterPolicy(RateLimiterOptions.ConvertPartitioner<TPartitionKey>(null, policy.GetPartition), policy.OnRejected)));
48+
});
49+
return builder;
50+
}
51+
52+
/// <summary>
53+
/// Disables rate limiting on the endpoint(s).
54+
/// </summary>
55+
/// <param name="builder">The endpoint convention builder.</param>
56+
/// <returns>The original convention builder parameter.</returns>
57+
/// <remarks>Will skip both the global limiter, and any endpoint-specific limiters that apply to the endpoint(s).</remarks>
58+
public static TBuilder DisableRateLimiting<TBuilder>(this TBuilder builder) where TBuilder : IEndpointConventionBuilder
59+
{
60+
ArgumentNullException.ThrowIfNull(builder);
61+
62+
builder.Add(endpointBuilder =>
63+
{
64+
endpointBuilder.Metadata.Add(DisableRateLimitingAttribute.Instance);
2865
});
2966

3067
return builder;

src/Middleware/RateLimiting/src/RateLimiterMetadata.cs

Lines changed: 0 additions & 24 deletions
This file was deleted.

src/Middleware/RateLimiting/src/RateLimiterOptions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ public RateLimiterOptions AddPolicy<TPartitionKey>(string policyName, IRateLimit
107107
}
108108

109109
// Converts a Partition<TKey> to a Partition<DefaultKeyType<TKey>> to prevent accidental collisions with the keys we create in the the RateLimiterOptionsExtensions.
110-
private static Func<HttpContext, RateLimitPartition<DefaultKeyType>> ConvertPartitioner<TPartitionKey>(string policyName, Func<HttpContext, RateLimitPartition<TPartitionKey>> partitioner)
110+
internal static Func<HttpContext, RateLimitPartition<DefaultKeyType>> ConvertPartitioner<TPartitionKey>(string? policyName, Func<HttpContext, RateLimitPartition<TPartitionKey>> partitioner)
111111
{
112112
return context =>
113113
{

0 commit comments

Comments
 (0)