From ef9b7c47c2e5535cf5dbcac19cdd4e0f0081c23f Mon Sep 17 00:00:00 2001 From: Will Godbe Date: Fri, 20 May 2022 14:28:41 -0700 Subject: [PATCH 01/35] Add IEndpointConventionBuilder extensions for RateLimitingMiddleware --- .../RateLimiting/src/IRateLimiterMetadata.cs | 15 ++++++++ .../RateLimiting/src/PublicAPI.Unshipped.txt | 7 ++++ ...iterEndpointConventionBuilderExtensions.cs | 38 +++++++++++++++++++ .../RateLimiting/src/RateLimiterMetadata.cs | 24 ++++++++++++ .../RateLimiting/src/RateLimiterOptions.cs | 1 - 5 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 src/Middleware/RateLimiting/src/IRateLimiterMetadata.cs create mode 100644 src/Middleware/RateLimiting/src/RateLimiterEndpointConventionBuilderExtensions.cs create mode 100644 src/Middleware/RateLimiting/src/RateLimiterMetadata.cs diff --git a/src/Middleware/RateLimiting/src/IRateLimiterMetadata.cs b/src/Middleware/RateLimiting/src/IRateLimiterMetadata.cs new file mode 100644 index 000000000000..7e2797d329f2 --- /dev/null +++ b/src/Middleware/RateLimiting/src/IRateLimiterMetadata.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.AspNetCore.RateLimiting; + +/// +/// An interface which can be used to identify a type which provides metadata needed for enabling request rate limiting support. +/// +public interface IRateLimiterMetadata +{ + /// + /// The name of the limiter which needs to be applied. + /// + string Name { get; } +} diff --git a/src/Middleware/RateLimiting/src/PublicAPI.Unshipped.txt b/src/Middleware/RateLimiting/src/PublicAPI.Unshipped.txt index b62cec89b1cc..ba0c5b5d2a38 100644 --- a/src/Middleware/RateLimiting/src/PublicAPI.Unshipped.txt +++ b/src/Middleware/RateLimiting/src/PublicAPI.Unshipped.txt @@ -1,3 +1,9 @@ +Microsoft.AspNetCore.Builder.RateLimiterEndpointConventionBuilderExtensions +Microsoft.AspNetCore.RateLimiting.IRateLimiterMetadata +Microsoft.AspNetCore.RateLimiting.IRateLimiterMetadata.Name.get -> string! +Microsoft.AspNetCore.RateLimiting.RateLimiterMetadata +Microsoft.AspNetCore.RateLimiting.RateLimiterMetadata.Name.get -> string! +Microsoft.AspNetCore.RateLimiting.RateLimiterMetadata.RateLimiterMetadata(string! name) -> void Microsoft.AspNetCore.RateLimiting.RateLimiterOptions Microsoft.AspNetCore.RateLimiting.RateLimiterOptions.DefaultRejectionStatusCode.get -> int Microsoft.AspNetCore.RateLimiting.RateLimiterOptions.DefaultRejectionStatusCode.set -> void @@ -7,5 +13,6 @@ Microsoft.AspNetCore.RateLimiting.RateLimiterOptions.OnRejected.get -> System.Fu Microsoft.AspNetCore.RateLimiting.RateLimiterOptions.OnRejected.set -> void Microsoft.AspNetCore.RateLimiting.RateLimiterOptions.RateLimiterOptions() -> void Microsoft.AspNetCore.RateLimiting.RateLimitingApplicationBuilderExtensions +static Microsoft.AspNetCore.Builder.RateLimiterEndpointConventionBuilderExtensions.RequireRateLimiting(this TBuilder builder, string! name) -> TBuilder static Microsoft.AspNetCore.RateLimiting.RateLimitingApplicationBuilderExtensions.UseRateLimiter(this Microsoft.AspNetCore.Builder.IApplicationBuilder! app) -> Microsoft.AspNetCore.Builder.IApplicationBuilder! static Microsoft.AspNetCore.RateLimiting.RateLimitingApplicationBuilderExtensions.UseRateLimiter(this Microsoft.AspNetCore.Builder.IApplicationBuilder! app, Microsoft.AspNetCore.RateLimiting.RateLimiterOptions! options) -> Microsoft.AspNetCore.Builder.IApplicationBuilder! diff --git a/src/Middleware/RateLimiting/src/RateLimiterEndpointConventionBuilderExtensions.cs b/src/Middleware/RateLimiting/src/RateLimiterEndpointConventionBuilderExtensions.cs new file mode 100644 index 000000000000..fe47ceef28f8 --- /dev/null +++ b/src/Middleware/RateLimiting/src/RateLimiterEndpointConventionBuilderExtensions.cs @@ -0,0 +1,38 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.AspNetCore.RateLimiting; + +namespace Microsoft.AspNetCore.Builder; + +/// +/// Rate limiter extension methods for . +/// +public static class RateLimiterEndpointConventionBuilderExtensions +{ + /// + /// Adds the specified rate limiter to the endpoint(s). + /// + /// The endpoint convention builder. + /// The name of the rate limiter to add to the endpoint. + /// The original convention builder parameter. + public static TBuilder RequireRateLimiting(this TBuilder builder, String name) where TBuilder : IEndpointConventionBuilder + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + builder.Add(endpointBuilder => + { + endpointBuilder.Metadata.Add(new RateLimiterMetadata(name)); + }); + + return builder; + } +} diff --git a/src/Middleware/RateLimiting/src/RateLimiterMetadata.cs b/src/Middleware/RateLimiting/src/RateLimiterMetadata.cs new file mode 100644 index 000000000000..a556c8fde820 --- /dev/null +++ b/src/Middleware/RateLimiting/src/RateLimiterMetadata.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.AspNetCore.RateLimiting; + +/// +/// Metadata that provides endpoint-specific request rate limiting. +/// +public class RateLimiterMetadata : IRateLimiterMetadata +{ + /// + /// Creates a new instance of using the specified limiter. + /// + /// The name of the limiter which needs to be applied. + public RateLimiterMetadata(string name) + { + Name = name; + } + + /// + /// The name of the limiter which needs to be applied. + /// + public string Name { get; } +} diff --git a/src/Middleware/RateLimiting/src/RateLimiterOptions.cs b/src/Middleware/RateLimiting/src/RateLimiterOptions.cs index bc1ab30b7a37..fc1272eb0084 100644 --- a/src/Middleware/RateLimiting/src/RateLimiterOptions.cs +++ b/src/Middleware/RateLimiting/src/RateLimiterOptions.cs @@ -11,7 +11,6 @@ namespace Microsoft.AspNetCore.RateLimiting; /// public sealed class RateLimiterOptions { - // TODO - Provide a default? private PartitionedRateLimiter _limiter = new NoLimiter(); private Func _onRejected = (context, lease) => { From df78df1684a1afcac0030ef257b4ae5dfcce95d0 Mon Sep 17 00:00:00 2001 From: Will Godbe Date: Thu, 2 Jun 2022 10:49:37 -0700 Subject: [PATCH 02/35] Some changes --- src/Middleware/RateLimiting/src/AspNetKey.cs | 13 ++++ src/Middleware/RateLimiting/src/NoKey.cs | 10 +++ .../RateLimiting/src/RateLimiterOptions.cs | 29 ++++++++ .../src/RateLimiterOptionsExtensions.cs | 73 +++++++++++++++++++ 4 files changed, 125 insertions(+) create mode 100644 src/Middleware/RateLimiting/src/AspNetKey.cs create mode 100644 src/Middleware/RateLimiting/src/NoKey.cs create mode 100644 src/Middleware/RateLimiting/src/RateLimiterOptionsExtensions.cs diff --git a/src/Middleware/RateLimiting/src/AspNetKey.cs b/src/Middleware/RateLimiting/src/AspNetKey.cs new file mode 100644 index 000000000000..cb7f629db901 --- /dev/null +++ b/src/Middleware/RateLimiting/src/AspNetKey.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.AspNetCore.RateLimiting; +internal sealed class AspNetKey +{ + public AspNetKey(TKey key) + { + Key = key; + } + + public TKey Key { get; } +} diff --git a/src/Middleware/RateLimiting/src/NoKey.cs b/src/Middleware/RateLimiting/src/NoKey.cs new file mode 100644 index 000000000000..a0677cf69ef8 --- /dev/null +++ b/src/Middleware/RateLimiting/src/NoKey.cs @@ -0,0 +1,10 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.AspNetCore.RateLimiting; +internal sealed class NoKey +{ + private NoKey() { } + + public static NoKey Instance { get; } = new NoKey(); +} diff --git a/src/Middleware/RateLimiting/src/RateLimiterOptions.cs b/src/Middleware/RateLimiting/src/RateLimiterOptions.cs index fc1272eb0084..a3273a56ff61 100644 --- a/src/Middleware/RateLimiting/src/RateLimiterOptions.cs +++ b/src/Middleware/RateLimiting/src/RateLimiterOptions.cs @@ -16,6 +16,8 @@ public sealed class RateLimiterOptions { return Task.CompletedTask; }; + private IDictionary>> PartitionMap { get; } + = new Dictionary>>(StringComparer.Ordinal); /// /// Gets or sets the @@ -44,4 +46,31 @@ public Func OnRejected /// will "win" over this default. /// public int DefaultRejectionStatusCode { get; set; } = StatusCodes.Status503ServiceUnavailable; + + /// + /// Adds a new rate limiter with the given name. + /// + /// The name to be associated with the given + /// Method called every time an Acquire or WaitAsync call is made to figure out what rate limiter to apply to the request. + public RateLimiterOptions AddLimiter(string name, Func> partitioner) + { + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + if (partitioner == null) + { + throw new ArgumentNullException(nameof(partitioner)); + } + + if (PartitionMap.ContainsKey(name)) + { + throw new ArgumentException("There already exists a partition with the name {name}"); + } + + PartitionMap.Add(name, partitioner); + + return this; + } } diff --git a/src/Middleware/RateLimiting/src/RateLimiterOptionsExtensions.cs b/src/Middleware/RateLimiting/src/RateLimiterOptionsExtensions.cs new file mode 100644 index 000000000000..62e502189e2f --- /dev/null +++ b/src/Middleware/RateLimiting/src/RateLimiterOptionsExtensions.cs @@ -0,0 +1,73 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Threading.RateLimiting; +using Microsoft.AspNetCore.Http; + +namespace Microsoft.AspNetCore.RateLimiting; +public static class RateLimiterOptionsExtensions +{ + /// + /// Adds a new with the given to the . + /// + /// The to add a limiter to. + /// The name that will be associated with the limiter. + /// The to be used for the limiter. + /// This . + public static RateLimiterOptions AddTokenBucketRateLimiter(this RateLimiterOptions options, string name, TokenBucketRateLimiterOptions tokenBucketRateLimiterOptions) + { + return options.AddLimiter(name, context => + { + return RateLimitPartition.CreateTokenBucketLimiter(NoKey.Instance, + _ => tokenBucketRateLimiterOptions); + }); + } + + /// + /// Adds a new with the given to the . + /// + /// The to add a limiter to. + /// The name that will be associated with the limiter. + /// The to be used for the limiter. + /// This . + public static RateLimiterOptions AddFixedWindowRateLimiter(this RateLimiterOptions options, string name, FixedWindowRateLimiterOptions fixedWindowRateLimiterOptions) + { + return options.AddLimiter(name, context => + { + return RateLimitPartition.CreateFixedWindowLimiter(NoKey.Instance, + _ => fixedWindowRateLimiterOptions); + }); + } + + /// + /// Adds a new with the given to the . + /// + /// The to add a limiter to. + /// The name that will be associated with the limiter. + /// The to be used for the limiter. + /// This . + public static RateLimiterOptions AddSlidingWindowRateLimiter(this RateLimiterOptions options, string name, SlidingWindowRateLimiterOptions slidingWindowRateLimiterOptions) + { + return options.AddLimiter(name, context => + { + return RateLimitPartition.CreateSlidingWindowLimiter(NoKey.Instance, + _ => slidingWindowRateLimiterOptions); + }); + } + + /// + /// Adds a new with the given to the . + /// + /// The to add a limiter to. + /// The name that will be associated with the limiter. + /// The to be used for the limiter. + /// This . + public static RateLimiterOptions AddConcurrencyLimiter(this RateLimiterOptions options, string name, ConcurrencyLimiterOptions concurrencyLimiterOptions) + { + return options.AddLimiter(name, context => + { + return RateLimitPartition.CreateConcurrencyLimiter(NoKey.Instance, + _ => concurrencyLimiterOptions); + }); + } +} From 6a4c91cb98300c496139d6abab80563535b96efa Mon Sep 17 00:00:00 2001 From: Will Godbe Date: Tue, 7 Jun 2022 15:40:50 -0700 Subject: [PATCH 03/35] More --- .../RateLimiting/src/AspNetKey.T.cs | 13 +++++++++ src/Middleware/RateLimiting/src/AspNetKey.cs | 8 +----- .../RateLimiting/src/PublicAPI.Unshipped.txt | 6 ++++ ...ateLimiterApplicationBuilderExtensions.cs} | 0 .../RateLimiting/src/RateLimiterOptions.cs | 9 +++--- .../src/RateLimiterOptionsExtensions.cs | 28 +++++++++++-------- .../RateLimiting/src/RateLimiterPolicy.cs | 23 +++++++++++++++ ...mitingApplicationBuilderExtensionsTests.cs | 2 +- 8 files changed, 65 insertions(+), 24 deletions(-) create mode 100644 src/Middleware/RateLimiting/src/AspNetKey.T.cs rename src/Middleware/RateLimiting/src/{RateLimitingApplicationBuilderExtensions.cs => RateLimiterApplicationBuilderExtensions.cs} (100%) create mode 100644 src/Middleware/RateLimiting/src/RateLimiterPolicy.cs diff --git a/src/Middleware/RateLimiting/src/AspNetKey.T.cs b/src/Middleware/RateLimiting/src/AspNetKey.T.cs new file mode 100644 index 000000000000..7a1e889b1d60 --- /dev/null +++ b/src/Middleware/RateLimiting/src/AspNetKey.T.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.AspNetCore.RateLimiting; +internal sealed class AspNetKey: AspNetKey +{ + public AspNetKey(TKey key) + { + Key = key; + } + + public TKey Key { get; } +} diff --git a/src/Middleware/RateLimiting/src/AspNetKey.cs b/src/Middleware/RateLimiting/src/AspNetKey.cs index cb7f629db901..89ef83ff8b1e 100644 --- a/src/Middleware/RateLimiting/src/AspNetKey.cs +++ b/src/Middleware/RateLimiting/src/AspNetKey.cs @@ -2,12 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. namespace Microsoft.AspNetCore.RateLimiting; -internal sealed class AspNetKey +internal abstract class AspNetKey { - public AspNetKey(TKey key) - { - Key = key; - } - - public TKey Key { get; } } diff --git a/src/Middleware/RateLimiting/src/PublicAPI.Unshipped.txt b/src/Middleware/RateLimiting/src/PublicAPI.Unshipped.txt index ba0c5b5d2a38..3b35e71330a8 100644 --- a/src/Middleware/RateLimiting/src/PublicAPI.Unshipped.txt +++ b/src/Middleware/RateLimiting/src/PublicAPI.Unshipped.txt @@ -5,6 +5,7 @@ Microsoft.AspNetCore.RateLimiting.RateLimiterMetadata Microsoft.AspNetCore.RateLimiting.RateLimiterMetadata.Name.get -> string! Microsoft.AspNetCore.RateLimiting.RateLimiterMetadata.RateLimiterMetadata(string! name) -> void Microsoft.AspNetCore.RateLimiting.RateLimiterOptions +Microsoft.AspNetCore.RateLimiting.RateLimiterOptions.AddPolicy(string! name, System.Func>! partitioner, bool global = false) -> Microsoft.AspNetCore.RateLimiting.RateLimiterOptions! Microsoft.AspNetCore.RateLimiting.RateLimiterOptions.DefaultRejectionStatusCode.get -> int Microsoft.AspNetCore.RateLimiting.RateLimiterOptions.DefaultRejectionStatusCode.set -> void Microsoft.AspNetCore.RateLimiting.RateLimiterOptions.Limiter.get -> System.Threading.RateLimiting.PartitionedRateLimiter! @@ -12,7 +13,12 @@ Microsoft.AspNetCore.RateLimiting.RateLimiterOptions.Limiter.set -> void Microsoft.AspNetCore.RateLimiting.RateLimiterOptions.OnRejected.get -> System.Func! Microsoft.AspNetCore.RateLimiting.RateLimiterOptions.OnRejected.set -> void Microsoft.AspNetCore.RateLimiting.RateLimiterOptions.RateLimiterOptions() -> void +Microsoft.AspNetCore.RateLimiting.RateLimiterOptionsExtensions Microsoft.AspNetCore.RateLimiting.RateLimitingApplicationBuilderExtensions static Microsoft.AspNetCore.Builder.RateLimiterEndpointConventionBuilderExtensions.RequireRateLimiting(this TBuilder builder, string! name) -> TBuilder +static Microsoft.AspNetCore.RateLimiting.RateLimiterOptionsExtensions.AddConcurrencyLimiter(this Microsoft.AspNetCore.RateLimiting.RateLimiterOptions! options, string! name, System.Threading.RateLimiting.ConcurrencyLimiterOptions! concurrencyLimiterOptions, bool global = false) -> Microsoft.AspNetCore.RateLimiting.RateLimiterOptions! +static Microsoft.AspNetCore.RateLimiting.RateLimiterOptionsExtensions.AddFixedWindowRateLimiter(this Microsoft.AspNetCore.RateLimiting.RateLimiterOptions! options, string! name, System.Threading.RateLimiting.FixedWindowRateLimiterOptions! fixedWindowRateLimiterOptions, bool global = false) -> Microsoft.AspNetCore.RateLimiting.RateLimiterOptions! +static Microsoft.AspNetCore.RateLimiting.RateLimiterOptionsExtensions.AddSlidingWindowRateLimiter(this Microsoft.AspNetCore.RateLimiting.RateLimiterOptions! options, string! name, System.Threading.RateLimiting.SlidingWindowRateLimiterOptions! slidingWindowRateLimiterOptions, bool global = false) -> Microsoft.AspNetCore.RateLimiting.RateLimiterOptions! +static Microsoft.AspNetCore.RateLimiting.RateLimiterOptionsExtensions.AddTokenBucketRateLimiter(this Microsoft.AspNetCore.RateLimiting.RateLimiterOptions! options, string! name, System.Threading.RateLimiting.TokenBucketRateLimiterOptions! tokenBucketRateLimiterOptions, bool global = false) -> Microsoft.AspNetCore.RateLimiting.RateLimiterOptions! static Microsoft.AspNetCore.RateLimiting.RateLimitingApplicationBuilderExtensions.UseRateLimiter(this Microsoft.AspNetCore.Builder.IApplicationBuilder! app) -> Microsoft.AspNetCore.Builder.IApplicationBuilder! static Microsoft.AspNetCore.RateLimiting.RateLimitingApplicationBuilderExtensions.UseRateLimiter(this Microsoft.AspNetCore.Builder.IApplicationBuilder! app, Microsoft.AspNetCore.RateLimiting.RateLimiterOptions! options) -> Microsoft.AspNetCore.Builder.IApplicationBuilder! diff --git a/src/Middleware/RateLimiting/src/RateLimitingApplicationBuilderExtensions.cs b/src/Middleware/RateLimiting/src/RateLimiterApplicationBuilderExtensions.cs similarity index 100% rename from src/Middleware/RateLimiting/src/RateLimitingApplicationBuilderExtensions.cs rename to src/Middleware/RateLimiting/src/RateLimiterApplicationBuilderExtensions.cs diff --git a/src/Middleware/RateLimiting/src/RateLimiterOptions.cs b/src/Middleware/RateLimiting/src/RateLimiterOptions.cs index a3273a56ff61..eb9d516f9814 100644 --- a/src/Middleware/RateLimiting/src/RateLimiterOptions.cs +++ b/src/Middleware/RateLimiting/src/RateLimiterOptions.cs @@ -16,8 +16,8 @@ public sealed class RateLimiterOptions { return Task.CompletedTask; }; - private IDictionary>> PartitionMap { get; } - = new Dictionary>>(StringComparer.Ordinal); + private IDictionary PartitionMap { get; } + = new Dictionary(StringComparer.Ordinal); /// /// Gets or sets the @@ -48,11 +48,12 @@ public Func OnRejected public int DefaultRejectionStatusCode { get; set; } = StatusCodes.Status503ServiceUnavailable; /// - /// Adds a new rate limiter with the given name. + /// Adds a new rate limiting policy with the given name. /// /// The name to be associated with the given /// Method called every time an Acquire or WaitAsync call is made to figure out what rate limiter to apply to the request. - public RateLimiterOptions AddLimiter(string name, Func> partitioner) + /// Determines if this policy should be shared across endpoints. Defaults to false. + public RateLimiterOptions AddPolicy(string name, Func> partitioner, bool global = false) { if (name == null) { diff --git a/src/Middleware/RateLimiting/src/RateLimiterOptionsExtensions.cs b/src/Middleware/RateLimiting/src/RateLimiterOptionsExtensions.cs index 62e502189e2f..5c4d2e98b0e3 100644 --- a/src/Middleware/RateLimiting/src/RateLimiterOptionsExtensions.cs +++ b/src/Middleware/RateLimiting/src/RateLimiterOptionsExtensions.cs @@ -13,14 +13,15 @@ public static class RateLimiterOptionsExtensions /// The to add a limiter to. /// The name that will be associated with the limiter. /// The to be used for the limiter. + /// Determines if this policy should be shared across endpoints. Defaults to false. /// This . - public static RateLimiterOptions AddTokenBucketRateLimiter(this RateLimiterOptions options, string name, TokenBucketRateLimiterOptions tokenBucketRateLimiterOptions) + public static RateLimiterOptions AddTokenBucketRateLimiter(this RateLimiterOptions options, string name, TokenBucketRateLimiterOptions tokenBucketRateLimiterOptions, bool global = false) { - return options.AddLimiter(name, context => + return options.AddPolicy(name, context => { return RateLimitPartition.CreateTokenBucketLimiter(NoKey.Instance, _ => tokenBucketRateLimiterOptions); - }); + }, global); } /// @@ -29,14 +30,15 @@ public static RateLimiterOptions AddTokenBucketRateLimiter(this RateLimiterOptio /// The to add a limiter to. /// The name that will be associated with the limiter. /// The to be used for the limiter. + /// Determines if this policy should be shared across endpoints. Defaults to false. /// This . - public static RateLimiterOptions AddFixedWindowRateLimiter(this RateLimiterOptions options, string name, FixedWindowRateLimiterOptions fixedWindowRateLimiterOptions) + public static RateLimiterOptions AddFixedWindowRateLimiter(this RateLimiterOptions options, string name, FixedWindowRateLimiterOptions fixedWindowRateLimiterOptions, bool global = false) { - return options.AddLimiter(name, context => + return options.AddPolicy(name, context => { return RateLimitPartition.CreateFixedWindowLimiter(NoKey.Instance, _ => fixedWindowRateLimiterOptions); - }); + }, global); } /// @@ -45,14 +47,15 @@ public static RateLimiterOptions AddFixedWindowRateLimiter(this RateLimiterOptio /// The to add a limiter to. /// The name that will be associated with the limiter. /// The to be used for the limiter. + /// Determines if this policy should be shared across endpoints. Defaults to false. /// This . - public static RateLimiterOptions AddSlidingWindowRateLimiter(this RateLimiterOptions options, string name, SlidingWindowRateLimiterOptions slidingWindowRateLimiterOptions) + public static RateLimiterOptions AddSlidingWindowRateLimiter(this RateLimiterOptions options, string name, SlidingWindowRateLimiterOptions slidingWindowRateLimiterOptions, bool global = false) { - return options.AddLimiter(name, context => + return options.AddPolicy(name, context => { return RateLimitPartition.CreateSlidingWindowLimiter(NoKey.Instance, _ => slidingWindowRateLimiterOptions); - }); + }, global); } /// @@ -61,13 +64,14 @@ public static RateLimiterOptions AddSlidingWindowRateLimiter(this RateLimiterOpt /// The to add a limiter to. /// The name that will be associated with the limiter. /// The to be used for the limiter. + /// Determines if this policy should be shared across endpoints. Defaults to false. /// This . - public static RateLimiterOptions AddConcurrencyLimiter(this RateLimiterOptions options, string name, ConcurrencyLimiterOptions concurrencyLimiterOptions) + public static RateLimiterOptions AddConcurrencyLimiter(this RateLimiterOptions options, string name, ConcurrencyLimiterOptions concurrencyLimiterOptions, bool global = false) { - return options.AddLimiter(name, context => + return options.AddPolicy(name, context => { return RateLimitPartition.CreateConcurrencyLimiter(NoKey.Instance, _ => concurrencyLimiterOptions); - }); + }, global); } } diff --git a/src/Middleware/RateLimiting/src/RateLimiterPolicy.cs b/src/Middleware/RateLimiting/src/RateLimiterPolicy.cs new file mode 100644 index 000000000000..0364d7a4571b --- /dev/null +++ b/src/Middleware/RateLimiting/src/RateLimiterPolicy.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.ComponentModel.DataAnnotations; +using System.Threading.RateLimiting; +using Microsoft.AspNetCore.Http; + +namespace Microsoft.AspNetCore.RateLimiting; +internal sealed class RateLimiterPolicy +{ + [Required] + public Func> Partitioner { get; init; } + + [Required] + public string PolicyName { get; init; } + + public PartitionKeyScope KeyScope { get; init; } = PartitionKeyScope.Policy; +} +internal enum PartitionKeyScope +{ + Policy, + Global +} diff --git a/src/Middleware/RateLimiting/test/RateLimitingApplicationBuilderExtensionsTests.cs b/src/Middleware/RateLimiting/test/RateLimitingApplicationBuilderExtensionsTests.cs index 37d6d5bc4c37..71a5160802a5 100644 --- a/src/Middleware/RateLimiting/test/RateLimitingApplicationBuilderExtensionsTests.cs +++ b/src/Middleware/RateLimiting/test/RateLimitingApplicationBuilderExtensionsTests.cs @@ -14,7 +14,7 @@ public class RateLimitingApplicationBuilderExtensionsTests : LoggedTest [Fact] public void UseRateLimiter_ThrowsOnNullAppBuilder() { - Assert.Throws(() => RateLimitingApplicationBuilderExtensions.UseRateLimiter(null)); + Assert.Throws(() => RateLimiterApplicationBuilderExtensions.UseRateLimiter(null)); } [Fact] From 982cdfb1fd4a77f0121350a2aab83d3e38c81707 Mon Sep 17 00:00:00 2001 From: Will Godbe Date: Thu, 9 Jun 2022 12:04:56 -0700 Subject: [PATCH 04/35] Rename --- src/Middleware/RateLimiting/src/PublicAPI.Unshipped.txt | 6 +++--- .../src/RateLimiterApplicationBuilderExtensions.cs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Middleware/RateLimiting/src/PublicAPI.Unshipped.txt b/src/Middleware/RateLimiting/src/PublicAPI.Unshipped.txt index 3b35e71330a8..1259a617f466 100644 --- a/src/Middleware/RateLimiting/src/PublicAPI.Unshipped.txt +++ b/src/Middleware/RateLimiting/src/PublicAPI.Unshipped.txt @@ -1,6 +1,7 @@ Microsoft.AspNetCore.Builder.RateLimiterEndpointConventionBuilderExtensions Microsoft.AspNetCore.RateLimiting.IRateLimiterMetadata Microsoft.AspNetCore.RateLimiting.IRateLimiterMetadata.Name.get -> string! +Microsoft.AspNetCore.RateLimiting.RateLimiterApplicationBuilderExtensions Microsoft.AspNetCore.RateLimiting.RateLimiterMetadata Microsoft.AspNetCore.RateLimiting.RateLimiterMetadata.Name.get -> string! Microsoft.AspNetCore.RateLimiting.RateLimiterMetadata.RateLimiterMetadata(string! name) -> void @@ -14,11 +15,10 @@ Microsoft.AspNetCore.RateLimiting.RateLimiterOptions.OnRejected.get -> System.Fu Microsoft.AspNetCore.RateLimiting.RateLimiterOptions.OnRejected.set -> void Microsoft.AspNetCore.RateLimiting.RateLimiterOptions.RateLimiterOptions() -> void Microsoft.AspNetCore.RateLimiting.RateLimiterOptionsExtensions -Microsoft.AspNetCore.RateLimiting.RateLimitingApplicationBuilderExtensions static Microsoft.AspNetCore.Builder.RateLimiterEndpointConventionBuilderExtensions.RequireRateLimiting(this TBuilder builder, string! name) -> TBuilder +static Microsoft.AspNetCore.RateLimiting.RateLimiterApplicationBuilderExtensions.UseRateLimiter(this Microsoft.AspNetCore.Builder.IApplicationBuilder! app) -> Microsoft.AspNetCore.Builder.IApplicationBuilder! +static Microsoft.AspNetCore.RateLimiting.RateLimiterApplicationBuilderExtensions.UseRateLimiter(this Microsoft.AspNetCore.Builder.IApplicationBuilder! app, Microsoft.AspNetCore.RateLimiting.RateLimiterOptions! options) -> Microsoft.AspNetCore.Builder.IApplicationBuilder! static Microsoft.AspNetCore.RateLimiting.RateLimiterOptionsExtensions.AddConcurrencyLimiter(this Microsoft.AspNetCore.RateLimiting.RateLimiterOptions! options, string! name, System.Threading.RateLimiting.ConcurrencyLimiterOptions! concurrencyLimiterOptions, bool global = false) -> Microsoft.AspNetCore.RateLimiting.RateLimiterOptions! static Microsoft.AspNetCore.RateLimiting.RateLimiterOptionsExtensions.AddFixedWindowRateLimiter(this Microsoft.AspNetCore.RateLimiting.RateLimiterOptions! options, string! name, System.Threading.RateLimiting.FixedWindowRateLimiterOptions! fixedWindowRateLimiterOptions, bool global = false) -> Microsoft.AspNetCore.RateLimiting.RateLimiterOptions! static Microsoft.AspNetCore.RateLimiting.RateLimiterOptionsExtensions.AddSlidingWindowRateLimiter(this Microsoft.AspNetCore.RateLimiting.RateLimiterOptions! options, string! name, System.Threading.RateLimiting.SlidingWindowRateLimiterOptions! slidingWindowRateLimiterOptions, bool global = false) -> Microsoft.AspNetCore.RateLimiting.RateLimiterOptions! static Microsoft.AspNetCore.RateLimiting.RateLimiterOptionsExtensions.AddTokenBucketRateLimiter(this Microsoft.AspNetCore.RateLimiting.RateLimiterOptions! options, string! name, System.Threading.RateLimiting.TokenBucketRateLimiterOptions! tokenBucketRateLimiterOptions, bool global = false) -> Microsoft.AspNetCore.RateLimiting.RateLimiterOptions! -static Microsoft.AspNetCore.RateLimiting.RateLimitingApplicationBuilderExtensions.UseRateLimiter(this Microsoft.AspNetCore.Builder.IApplicationBuilder! app) -> Microsoft.AspNetCore.Builder.IApplicationBuilder! -static Microsoft.AspNetCore.RateLimiting.RateLimitingApplicationBuilderExtensions.UseRateLimiter(this Microsoft.AspNetCore.Builder.IApplicationBuilder! app, Microsoft.AspNetCore.RateLimiting.RateLimiterOptions! options) -> Microsoft.AspNetCore.Builder.IApplicationBuilder! diff --git a/src/Middleware/RateLimiting/src/RateLimiterApplicationBuilderExtensions.cs b/src/Middleware/RateLimiting/src/RateLimiterApplicationBuilderExtensions.cs index 7cda2ab98c53..2e5c7b070cb0 100644 --- a/src/Middleware/RateLimiting/src/RateLimiterApplicationBuilderExtensions.cs +++ b/src/Middleware/RateLimiting/src/RateLimiterApplicationBuilderExtensions.cs @@ -9,7 +9,7 @@ namespace Microsoft.AspNetCore.RateLimiting; /// /// Extension methods for the RateLimiting middleware. /// -public static class RateLimitingApplicationBuilderExtensions +public static class RateLimiterApplicationBuilderExtensions { /// /// Enables rate limiting for the application. From 12ce9d6ad6ff70aabd7ea77efebc596bbc5dd50c Mon Sep 17 00:00:00 2001 From: Will Godbe Date: Tue, 21 Jun 2022 12:46:47 -0700 Subject: [PATCH 05/35] More --- .../RateLimiting/src/IRateLimiterPolicy.cs | 14 ++++++ ...iterEndpointConventionBuilderExtensions.cs | 10 ++-- .../RateLimiting/src/RateLimiterOptions.cs | 13 ++++- .../src/RateLimiterOptionsExtensions.cs | 50 +++++++++++-------- .../RateLimiting/src/RateLimiterPolicy.cs | 9 +++- 5 files changed, 69 insertions(+), 27 deletions(-) create mode 100644 src/Middleware/RateLimiting/src/IRateLimiterPolicy.cs diff --git a/src/Middleware/RateLimiting/src/IRateLimiterPolicy.cs b/src/Middleware/RateLimiting/src/IRateLimiterPolicy.cs new file mode 100644 index 000000000000..fe1e783ae0aa --- /dev/null +++ b/src/Middleware/RateLimiting/src/IRateLimiterPolicy.cs @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Threading.RateLimiting; +using Microsoft.AspNetCore.Http; + +namespace Microsoft.AspNetCore.RateLimiting; + +public interface IRateLimiterPolicy +{ + public int CustomRejectionStatusCode { get; } + + public RateLimitPartition GetPartition(HttpContext httpContext); +} diff --git a/src/Middleware/RateLimiting/src/RateLimiterEndpointConventionBuilderExtensions.cs b/src/Middleware/RateLimiting/src/RateLimiterEndpointConventionBuilderExtensions.cs index fe47ceef28f8..eaef2775d83c 100644 --- a/src/Middleware/RateLimiting/src/RateLimiterEndpointConventionBuilderExtensions.cs +++ b/src/Middleware/RateLimiting/src/RateLimiterEndpointConventionBuilderExtensions.cs @@ -14,23 +14,23 @@ public static class RateLimiterEndpointConventionBuilderExtensions /// Adds the specified rate limiter to the endpoint(s). /// /// The endpoint convention builder. - /// The name of the rate limiter to add to the endpoint. + /// The name of the rate limiter to add to the endpoint. /// The original convention builder parameter. - public static TBuilder RequireRateLimiting(this TBuilder builder, String name) where TBuilder : IEndpointConventionBuilder + public static TBuilder RequireRateLimiting(this TBuilder builder, String policyName) where TBuilder : IEndpointConventionBuilder { if (builder == null) { throw new ArgumentNullException(nameof(builder)); } - if (name == null) + if (policyName == null) { - throw new ArgumentNullException(nameof(name)); + throw new ArgumentNullException(nameof(policyName)); } builder.Add(endpointBuilder => { - endpointBuilder.Metadata.Add(new RateLimiterMetadata(name)); + endpointBuilder.Metadata.Add(new RateLimiterMetadata(policyName)); }); return builder; diff --git a/src/Middleware/RateLimiting/src/RateLimiterOptions.cs b/src/Middleware/RateLimiting/src/RateLimiterOptions.cs index eb9d516f9814..255657360b6d 100644 --- a/src/Middleware/RateLimiting/src/RateLimiterOptions.cs +++ b/src/Middleware/RateLimiting/src/RateLimiterOptions.cs @@ -53,7 +53,7 @@ public Func OnRejected /// The name to be associated with the given /// Method called every time an Acquire or WaitAsync call is made to figure out what rate limiter to apply to the request. /// Determines if this policy should be shared across endpoints. Defaults to false. - public RateLimiterOptions AddPolicy(string name, Func> partitioner, bool global = false) + public RateLimiterOptions AddPolicy(string policyName, Func> partitioner) { if (name == null) { @@ -73,5 +73,16 @@ public RateLimiterOptions AddPolicy(string name, Func> func = context => + { + RateLimitPartition partition = partitioner(context); + return new RateLimitPartition>(new AspNetKey(partition.PartitionKey), partition.Factory(partition.PartitionKey)) + }; + } + + public RateLimiterOptions AddPolicy(string name, bool global = false) where TPolicy : IRateLimiterPolicy + { + return this; } } diff --git a/src/Middleware/RateLimiting/src/RateLimiterOptionsExtensions.cs b/src/Middleware/RateLimiting/src/RateLimiterOptionsExtensions.cs index 5c4d2e98b0e3..dc19551fde23 100644 --- a/src/Middleware/RateLimiting/src/RateLimiterOptionsExtensions.cs +++ b/src/Middleware/RateLimiting/src/RateLimiterOptionsExtensions.cs @@ -11,67 +11,77 @@ public static class RateLimiterOptionsExtensions /// Adds a new with the given to the . /// /// The to add a limiter to. - /// The name that will be associated with the limiter. + /// The name that will be associated with the limiter. /// The to be used for the limiter. - /// Determines if this policy should be shared across endpoints. Defaults to false. /// This . - public static RateLimiterOptions AddTokenBucketRateLimiter(this RateLimiterOptions options, string name, TokenBucketRateLimiterOptions tokenBucketRateLimiterOptions, bool global = false) + public static RateLimiterOptions AddTokenBucketRateLimiter(this RateLimiterOptions options, string policyName, TokenBucketRateLimiterOptions tokenBucketRateLimiterOptions) { - return options.AddPolicy(name, context => + return options.AddPolicy(policyName, context => { return RateLimitPartition.CreateTokenBucketLimiter(NoKey.Instance, _ => tokenBucketRateLimiterOptions); - }, global); + }); } /// /// Adds a new with the given to the . /// /// The to add a limiter to. - /// The name that will be associated with the limiter. + /// The name that will be associated with the limiter. /// The to be used for the limiter. - /// Determines if this policy should be shared across endpoints. Defaults to false. /// This . - public static RateLimiterOptions AddFixedWindowRateLimiter(this RateLimiterOptions options, string name, FixedWindowRateLimiterOptions fixedWindowRateLimiterOptions, bool global = false) + public static RateLimiterOptions AddFixedWindowRateLimiter(this RateLimiterOptions options, string policyName, FixedWindowRateLimiterOptions fixedWindowRateLimiterOptions) { - return options.AddPolicy(name, context => + return options.AddPolicy(policyName, context => { return RateLimitPartition.CreateFixedWindowLimiter(NoKey.Instance, _ => fixedWindowRateLimiterOptions); - }, global); + }); } /// /// Adds a new with the given to the . /// /// The to add a limiter to. - /// The name that will be associated with the limiter. + /// The name that will be associated with the limiter. /// The to be used for the limiter. - /// Determines if this policy should be shared across endpoints. Defaults to false. /// This . - public static RateLimiterOptions AddSlidingWindowRateLimiter(this RateLimiterOptions options, string name, SlidingWindowRateLimiterOptions slidingWindowRateLimiterOptions, bool global = false) + public static RateLimiterOptions AddSlidingWindowRateLimiter(this RateLimiterOptions options, string policyName, SlidingWindowRateLimiterOptions slidingWindowRateLimiterOptions) { - return options.AddPolicy(name, context => + return options.AddPolicy(policyName, context => { return RateLimitPartition.CreateSlidingWindowLimiter(NoKey.Instance, _ => slidingWindowRateLimiterOptions); - }, global); + }); } /// /// Adds a new with the given to the . /// /// The to add a limiter to. - /// The name that will be associated with the limiter. + /// The name that will be associated with the limiter. /// The to be used for the limiter. - /// Determines if this policy should be shared across endpoints. Defaults to false. /// This . - public static RateLimiterOptions AddConcurrencyLimiter(this RateLimiterOptions options, string name, ConcurrencyLimiterOptions concurrencyLimiterOptions, bool global = false) + public static RateLimiterOptions AddConcurrencyLimiter(this RateLimiterOptions options, string policyName, ConcurrencyLimiterOptions concurrencyLimiterOptions) { - return options.AddPolicy(name, context => + return options.AddPolicy(policyName, context => { return RateLimitPartition.CreateConcurrencyLimiter(NoKey.Instance, _ => concurrencyLimiterOptions); - }, global); + }); + } + + /// + /// Adds a new no-op to the . + /// + /// The to add a limiter to. + /// The name that will be associated with the limiter. + /// This . + public static RateLimiterOptions AddNoLimiter(this RateLimiterOptions options, string policyName) + { + return options.AddPolicy(policyName, context => + { + return RateLimitPartition.CreateNoLimiter(NoKey.Instance); + }); } } diff --git a/src/Middleware/RateLimiting/src/RateLimiterPolicy.cs b/src/Middleware/RateLimiting/src/RateLimiterPolicy.cs index 0364d7a4571b..f39a3e53e461 100644 --- a/src/Middleware/RateLimiting/src/RateLimiterPolicy.cs +++ b/src/Middleware/RateLimiting/src/RateLimiterPolicy.cs @@ -6,7 +6,7 @@ using Microsoft.AspNetCore.Http; namespace Microsoft.AspNetCore.RateLimiting; -internal sealed class RateLimiterPolicy +internal sealed class RateLimiterPolicy : IRateLimiterPolicy { [Required] public Func> Partitioner { get; init; } @@ -15,6 +15,13 @@ internal sealed class RateLimiterPolicy public string PolicyName { get; init; } public PartitionKeyScope KeyScope { get; init; } = PartitionKeyScope.Policy; + + public int CustomRejectionStatusCode => throw new NotImplementedException(); + + public RateLimitPartition GetPartition(HttpContext httpContext) + { + throw new NotImplementedException(); + } } internal enum PartitionKeyScope { From 858e7d5945dbdca20645c32ce6ef2d3c1152b1dd Mon Sep 17 00:00:00 2001 From: Will Godbe Date: Tue, 21 Jun 2022 14:59:25 -0700 Subject: [PATCH 06/35] More fixes --- eng/Versions.props | 6 +++ .../RateLimiting/src/IRateLimiterMetadata.cs | 2 +- .../RateLimiting/src/IRateLimiterPolicy.cs | 6 +-- .../RateLimiting/src/OnRejectedContext.cs | 13 +++++++ ...RateLimiterApplicationBuilderExtensions.cs | 4 +- .../RateLimiting/src/RateLimiterMetadata.cs | 2 +- .../RateLimiting/src/RateLimiterOptions.cs | 37 +++++++++++-------- .../src/RateLimiterOptionsExtensions.cs | 6 +-- .../RateLimiting/src/RateLimiterPolicy.cs | 2 + 9 files changed, 52 insertions(+), 26 deletions(-) create mode 100644 src/Middleware/RateLimiting/src/OnRejectedContext.cs diff --git a/eng/Versions.props b/eng/Versions.props index 7e88ca05dd42..4b59b67b2b0f 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -50,6 +50,8 @@ true false + + true 17.1.0-preview-20211109-03 + + 4.4.0-1.22315.13 - 4.4.0-1.22315.13 + 4.4.0-1.22358.14