Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make RateLimitingMiddleware endpoint aware #42417

Merged
merged 40 commits into from
Jul 12, 2022
Merged

Make RateLimitingMiddleware endpoint aware #42417

merged 40 commits into from
Jul 12, 2022

Conversation

wtgodbe
Copy link
Member

@wtgodbe wtgodbe commented Jun 24, 2022

#41667

Follows the CORS pattern of adding metadata to endpoints w/ an IEndpointConventionBuilder. Users provide policies, which are a Func<HttpContext, RateLimitPartition<TKey>>, where a RateLimitPartition is a func for creating a RateLimiter (used by the runtime to create PartitionedRateLimiters); and a OnRejected function. We provide convenience methods for the built-in RateLimiters in runtime (AddConcurrencyLimiter, etc), which don't support custom OnRejected funcs. These policies will always apply to endpoints - the user can also set a GlobalLimiter on the option which will be applied to every request (and will be chained w/ the endpoint specific policies - global first).

Still adding tests & samples, but wanted to open this now to get eyes on it.

@@ -50,6 +50,8 @@
<UsingToolNetFrameworkReferenceAssemblies Condition="'$(OS)' != 'Windows_NT'">true</UsingToolNetFrameworkReferenceAssemblies>
<!-- Disable XLIFF tasks -->
<UsingToolXliff>false</UsingToolXliff>
<!-- Use custom version of Roslyn Compiler -->
<UsingToolMicrosoftNetCompilers>true</UsingToolMicrosoftNetCompilers>
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is to allow for the required keyword I use in many places in this PR

@wtgodbe
Copy link
Member Author

wtgodbe commented Jun 29, 2022

Yeah, I think so. I'll do that

Actually, never mind - we reference System.Threading.Ratelimiting which isn't in Microsoft.Netcore.App, so we'd have to include it in our shared framework in order to also include Microsoft.Aspnetcore.RateLimitingMiddleware. So might be better just to leave it as a package

var helloName = "helloPolicy";

// Define endpoint limiters and a global limiter.
var options = new RateLimiterOptions()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why isn't this using services?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the sample to inject an ILogger - anything else you'd like to see?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was asking why the options was being newed up instead of a call to AddRateLimiting on builder.Services.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We removed that extension in the last API review: #37384 (comment), in favor of just the UseRateLimiter(options) extension on IApplicationBuilder. We could bring it back in the next iteration

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK that doesn't look ideal at all. It doesn't look like any of the other middleware that is configured with this level of policy.

@Tratcher @halter73 @BrennanConroy What was the thinking here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need a do over here to align with the patterns we have in the rest of the stack. The newly authored output caching middleware also follows this pattern

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are a few other places we do it this way:

public static IApplicationBuilder UseStaticFiles(this IApplicationBuilder app, StaticFileOptions options)

public static IApplicationBuilder UseHttpMethodOverride(this IApplicationBuilder builder, HttpMethodOverrideOptions options)

public static IApplicationBuilder UseWebSockets(this IApplicationBuilder app, WebSocketOptions options)

But I think long term it's probably better to switch to UseRateLimiter on the ServiceCollection w/ an Action. Given that we already shipped this version of the API for preview6, I'd like to get this PR in as-is (modulo the other minor feedback) before the preview7 snap tomorrow, then go through another API review to switch to the action-based approach (as well as a couple other small changes) to be merged later this week

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

@halter73 halter73 Jul 11, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@davidfowl You're late to the party! You should read my thoughts on this at #41655 (comment) when @Elfocrash made the same point if you haven't yet. We're way more consistent with this options pattern (or should I say lack of options pattern) for middleware with no services than I would have thought. We're rarely this consistent with anything else 🤣.

But I think long term it's probably better to switch to UseRateLimiter on the ServiceCollection w/ an Action. Given that we already shipped this version of the API for preview6, I'd like to get this PR in as-is (modulo the other minor feedback) before the preview7 snap tomorrow, then go through another API review to switch to the action-based approach (as well as a couple other small changes) to be merged later this week

Completely agree.

I do understand no matter how consistent we are with not using the options pattern in this specific scenario, it's rare to use middleware without any default services but with interesting options must people will want to configure. And this does feel awkward when you're more used to configuring options for middleware that consumes services.

