From b5e7191989e54c16ec7c2b5959fe9632e96a443c Mon Sep 17 00:00:00 2001 From: Ilya Klimenko Date: Wed, 11 Jan 2023 15:57:52 +0300 Subject: [PATCH 1/7] Added IRateLimiterStatisticsFeatures with default impl and tests --- .../DefaultRateLimiterStatisticsFeature.cs | 28 +++++++ .../Features/IRateLimiterStatisticsFeature.cs | 28 +++++++ .../RateLimiting/src/PublicAPI.Unshipped.txt | 5 ++ .../RateLimiting/src/RateLimiterOptions.cs | 9 +++ .../src/RateLimitingMiddleware.cs | 16 +++- .../test/RateLimitingMiddlewareTests.cs | 75 ++++++++++++++++++- .../test/TestPartitionedRateLimiter.cs | 6 +- .../RateLimiting/test/TestRateLimiter.cs | 6 +- .../test/TestRateLimiterPolicy.cs | 6 +- 9 files changed, 171 insertions(+), 8 deletions(-) create mode 100644 src/Middleware/RateLimiting/src/Features/DefaultRateLimiterStatisticsFeature.cs create mode 100644 src/Middleware/RateLimiting/src/Features/IRateLimiterStatisticsFeature.cs diff --git a/src/Middleware/RateLimiting/src/Features/DefaultRateLimiterStatisticsFeature.cs b/src/Middleware/RateLimiting/src/Features/DefaultRateLimiterStatisticsFeature.cs new file mode 100644 index 000000000000..9a014c252120 --- /dev/null +++ b/src/Middleware/RateLimiting/src/Features/DefaultRateLimiterStatisticsFeature.cs @@ -0,0 +1,28 @@ +// 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.Features; + +internal class DefaultRateLimiterStatisticsFeature : IRateLimiterStatisticsFeature +{ + private readonly HttpContext _httpContext; + private readonly PartitionedRateLimiter? _globalLimiter; + private readonly PartitionedRateLimiter _endpointLimiter; + + public DefaultRateLimiterStatisticsFeature( + PartitionedRateLimiter? globalLimiter, + PartitionedRateLimiter endpointLimiter, + HttpContext httpContext) + { + _globalLimiter = globalLimiter; + _endpointLimiter = endpointLimiter; + _httpContext = httpContext; + } + + public RateLimiterStatistics? GetEndpointStatistics() => _endpointLimiter.GetStatistics(_httpContext); + + public RateLimiterStatistics? GetGlobalStatistics() => _globalLimiter?.GetStatistics(_httpContext); +} diff --git a/src/Middleware/RateLimiting/src/Features/IRateLimiterStatisticsFeature.cs b/src/Middleware/RateLimiting/src/Features/IRateLimiterStatisticsFeature.cs new file mode 100644 index 000000000000..fd2c296565aa --- /dev/null +++ b/src/Middleware/RateLimiting/src/Features/IRateLimiterStatisticsFeature.cs @@ -0,0 +1,28 @@ +// 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.Features; + +/// +/// An Interface which is used to represent statistics methods for global and endpoint limiters. +/// Obtained via . +/// +/// +/// Requires to be true +/// +public interface IRateLimiterStatisticsFeature +{ + /// + /// Method to fetch for global + /// + /// for global . + RateLimiterStatistics? GetGlobalStatistics(); + /// + /// Method to fetch for global + /// + /// for global . + RateLimiterStatistics? GetEndpointStatistics(); +} diff --git a/src/Middleware/RateLimiting/src/PublicAPI.Unshipped.txt b/src/Middleware/RateLimiting/src/PublicAPI.Unshipped.txt index 9e18f7083b21..da4df2547ad7 100644 --- a/src/Middleware/RateLimiting/src/PublicAPI.Unshipped.txt +++ b/src/Middleware/RateLimiting/src/PublicAPI.Unshipped.txt @@ -6,6 +6,9 @@ Microsoft.AspNetCore.RateLimiting.DisableRateLimitingAttribute.DisableRateLimiti Microsoft.AspNetCore.RateLimiting.EnableRateLimitingAttribute Microsoft.AspNetCore.RateLimiting.EnableRateLimitingAttribute.EnableRateLimitingAttribute(string! policyName) -> void Microsoft.AspNetCore.RateLimiting.EnableRateLimitingAttribute.PolicyName.get -> string? +Microsoft.AspNetCore.RateLimiting.Features.IRateLimiterStatisticsFeature +Microsoft.AspNetCore.RateLimiting.Features.IRateLimiterStatisticsFeature.GetEndpointStatistics() -> System.Threading.RateLimiting.RateLimiterStatistics? +Microsoft.AspNetCore.RateLimiting.Features.IRateLimiterStatisticsFeature.GetGlobalStatistics() -> System.Threading.RateLimiting.RateLimiterStatistics? Microsoft.AspNetCore.RateLimiting.IRateLimiterPolicy Microsoft.AspNetCore.RateLimiting.IRateLimiterPolicy.GetPartition(Microsoft.AspNetCore.Http.HttpContext! httpContext) -> System.Threading.RateLimiting.RateLimitPartition Microsoft.AspNetCore.RateLimiting.IRateLimiterPolicy.OnRejected.get -> System.Func? @@ -26,6 +29,8 @@ Microsoft.AspNetCore.RateLimiting.RateLimiterOptions.OnRejected.set -> void Microsoft.AspNetCore.RateLimiting.RateLimiterOptions.RateLimiterOptions() -> void Microsoft.AspNetCore.RateLimiting.RateLimiterOptions.RejectionStatusCode.get -> int Microsoft.AspNetCore.RateLimiting.RateLimiterOptions.RejectionStatusCode.set -> void +Microsoft.AspNetCore.RateLimiting.RateLimiterOptions.TrackStatistics.get -> bool +Microsoft.AspNetCore.RateLimiting.RateLimiterOptions.TrackStatistics.set -> void Microsoft.AspNetCore.RateLimiting.RateLimiterOptionsExtensions static Microsoft.AspNetCore.Builder.RateLimiterApplicationBuilderExtensions.UseRateLimiter(this Microsoft.AspNetCore.Builder.IApplicationBuilder! app) -> Microsoft.AspNetCore.Builder.IApplicationBuilder! static Microsoft.AspNetCore.Builder.RateLimiterApplicationBuilderExtensions.UseRateLimiter(this Microsoft.AspNetCore.Builder.IApplicationBuilder! app, Microsoft.AspNetCore.RateLimiting.RateLimiterOptions! options) -> Microsoft.AspNetCore.Builder.IApplicationBuilder! diff --git a/src/Middleware/RateLimiting/src/RateLimiterOptions.cs b/src/Middleware/RateLimiting/src/RateLimiterOptions.cs index 9872e1ca788a..062bfa68ecc8 100644 --- a/src/Middleware/RateLimiting/src/RateLimiterOptions.cs +++ b/src/Middleware/RateLimiting/src/RateLimiterOptions.cs @@ -4,6 +4,7 @@ using System.Diagnostics.CodeAnalysis; using System.Threading.RateLimiting; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.RateLimiting.Features; using Microsoft.Extensions.DependencyInjection; namespace Microsoft.AspNetCore.RateLimiting; @@ -40,6 +41,14 @@ public sealed class RateLimiterOptions /// public int RejectionStatusCode { get; set; } = StatusCodes.Status503ServiceUnavailable; + /// + /// Gets or sets flag to track or not to tracks global and endpoint + /// + /// + /// If enabled, adds to + /// + public bool TrackStatistics { get; set; } + /// /// Adds a new rate limiting policy with the given /// diff --git a/src/Middleware/RateLimiting/src/RateLimitingMiddleware.cs b/src/Middleware/RateLimiting/src/RateLimitingMiddleware.cs index d743f77feea6..e1029216d405 100644 --- a/src/Middleware/RateLimiting/src/RateLimitingMiddleware.cs +++ b/src/Middleware/RateLimiting/src/RateLimitingMiddleware.cs @@ -3,6 +3,7 @@ using System.Threading.RateLimiting; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.RateLimiting.Features; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -19,6 +20,7 @@ internal sealed partial class RateLimitingMiddleware private readonly PartitionedRateLimiter? _globalLimiter; private readonly PartitionedRateLimiter _endpointLimiter; private readonly int _rejectionStatusCode; + private readonly bool _trackStatistics; private readonly Dictionary _policyMap; private readonly DefaultKeyType _defaultPolicyKey = new DefaultKeyType("__defaultPolicy", new PolicyNameKey { PolicyName = "__defaultPolicyKey" }); @@ -39,6 +41,7 @@ public RateLimitingMiddleware(RequestDelegate next, ILogger(options.Value.PolicyMap); // Activate policies passed to AddPolicy @@ -78,6 +81,12 @@ public Task Invoke(HttpContext context) private async Task InvokeInternal(HttpContext context, EnableRateLimitingAttribute? enableRateLimitingAttribute) { using var leaseContext = await TryAcquireAsync(context); + + if (_trackStatistics) + { + AddRateLimiterStatisticsFeature(context); + } + if (leaseContext.Lease?.IsAcquired == true) { await _next(context); @@ -242,6 +251,11 @@ private PartitionedRateLimiter CreateEndpointLimiter() }, new DefaultKeyTypeEqualityComparer()); } + private void AddRateLimiterStatisticsFeature(HttpContext context) + { + context.Features.Set(new DefaultRateLimiterStatisticsFeature(_globalLimiter, _endpointLimiter, context)); + } + private static partial class RateLimiterLog { [LoggerMessage(1, LogLevel.Debug, "Rate limits exceeded, rejecting this request.", EventName = "RequestRejectedLimitsExceeded")] @@ -253,4 +267,4 @@ private static partial class RateLimiterLog [LoggerMessage(3, LogLevel.Debug, "The request was canceled.", EventName = "RequestCanceled")] internal static partial void RequestCanceled(ILogger logger); } -} \ No newline at end of file +} diff --git a/src/Middleware/RateLimiting/test/RateLimitingMiddlewareTests.cs b/src/Middleware/RateLimiting/test/RateLimitingMiddlewareTests.cs index 24c6ccd93990..5fafc694357b 100644 --- a/src/Middleware/RateLimiting/test/RateLimitingMiddlewareTests.cs +++ b/src/Middleware/RateLimiting/test/RateLimitingMiddlewareTests.cs @@ -4,6 +4,7 @@ using System.Threading.RateLimiting; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.RateLimiting.Features; using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; @@ -606,6 +607,75 @@ public async Task MultipleEndpointPolicies_LastOneWins() Assert.Equal(StatusCodes.Status403Forbidden, context.Response.StatusCode); } + [Fact] + public async Task StatisticsFeature_NotTracked() + { + // Arrange + var options = CreateOptionsAccessor(); + var policy = new TestRateLimiterPolicy("myKey1", 404, false); + + var middleware = CreateTestRateLimitingMiddleware(options); + + var context = new DefaultHttpContext(); + var endpoint = CreateEndpointWithRateLimitPolicy(policy); + context.SetEndpoint(endpoint); + + // Act + await middleware.Invoke(context).DefaultTimeout(); + + // Assert + Assert.Null(context.Features.Get()); + } + + [Fact] + public async Task StatisticsFeature_SuccessfullyTracked() + { + // Arrange + var options = CreateOptionsAccessor(trackStatistics: true); + + var policy = new TestRateLimiterPolicy("myKey1", 404, false); + + var middleware = CreateTestRateLimitingMiddleware(options); + + var context = new DefaultHttpContext(); + var endpoint = CreateEndpointWithRateLimitPolicy(policy); + context.SetEndpoint(endpoint); + + // Act + await middleware.Invoke(context).DefaultTimeout(); + + // Assert + Assert.NotNull(context.Features.Get()); + } + + [Fact] + public async Task StatisticsFeature_GetsStatistics_ForGlobalAndEndpointLimiter() + { + // Arrange + var options = CreateOptionsAccessor(trackStatistics: true); + + var globalStatistics = new RateLimiterStatistics(); + var endpointStatistics = new RateLimiterStatistics(); + + var policy = new TestRateLimiterPolicy("myKey1", 404, true, endpointStatistics); + options.Value.GlobalLimiter = new TestPartitionedRateLimiter(new TestRateLimiter(false), globalStatistics); + + var middleware = CreateTestRateLimitingMiddleware(options); + + var context = new DefaultHttpContext(); + var endpoint = CreateEndpointWithRateLimitPolicy(policy); + context.SetEndpoint(endpoint); + + // Act + await middleware.Invoke(context).DefaultTimeout(); + + // Assert + var statisticsFeature = context.Features.Get(); + + Assert.Equal(endpointStatistics, statisticsFeature.GetEndpointStatistics()); + Assert.Equal(globalStatistics, statisticsFeature.GetGlobalStatistics()); + } + private Endpoint CreateEndpointWithRateLimitPolicy(IRateLimiterPolicy policy) { var endpointBuilder = new TestEndpointBuilder(); @@ -639,5 +709,8 @@ private RateLimitingMiddleware CreateTestRateLimitingMiddleware(IOptions()); - private IOptions CreateOptionsAccessor() => Options.Create(new RateLimiterOptions()); + private IOptions CreateOptionsAccessor(bool trackStatistics = false) => Options.Create(new RateLimiterOptions() + { + TrackStatistics = trackStatistics + }); } diff --git a/src/Middleware/RateLimiting/test/TestPartitionedRateLimiter.cs b/src/Middleware/RateLimiting/test/TestPartitionedRateLimiter.cs index fa19f79779b7..791cd82870a3 100644 --- a/src/Middleware/RateLimiting/test/TestPartitionedRateLimiter.cs +++ b/src/Middleware/RateLimiting/test/TestPartitionedRateLimiter.cs @@ -13,12 +13,14 @@ namespace Microsoft.AspNetCore.RateLimiting; internal class TestPartitionedRateLimiter : PartitionedRateLimiter { private List limiters = new List(); + private RateLimiterStatistics _statistics; public TestPartitionedRateLimiter() { } - public TestPartitionedRateLimiter(RateLimiter limiter) + public TestPartitionedRateLimiter(RateLimiter limiter, RateLimiterStatistics statistics = null) { limiters.Add(limiter); + _statistics = statistics; } public void AddLimiter(RateLimiter limiter) @@ -28,7 +30,7 @@ public void AddLimiter(RateLimiter limiter) public override RateLimiterStatistics GetStatistics(TResource resourceID) { - throw new NotImplementedException(); + return _statistics; } protected override RateLimitLease AttemptAcquireCore(TResource resourceID, int permitCount) diff --git a/src/Middleware/RateLimiting/test/TestRateLimiter.cs b/src/Middleware/RateLimiting/test/TestRateLimiter.cs index 2ec0ace3ae66..0643a0bb0699 100644 --- a/src/Middleware/RateLimiting/test/TestRateLimiter.cs +++ b/src/Middleware/RateLimiting/test/TestRateLimiter.cs @@ -8,17 +8,19 @@ namespace Microsoft.AspNetCore.RateLimiting; internal class TestRateLimiter : RateLimiter { private readonly bool _alwaysAccept; + private RateLimiterStatistics _statistics; - public TestRateLimiter(bool alwaysAccept) + public TestRateLimiter(bool alwaysAccept, RateLimiterStatistics statistics = null) { _alwaysAccept = alwaysAccept; + _statistics = statistics; } public override TimeSpan? IdleDuration => throw new NotImplementedException(); public override RateLimiterStatistics GetStatistics() { - throw new NotImplementedException(); + return _statistics; } protected override RateLimitLease AttemptAcquireCore(int permitCount) diff --git a/src/Middleware/RateLimiting/test/TestRateLimiterPolicy.cs b/src/Middleware/RateLimiting/test/TestRateLimiterPolicy.cs index 49a7cd7845a7..ce80b044b25f 100644 --- a/src/Middleware/RateLimiting/test/TestRateLimiterPolicy.cs +++ b/src/Middleware/RateLimiting/test/TestRateLimiterPolicy.cs @@ -10,11 +10,13 @@ internal class TestRateLimiterPolicy : IRateLimiterPolicy private readonly string _key; private readonly bool _alwaysAccept; private readonly Func _onRejected; + private readonly RateLimiterStatistics _statistics; - public TestRateLimiterPolicy(string key, int statusCode, bool alwaysAccept) + public TestRateLimiterPolicy(string key, int statusCode, bool alwaysAccept, RateLimiterStatistics statistics = null) { _key = key; _alwaysAccept = alwaysAccept; + _statistics = statistics; _onRejected = (context, token) => { @@ -29,7 +31,7 @@ public RateLimitPartition GetPartition(HttpContext httpContext) { return RateLimitPartition.Get(_key, (key => { - return new TestRateLimiter(_alwaysAccept); + return new TestRateLimiter(_alwaysAccept, _statistics); })); } } From 34b5fd8875a65d7c78d5c0c77279549b675b5e0b Mon Sep 17 00:00:00 2001 From: Ilya Klimenko Date: Fri, 13 Jan 2023 12:06:29 +0300 Subject: [PATCH 2/7] Review fixes: namespace removed, docs improved, allocated once --- .../DefaultRateLimiterStatisticsFeature.cs | 15 +++++++-------- .../IRateLimiterStatisticsFeature.cs | 13 +++++++------ .../RateLimiting/src/RateLimiterOptions.cs | 5 ++--- .../RateLimiting/src/RateLimitingMiddleware.cs | 9 +++++++-- .../test/RateLimitingMiddlewareTests.cs | 1 - 5 files changed, 23 insertions(+), 20 deletions(-) rename src/Middleware/RateLimiting/src/{Features => }/DefaultRateLimiterStatisticsFeature.cs (63%) rename src/Middleware/RateLimiting/src/{Features => }/IRateLimiterStatisticsFeature.cs (68%) diff --git a/src/Middleware/RateLimiting/src/Features/DefaultRateLimiterStatisticsFeature.cs b/src/Middleware/RateLimiting/src/DefaultRateLimiterStatisticsFeature.cs similarity index 63% rename from src/Middleware/RateLimiting/src/Features/DefaultRateLimiterStatisticsFeature.cs rename to src/Middleware/RateLimiting/src/DefaultRateLimiterStatisticsFeature.cs index 9a014c252120..c34f1eeaef0b 100644 --- a/src/Middleware/RateLimiting/src/Features/DefaultRateLimiterStatisticsFeature.cs +++ b/src/Middleware/RateLimiting/src/DefaultRateLimiterStatisticsFeature.cs @@ -4,25 +4,24 @@ using System.Threading.RateLimiting; using Microsoft.AspNetCore.Http; -namespace Microsoft.AspNetCore.RateLimiting.Features; +namespace Microsoft.AspNetCore.RateLimiting; -internal class DefaultRateLimiterStatisticsFeature : IRateLimiterStatisticsFeature +internal sealed class DefaultRateLimiterStatisticsFeature : IRateLimiterStatisticsFeature { - private readonly HttpContext _httpContext; private readonly PartitionedRateLimiter? _globalLimiter; private readonly PartitionedRateLimiter _endpointLimiter; + internal HttpContext? HttpContext { private get; set; } + public DefaultRateLimiterStatisticsFeature( PartitionedRateLimiter? globalLimiter, - PartitionedRateLimiter endpointLimiter, - HttpContext httpContext) + PartitionedRateLimiter endpointLimiter) { _globalLimiter = globalLimiter; _endpointLimiter = endpointLimiter; - _httpContext = httpContext; } - public RateLimiterStatistics? GetEndpointStatistics() => _endpointLimiter.GetStatistics(_httpContext); + public RateLimiterStatistics? GetEndpointStatistics() => _endpointLimiter.GetStatistics(HttpContext); - public RateLimiterStatistics? GetGlobalStatistics() => _globalLimiter?.GetStatistics(_httpContext); + public RateLimiterStatistics? GetGlobalStatistics() => _globalLimiter?.GetStatistics(HttpContext); } diff --git a/src/Middleware/RateLimiting/src/Features/IRateLimiterStatisticsFeature.cs b/src/Middleware/RateLimiting/src/IRateLimiterStatisticsFeature.cs similarity index 68% rename from src/Middleware/RateLimiting/src/Features/IRateLimiterStatisticsFeature.cs rename to src/Middleware/RateLimiting/src/IRateLimiterStatisticsFeature.cs index fd2c296565aa..c79753554ecd 100644 --- a/src/Middleware/RateLimiting/src/Features/IRateLimiterStatisticsFeature.cs +++ b/src/Middleware/RateLimiting/src/IRateLimiterStatisticsFeature.cs @@ -4,25 +4,26 @@ using System.Threading.RateLimiting; using Microsoft.AspNetCore.Http; -namespace Microsoft.AspNetCore.RateLimiting.Features; +namespace Microsoft.AspNetCore.RateLimiting; /// /// An Interface which is used to represent statistics methods for global and endpoint limiters. /// Obtained via . /// /// -/// Requires to be true +/// Requires to be true. /// public interface IRateLimiterStatisticsFeature { /// - /// Method to fetch for global + /// Method to fetch for the global /// - /// for global . + /// for the global . RateLimiterStatistics? GetGlobalStatistics(); /// - /// Method to fetch for global + /// Method to fetch for the endpoints /// - /// for global . + /// for the endpoints . RateLimiterStatistics? GetEndpointStatistics(); + } diff --git a/src/Middleware/RateLimiting/src/RateLimiterOptions.cs b/src/Middleware/RateLimiting/src/RateLimiterOptions.cs index 062bfa68ecc8..8aa1b75ca84c 100644 --- a/src/Middleware/RateLimiting/src/RateLimiterOptions.cs +++ b/src/Middleware/RateLimiting/src/RateLimiterOptions.cs @@ -4,7 +4,6 @@ using System.Diagnostics.CodeAnalysis; using System.Threading.RateLimiting; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.RateLimiting.Features; using Microsoft.Extensions.DependencyInjection; namespace Microsoft.AspNetCore.RateLimiting; @@ -42,10 +41,10 @@ public sealed class RateLimiterOptions public int RejectionStatusCode { get; set; } = StatusCodes.Status503ServiceUnavailable; /// - /// Gets or sets flag to track or not to tracks global and endpoint + /// Gets or sets whether to track global and endpoint . /// /// - /// If enabled, adds to + /// If enabled, adds to . /// public bool TrackStatistics { get; set; } diff --git a/src/Middleware/RateLimiting/src/RateLimitingMiddleware.cs b/src/Middleware/RateLimiting/src/RateLimitingMiddleware.cs index e1029216d405..b842e7f92ad5 100644 --- a/src/Middleware/RateLimiting/src/RateLimitingMiddleware.cs +++ b/src/Middleware/RateLimiting/src/RateLimitingMiddleware.cs @@ -3,7 +3,6 @@ using System.Threading.RateLimiting; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.RateLimiting.Features; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -21,6 +20,7 @@ internal sealed partial class RateLimitingMiddleware private readonly PartitionedRateLimiter _endpointLimiter; private readonly int _rejectionStatusCode; private readonly bool _trackStatistics; + private readonly DefaultRateLimiterStatisticsFeature? _statisticsFeature; private readonly Dictionary _policyMap; private readonly DefaultKeyType _defaultPolicyKey = new DefaultKeyType("__defaultPolicy", new PolicyNameKey { PolicyName = "__defaultPolicyKey" }); @@ -53,6 +53,10 @@ public RateLimitingMiddleware(RequestDelegate next, ILogger CreateEndpointLimiter() private void AddRateLimiterStatisticsFeature(HttpContext context) { - context.Features.Set(new DefaultRateLimiterStatisticsFeature(_globalLimiter, _endpointLimiter, context)); + _statisticsFeature.HttpContext = context; + context.Features.Set(_statisticsFeature); } private static partial class RateLimiterLog diff --git a/src/Middleware/RateLimiting/test/RateLimitingMiddlewareTests.cs b/src/Middleware/RateLimiting/test/RateLimitingMiddlewareTests.cs index 5fafc694357b..0cf985fdbd4f 100644 --- a/src/Middleware/RateLimiting/test/RateLimitingMiddlewareTests.cs +++ b/src/Middleware/RateLimiting/test/RateLimitingMiddlewareTests.cs @@ -4,7 +4,6 @@ using System.Threading.RateLimiting; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.RateLimiting.Features; using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; From 687b94f9ab9782a32e6a7e8024a81ed0ed63300c Mon Sep 17 00:00:00 2001 From: Ilya Klimenko Date: Fri, 13 Jan 2023 12:22:04 +0300 Subject: [PATCH 3/7] Fix public API + nre dereference --- .../src/DefaultRateLimiterStatisticsFeature.cs | 11 ++++++++--- .../RateLimiting/src/PublicAPI.Unshipped.txt | 6 +++--- .../RateLimiting/src/RateLimitingMiddleware.cs | 2 +- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/Middleware/RateLimiting/src/DefaultRateLimiterStatisticsFeature.cs b/src/Middleware/RateLimiting/src/DefaultRateLimiterStatisticsFeature.cs index c34f1eeaef0b..97972081ff09 100644 --- a/src/Middleware/RateLimiting/src/DefaultRateLimiterStatisticsFeature.cs +++ b/src/Middleware/RateLimiting/src/DefaultRateLimiterStatisticsFeature.cs @@ -11,7 +11,7 @@ internal sealed class DefaultRateLimiterStatisticsFeature : IRateLimiterStatisti private readonly PartitionedRateLimiter? _globalLimiter; private readonly PartitionedRateLimiter _endpointLimiter; - internal HttpContext? HttpContext { private get; set; } + private HttpContext? _httpContext; public DefaultRateLimiterStatisticsFeature( PartitionedRateLimiter? globalLimiter, @@ -21,7 +21,12 @@ public DefaultRateLimiterStatisticsFeature( _endpointLimiter = endpointLimiter; } - public RateLimiterStatistics? GetEndpointStatistics() => _endpointLimiter.GetStatistics(HttpContext); + public void SetHttpContext(HttpContext context) + { + _httpContext = context; + } + + public RateLimiterStatistics? GetEndpointStatistics() => _endpointLimiter.GetStatistics(_httpContext); - public RateLimiterStatistics? GetGlobalStatistics() => _globalLimiter?.GetStatistics(HttpContext); + public RateLimiterStatistics? GetGlobalStatistics() => _globalLimiter?.GetStatistics(_httpContext); } diff --git a/src/Middleware/RateLimiting/src/PublicAPI.Unshipped.txt b/src/Middleware/RateLimiting/src/PublicAPI.Unshipped.txt index da4df2547ad7..0c8606a1ef45 100644 --- a/src/Middleware/RateLimiting/src/PublicAPI.Unshipped.txt +++ b/src/Middleware/RateLimiting/src/PublicAPI.Unshipped.txt @@ -6,12 +6,12 @@ Microsoft.AspNetCore.RateLimiting.DisableRateLimitingAttribute.DisableRateLimiti Microsoft.AspNetCore.RateLimiting.EnableRateLimitingAttribute Microsoft.AspNetCore.RateLimiting.EnableRateLimitingAttribute.EnableRateLimitingAttribute(string! policyName) -> void Microsoft.AspNetCore.RateLimiting.EnableRateLimitingAttribute.PolicyName.get -> string? -Microsoft.AspNetCore.RateLimiting.Features.IRateLimiterStatisticsFeature -Microsoft.AspNetCore.RateLimiting.Features.IRateLimiterStatisticsFeature.GetEndpointStatistics() -> System.Threading.RateLimiting.RateLimiterStatistics? -Microsoft.AspNetCore.RateLimiting.Features.IRateLimiterStatisticsFeature.GetGlobalStatistics() -> System.Threading.RateLimiting.RateLimiterStatistics? Microsoft.AspNetCore.RateLimiting.IRateLimiterPolicy Microsoft.AspNetCore.RateLimiting.IRateLimiterPolicy.GetPartition(Microsoft.AspNetCore.Http.HttpContext! httpContext) -> System.Threading.RateLimiting.RateLimitPartition Microsoft.AspNetCore.RateLimiting.IRateLimiterPolicy.OnRejected.get -> System.Func? +Microsoft.AspNetCore.RateLimiting.IRateLimiterStatisticsFeature +Microsoft.AspNetCore.RateLimiting.IRateLimiterStatisticsFeature.GetEndpointStatistics() -> System.Threading.RateLimiting.RateLimiterStatistics? +Microsoft.AspNetCore.RateLimiting.IRateLimiterStatisticsFeature.GetGlobalStatistics() -> System.Threading.RateLimiting.RateLimiterStatistics? Microsoft.AspNetCore.RateLimiting.OnRejectedContext Microsoft.AspNetCore.RateLimiting.OnRejectedContext.HttpContext.get -> Microsoft.AspNetCore.Http.HttpContext! Microsoft.AspNetCore.RateLimiting.OnRejectedContext.HttpContext.init -> void diff --git a/src/Middleware/RateLimiting/src/RateLimitingMiddleware.cs b/src/Middleware/RateLimiting/src/RateLimitingMiddleware.cs index b842e7f92ad5..9ead4ed4d093 100644 --- a/src/Middleware/RateLimiting/src/RateLimitingMiddleware.cs +++ b/src/Middleware/RateLimiting/src/RateLimitingMiddleware.cs @@ -257,7 +257,7 @@ private PartitionedRateLimiter CreateEndpointLimiter() private void AddRateLimiterStatisticsFeature(HttpContext context) { - _statisticsFeature.HttpContext = context; + _statisticsFeature?.SetHttpContext(context); context.Features.Set(_statisticsFeature); } From edeb9a5e3bdc4efe449d3605cbad443f07e59812 Mon Sep 17 00:00:00 2001 From: Ilya Klimenko Date: Fri, 13 Jan 2023 12:58:31 +0300 Subject: [PATCH 4/7] fixed nre error --- .../RateLimiting/src/DefaultRateLimiterStatisticsFeature.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Middleware/RateLimiting/src/DefaultRateLimiterStatisticsFeature.cs b/src/Middleware/RateLimiting/src/DefaultRateLimiterStatisticsFeature.cs index 97972081ff09..9a501ee9f8d1 100644 --- a/src/Middleware/RateLimiting/src/DefaultRateLimiterStatisticsFeature.cs +++ b/src/Middleware/RateLimiting/src/DefaultRateLimiterStatisticsFeature.cs @@ -26,7 +26,7 @@ public void SetHttpContext(HttpContext context) _httpContext = context; } - public RateLimiterStatistics? GetEndpointStatistics() => _endpointLimiter.GetStatistics(_httpContext); + public RateLimiterStatistics? GetEndpointStatistics() => _endpointLimiter.GetStatistics(_httpContext!); - public RateLimiterStatistics? GetGlobalStatistics() => _globalLimiter?.GetStatistics(_httpContext); + public RateLimiterStatistics? GetGlobalStatistics() => _globalLimiter?.GetStatistics(_httpContext!); } From 0ff662410f58daebac9ea92547894dde9225c66a Mon Sep 17 00:00:00 2001 From: Ilya Klimenko Date: Fri, 13 Jan 2023 20:17:46 +0300 Subject: [PATCH 5/7] Changed to allocating on every http request --- .../src/DefaultRateLimiterStatisticsFeature.cs | 14 +++++--------- .../RateLimiting/src/RateLimitingMiddleware.cs | 9 +-------- 2 files changed, 6 insertions(+), 17 deletions(-) diff --git a/src/Middleware/RateLimiting/src/DefaultRateLimiterStatisticsFeature.cs b/src/Middleware/RateLimiting/src/DefaultRateLimiterStatisticsFeature.cs index 9a501ee9f8d1..9902ac8d73ba 100644 --- a/src/Middleware/RateLimiting/src/DefaultRateLimiterStatisticsFeature.cs +++ b/src/Middleware/RateLimiting/src/DefaultRateLimiterStatisticsFeature.cs @@ -10,23 +10,19 @@ internal sealed class DefaultRateLimiterStatisticsFeature : IRateLimiterStatisti { private readonly PartitionedRateLimiter? _globalLimiter; private readonly PartitionedRateLimiter _endpointLimiter; - - private HttpContext? _httpContext; + private readonly HttpContext _httpContext; public DefaultRateLimiterStatisticsFeature( PartitionedRateLimiter? globalLimiter, - PartitionedRateLimiter endpointLimiter) + PartitionedRateLimiter endpointLimiter, + HttpContext context) { _globalLimiter = globalLimiter; _endpointLimiter = endpointLimiter; - } - - public void SetHttpContext(HttpContext context) - { _httpContext = context; } - public RateLimiterStatistics? GetEndpointStatistics() => _endpointLimiter.GetStatistics(_httpContext!); + public RateLimiterStatistics? GetEndpointStatistics() => _endpointLimiter.GetStatistics(_httpContext); - public RateLimiterStatistics? GetGlobalStatistics() => _globalLimiter?.GetStatistics(_httpContext!); + public RateLimiterStatistics? GetGlobalStatistics() => _globalLimiter?.GetStatistics(_httpContext); } diff --git a/src/Middleware/RateLimiting/src/RateLimitingMiddleware.cs b/src/Middleware/RateLimiting/src/RateLimitingMiddleware.cs index 9ead4ed4d093..b32eee04c289 100644 --- a/src/Middleware/RateLimiting/src/RateLimitingMiddleware.cs +++ b/src/Middleware/RateLimiting/src/RateLimitingMiddleware.cs @@ -20,7 +20,6 @@ internal sealed partial class RateLimitingMiddleware private readonly PartitionedRateLimiter _endpointLimiter; private readonly int _rejectionStatusCode; private readonly bool _trackStatistics; - private readonly DefaultRateLimiterStatisticsFeature? _statisticsFeature; private readonly Dictionary _policyMap; private readonly DefaultKeyType _defaultPolicyKey = new DefaultKeyType("__defaultPolicy", new PolicyNameKey { PolicyName = "__defaultPolicyKey" }); @@ -52,11 +51,6 @@ public RateLimitingMiddleware(RequestDelegate next, ILogger CreateEndpointLimiter() private void AddRateLimiterStatisticsFeature(HttpContext context) { - _statisticsFeature?.SetHttpContext(context); - context.Features.Set(_statisticsFeature); + context.Features.Set(new DefaultRateLimiterStatisticsFeature(_globalLimiter, _endpointLimiter, context)); } private static partial class RateLimiterLog From 6836d806dec824de96f4668bbd4064f93793ab8c Mon Sep 17 00:00:00 2001 From: Ilya Klimenko Date: Sun, 15 Jan 2023 16:11:46 +0300 Subject: [PATCH 6/7] Fix merge request --- .../RateLimiting/src/PublicAPI.Unshipped.txt | 39 ------------------- 1 file changed, 39 deletions(-) diff --git a/src/Middleware/RateLimiting/src/PublicAPI.Unshipped.txt b/src/Middleware/RateLimiting/src/PublicAPI.Unshipped.txt index 1901c867c9da..82c8b7ec4d30 100644 --- a/src/Middleware/RateLimiting/src/PublicAPI.Unshipped.txt +++ b/src/Middleware/RateLimiting/src/PublicAPI.Unshipped.txt @@ -1,45 +1,6 @@ -Microsoft.AspNetCore.Builder.RateLimiterApplicationBuilderExtensions -Microsoft.AspNetCore.Builder.RateLimiterEndpointConventionBuilderExtensions -Microsoft.AspNetCore.Builder.RateLimiterServiceCollectionExtensions -Microsoft.AspNetCore.RateLimiting.DisableRateLimitingAttribute -Microsoft.AspNetCore.RateLimiting.DisableRateLimitingAttribute.DisableRateLimitingAttribute() -> void -Microsoft.AspNetCore.RateLimiting.EnableRateLimitingAttribute -Microsoft.AspNetCore.RateLimiting.EnableRateLimitingAttribute.EnableRateLimitingAttribute(string! policyName) -> void -Microsoft.AspNetCore.RateLimiting.EnableRateLimitingAttribute.PolicyName.get -> string? -Microsoft.AspNetCore.RateLimiting.IRateLimiterPolicy -Microsoft.AspNetCore.RateLimiting.IRateLimiterPolicy.GetPartition(Microsoft.AspNetCore.Http.HttpContext! httpContext) -> System.Threading.RateLimiting.RateLimitPartition -Microsoft.AspNetCore.RateLimiting.IRateLimiterPolicy.OnRejected.get -> System.Func? Microsoft.AspNetCore.RateLimiting.IRateLimiterStatisticsFeature Microsoft.AspNetCore.RateLimiting.IRateLimiterStatisticsFeature.GetEndpointStatistics() -> System.Threading.RateLimiting.RateLimiterStatistics? Microsoft.AspNetCore.RateLimiting.IRateLimiterStatisticsFeature.GetGlobalStatistics() -> System.Threading.RateLimiting.RateLimiterStatistics? -Microsoft.AspNetCore.RateLimiting.OnRejectedContext -Microsoft.AspNetCore.RateLimiting.OnRejectedContext.HttpContext.get -> Microsoft.AspNetCore.Http.HttpContext! -Microsoft.AspNetCore.RateLimiting.OnRejectedContext.HttpContext.init -> void -Microsoft.AspNetCore.RateLimiting.OnRejectedContext.Lease.get -> System.Threading.RateLimiting.RateLimitLease! -Microsoft.AspNetCore.RateLimiting.OnRejectedContext.Lease.init -> void -Microsoft.AspNetCore.RateLimiting.OnRejectedContext.OnRejectedContext() -> void -Microsoft.AspNetCore.RateLimiting.RateLimiterOptions -Microsoft.AspNetCore.RateLimiting.RateLimiterOptions.AddPolicy(string! policyName) -> Microsoft.AspNetCore.RateLimiting.RateLimiterOptions! -Microsoft.AspNetCore.RateLimiting.RateLimiterOptions.AddPolicy(string! policyName, Microsoft.AspNetCore.RateLimiting.IRateLimiterPolicy! policy) -> Microsoft.AspNetCore.RateLimiting.RateLimiterOptions! -Microsoft.AspNetCore.RateLimiting.RateLimiterOptions.AddPolicy(string! policyName, System.Func>! partitioner) -> Microsoft.AspNetCore.RateLimiting.RateLimiterOptions! -Microsoft.AspNetCore.RateLimiting.RateLimiterOptions.GlobalLimiter.get -> System.Threading.RateLimiting.PartitionedRateLimiter? -Microsoft.AspNetCore.RateLimiting.RateLimiterOptions.GlobalLimiter.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.RateLimiterOptions.RejectionStatusCode.get -> int -Microsoft.AspNetCore.RateLimiting.RateLimiterOptions.RejectionStatusCode.set -> void Microsoft.AspNetCore.RateLimiting.RateLimiterOptions.TrackStatistics.get -> bool Microsoft.AspNetCore.RateLimiting.RateLimiterOptions.TrackStatistics.set -> void -Microsoft.AspNetCore.RateLimiting.RateLimiterOptionsExtensions -static Microsoft.AspNetCore.Builder.RateLimiterApplicationBuilderExtensions.UseRateLimiter(this Microsoft.AspNetCore.Builder.IApplicationBuilder! app) -> Microsoft.AspNetCore.Builder.IApplicationBuilder! -static Microsoft.AspNetCore.Builder.RateLimiterApplicationBuilderExtensions.UseRateLimiter(this Microsoft.AspNetCore.Builder.IApplicationBuilder! app, Microsoft.AspNetCore.RateLimiting.RateLimiterOptions! options) -> Microsoft.AspNetCore.Builder.IApplicationBuilder! -static Microsoft.AspNetCore.Builder.RateLimiterEndpointConventionBuilderExtensions.DisableRateLimiting(this TBuilder builder) -> TBuilder -static Microsoft.AspNetCore.Builder.RateLimiterEndpointConventionBuilderExtensions.RequireRateLimiting(this TBuilder builder, Microsoft.AspNetCore.RateLimiting.IRateLimiterPolicy! policy) -> TBuilder -static Microsoft.AspNetCore.Builder.RateLimiterEndpointConventionBuilderExtensions.RequireRateLimiting(this TBuilder builder, string! policyName) -> TBuilder -static Microsoft.AspNetCore.Builder.RateLimiterServiceCollectionExtensions.AddRateLimiter(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services, System.Action! configureOptions) -> Microsoft.Extensions.DependencyInjection.IServiceCollection! -static Microsoft.AspNetCore.RateLimiting.RateLimiterOptionsExtensions.AddConcurrencyLimiter(this Microsoft.AspNetCore.RateLimiting.RateLimiterOptions! options, string! policyName, System.Action! configureOptions) -> Microsoft.AspNetCore.RateLimiting.RateLimiterOptions! -static Microsoft.AspNetCore.RateLimiting.RateLimiterOptionsExtensions.AddFixedWindowLimiter(this Microsoft.AspNetCore.RateLimiting.RateLimiterOptions! options, string! policyName, System.Action! configureOptions) -> Microsoft.AspNetCore.RateLimiting.RateLimiterOptions! -static Microsoft.AspNetCore.RateLimiting.RateLimiterOptionsExtensions.AddSlidingWindowLimiter(this Microsoft.AspNetCore.RateLimiting.RateLimiterOptions! options, string! policyName, System.Action! configureOptions) -> Microsoft.AspNetCore.RateLimiting.RateLimiterOptions! -static Microsoft.AspNetCore.RateLimiting.RateLimiterOptionsExtensions.AddTokenBucketLimiter(this Microsoft.AspNetCore.RateLimiting.RateLimiterOptions! options, string! policyName, System.Action! configureOptions) -> Microsoft.AspNetCore.RateLimiting.RateLimiterOptions! #nullable enable From 896aabc4e6ed848edcb4f7f01dada6356daaf8fe Mon Sep 17 00:00:00 2001 From: Ilya Klimenko Date: Sun, 15 Jan 2023 16:29:22 +0300 Subject: [PATCH 7/7] fix nullable enable issue --- src/Middleware/RateLimiting/src/PublicAPI.Unshipped.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Middleware/RateLimiting/src/PublicAPI.Unshipped.txt b/src/Middleware/RateLimiting/src/PublicAPI.Unshipped.txt index 82c8b7ec4d30..63f0e01cea3f 100644 --- a/src/Middleware/RateLimiting/src/PublicAPI.Unshipped.txt +++ b/src/Middleware/RateLimiting/src/PublicAPI.Unshipped.txt @@ -1,6 +1,6 @@ +#nullable enable Microsoft.AspNetCore.RateLimiting.IRateLimiterStatisticsFeature Microsoft.AspNetCore.RateLimiting.IRateLimiterStatisticsFeature.GetEndpointStatistics() -> System.Threading.RateLimiting.RateLimiterStatistics? Microsoft.AspNetCore.RateLimiting.IRateLimiterStatisticsFeature.GetGlobalStatistics() -> System.Threading.RateLimiting.RateLimiterStatistics? Microsoft.AspNetCore.RateLimiting.RateLimiterOptions.TrackStatistics.get -> bool Microsoft.AspNetCore.RateLimiting.RateLimiterOptions.TrackStatistics.set -> void -#nullable enable