Is it likely that UseRateLimiter will need to register default services for .NET 7? If so, configuring options when adding the services like you do for most things would avoid this controversy entirely!

Copy link
Member

@davidfowl davidfowl Jul 12, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

None of those options are this complex. There's no policy, no methods, just properties on pocos.

/// <summary>
/// An interface which can be used to identify a type which provides metadata needed for enabling request rate limiting support.
/// </summary>
internal interface IRateLimiterMetadata
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We talked about this during preview6 API discussions and decided to leave it out for preview6 and talk about making it public in preview7.

src/Middleware/RateLimiting/src/RateLimiterMetadata.cs Outdated Show resolved Hide resolved
src/Middleware/RateLimiting/src/RateLimiterOptions.cs Outdated Show resolved Hide resolved
src/Middleware/RateLimiting/src/RateLimiterOptions.cs Outdated Show resolved Hide resolved
src/Middleware/RateLimiting/src/RateLimiterOptions.cs Outdated Show resolved Hide resolved
src/Middleware/RateLimiting/src/PolicyTypeInfo.cs Outdated Show resolved Hide resolved
src/Middleware/RateLimiting/src/LeaseContext.cs Outdated Show resolved Hide resolved
Comment on lines 6 to 7
namespace Microsoft.AspNetCore.RateLimiting;
internal struct LeaseContext : IDisposable
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know enough about the subject but does making this struct to readonly ref improve anything!?

src/Middleware/RateLimiting/src/PolicyNameKey.cs Outdated Show resolved Hide resolved
@wtgodbe
Copy link
Member Author

wtgodbe commented Jul 11, 2022

Looks like the updated compiler doesn't like the scoped keyword - do I need to undo this change @halter73 @Tratcher @javiercn? https://github.com/dotnet/aspnetcore/pull/42571/files#diff-8903dde51f55a90fde6324b9c96928893021a4539db781bb8ee212812cd4f3ffR316

##[error]src\Servers\Kestrel\Core\src\Internal\Http\HttpParser.cs(316,29): error CS1001: (NETCORE_ENGINEERING_TELEMETRY=Build) Identifier expected
D:\a_work\1\s\src\Servers\Kestrel\Core\src\Internal\Http\HttpParser.cs(316,29): error CS1003: Syntax error, '>' expected [D:\a_work\1\s\src\Servers\Kestrel\Core\src\Microsoft.AspNetCore.Server.Kestrel.Core.csproj]
##[error]src\Servers\Kestrel\Core\src\Internal\Http\HttpParser.cs(316,29): error CS1003: (NETCORE_ENGINEERING_TELEMETRY=Build) Syntax error, '>' expected
D:\a_work\1\s\src\Servers\Kestrel\Core\src\Internal\Http\HttpParser.cs(316,29): error CS1003: Syntax error, '(' expected [D:\a_work\1\s\src\Servers\Kestrel\Core\src\Microsoft.AspNetCore.Server.Kestrel.Core.csproj]
##[error]src\Servers\Kestrel\Core\src\Internal\Http\HttpParser.cs(316,29): error CS1003: (NETCORE_ENGINEERING_TELEMETRY=Build) Syntax error, '(' expected
D:\a_work\1\s\src\Servers\Kestrel\Core\src\Internal\Http\HttpParser.cs(316,33): error CS1001: Identifier expected [D:\a_work\1\s\src\Servers\Kestrel\Core\src\Microsoft.AspNetCore.Server.Kestrel.Core.csproj]
##[error]src\Servers\Kestrel\Core\src\Internal\Http\HttpParser.cs(316,33): error CS1001: (NETCORE_ENGINEERING_TELEMETRY=Build) Identifier expected
D:\a_work\1\s\src\Servers\Kestrel\Core\src\Internal\Http\HttpParser.cs(316,33): error CS1026: ) expected [D:\a_work\1\s\src\Servers\Kestrel\Core\src\Microsoft.AspNetCore.Server.Kestrel.Core.csproj]
##[error]src\Servers\Kestrel\Core\src\Internal\Http\HttpParser.cs(316,33): error CS1026: (NETCORE_ENGINEERING_TELEMETRY=Build) ) expected
D:\a_work\1\s\src\Servers\Kestrel\Core\src\Internal\Http\HttpParser.cs(316,33): error CS1002: ; expected [D:\a_work\1\s\src\Servers\Kestrel\Core\src\Microsoft.AspNetCore.Server.Kestrel.Core.csproj]
##[error]src\Servers\Kestrel\Core\src\Internal\Http\HttpParser.cs(316,33): error CS1002: (NETCORE_ENGINEERING_TELEMETRY=Build) ; expected
D:\a_work\1\s\src\Servers\Kestrel\Core\src\Internal\Http\HttpParser.cs(316,33): error CS1525: Invalid expression term '>' [D:\a_work\1\s\src\Servers\Kestrel\Core\src\Microsoft.AspNetCore.Server.Kestrel.Core.csproj]
##[error]src\Servers\Kestrel\Core\src\Internal\Http\HttpParser.cs(316,33): error CS1525: (NETCORE_ENGINEERING_TELEMETRY=Build) Invalid expression term '>'

eng/Versions.props Show resolved Hide resolved
var helloName = "helloPolicy";

// Define endpoint limiters and a global limiter.
var options = new RateLimiterOptions()
Copy link
Member

@halter73 halter73 Jul 11, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@davidfowl You're late to the party! You should read my thoughts on this at #41655 (comment) when @Elfocrash made the same point if you haven't yet. We're way more consistent with this options pattern (or should I say lack of options pattern) for middleware with no services than I would have thought. We're rarely this consistent with anything else 🤣.

But I think long term it's probably better to switch to UseRateLimiter on the ServiceCollection w/ an Action. Given that we already shipped this version of the API for preview6, I'd like to get this PR in as-is (modulo the other minor feedback) before the preview7 snap tomorrow, then go through another API review to switch to the action-based approach (as well as a couple other small changes) to be merged later this week

Completely agree.

I do understand no matter how consistent we are with not using the options pattern in this specific scenario, it's rare to use middleware without any default services but with interesting options must people will want to configure. And this does feel awkward when you're more used to configuring options for middleware that consumes services.

Is it likely that UseRateLimiter will need to register default services for .NET 7? If so, configuring options when adding the services like you do for most things would avoid this controversy entirely!

@@ -14,11 +14,11 @@ public interface IRateLimiterPolicy<TPartitionKey>
/// <summary>
/// Gets the <see cref="Func{OnRejectedContext, CancellationToken, ValueTask}"/> that handles requests rejected by this middleware.
/// </summary>
public Func<OnRejectedContext, CancellationToken, ValueTask>? OnRejected { get; }
Func<OnRejectedContext, CancellationToken, ValueTask>? OnRejected { get; }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason it's a Func instead of a method is because of the policy.OnRejected is not null check here. This seems preferrable than adding a bool OverridesOnRejected { get; } to determine whether the policy.

Maybe it's okay if we always run the global OnRejected Func and the policy's OnRejected method though.

src/Middleware/RateLimiting/src/PolicyNameKey.cs Outdated Show resolved Hide resolved
src/Middleware/RateLimiting/src/RateLimitingMiddleware.cs Outdated Show resolved Hide resolved
@wtgodbe wtgodbe merged commit 450671c into dotnet:main Jul 12, 2022
@wtgodbe wtgodbe deleted the wtgodbe/RateLimit2 branch July 12, 2022 23:24
@ghost ghost added this to the 7.0-preview7 milestone Jul 12, 2022
@adityamandaleeka adityamandaleeka added the blog-candidate Consider mentioning this in the release blog post label Aug 4, 2022
@ghost
Copy link

ghost commented Aug 4, 2022

@wtgodbe, this change will be considered for inclusion in the blog post for the release it'll ship in. Nice work!

Please ensure that the original comment in this thread contains a clear explanation of what the change does, why it's important (what problem does it solve?), and, if relevant, include things like code samples and/or performance numbers.

This content may not be exactly what goes into the blog post, but it will help the team putting together the announcement.

Thanks!

@amcasey amcasey added area-middleware Includes: URL rewrite, redirect, response cache/compression, session, and other general middlewares and removed area-runtime labels Jun 6, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-middleware Includes: URL rewrite, redirect, response cache/compression, session, and other general middlewares blog-candidate Consider mentioning this in the release blog post
Projects
None yet
Development

Successfully merging this pull request may close these issues.