-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
#2138 Introduce ASP.NET rate limiting #2188
base: develop
Are you sure you want to change the base?
#2138 Introduce ASP.NET rate limiting #2188
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for PR creation!
Please move current test/Ocelot.IntegrationTests/RateLimitingTests to Ocelot.AcceptanceTests project, we already have a suitable class: ClientRateLimitingTests. However, these tests only cover the Ocelot feature "Rate Limit by Client's Header." Since you are proposing a new feature, a new testing class should be created.
P.S. I recommend (and require) inheriting from Steps
, which offers many useful testing helpers, so there's no need to reinvent the wheel.
Yhlas, thank you once again. Here's the current status: I will request an official code review after transitioning tests to acceptance testing. Please remember to draft the preliminary docs. Regarding the delivery vision, I cannot include it in the current Autumn'24 milestone due to anticipated multiple development rounds. Post-release, our team intends to upgrade Ocelot to .NET 9. It appears your feature may be incorporated into the release alongside this upgrade. Therefore, I suggest testing the PR code with the current .NET 9 RC. Is that okay? Consequently, we will target two frameworks in the project files: |
I targeted Ocelot project and a sample to net 9.0-rc. No issue on manual testing. I will start working on docs for this in a few days. Thanks! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can enhance the quality of the PR: my suggestions are outlined below 👇
FYI, I will revisit this PR once it has been reviewed by a team member as well.
P.S. Keep in mind Line-Ending Gotchas aka EOL Fun. I saw some files have fake changes because of changed line endings.
test/Ocelot.UnitTests/Configuration/Validation/RouteFluentValidatorTests.cs
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How do you use it? Is this built-in Visual Studio feature to call APIs?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Little "play" icon shows near each request definition that can be clicked to send http request. Below screenshot is from Rider, but VS has this feature too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But this is Rider IDE! Thanks for the link!
I will learn the same feature for dev productivity in Visual Studio...
public static IServiceCollection AddRateLimiting(this IServiceCollection services) => services | ||
.AddSingleton<IRateLimiting, RateLimiting.RateLimiting>() | ||
.AddSingleton<IRateLimitStorage, MemoryCacheRateLimitStorage>(); | ||
public static IServiceCollection AddRateLimiting(this IServiceCollection services, IConfiguration configurationRoot) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interface Breaking Change❗
Can old Ocelot applications compile successfully with this change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will add a new method instead of overriding existing one. I assumed this method is for internal purposes, but now I realize that it was a wrong assumption given that this method is public.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The decision hinges on the ultimate logic! Perhaps separating the internal extension methods is not essential.
I advise to revisit this once all the logic has been developed.
@@ -103,7 +103,7 @@ public OcelotBuilder(IServiceCollection services, IConfiguration configurationRo | |||
Services.TryAddSingleton<IDownstreamRouteProviderFactory, DownstreamRouteProviderFactory>(); | |||
Services.TryAddSingleton<IHttpResponder, HttpContextResponder>(); | |||
Services.TryAddSingleton<IErrorsToHttpStatusCodeMapper, ErrorsToHttpStatusCodeMapper>(); | |||
Services.AddRateLimiting(); // Feature: Rate Limiting | |||
Services.AddRateLimiting(configurationRoot); // Feature: Rate Limiting |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generally, it should be acceptable; however, I would advise separating the setup for each rate limiting mode.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
separated
#if NET7_0_OR_GREATER | ||
app.UseWhen(UseDotNetRateLimiter, rateLimitedApp => | ||
{ | ||
rateLimitedApp.UseRateLimiter(); | ||
}); | ||
#endif |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good, but it can be encapsulated in UseRateLimiting
method (line 80), right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
@raman-m That's a great idea, but we are stuck with the .NET rate limiters specs, you can't modify them on the fly, as you could with the current limiters. The rate limiters policies must be known, and therefore configured during startup |
@ggnaegi, what is your suggestion? |
…Context` response accessed via `IHttpContextAccessor` (ThreeMammals#1307) * set rate limiting headers on the proper httpcontext * fix Retry-After header * merge fix * refactor of ClientRateLimitTests * merge fix * Fix build after rebasing * EOL: test/Ocelot.AcceptanceTests/Steps.cs * Add `RateLimitingSteps` * code review by @raman-m * Inject IHttpContextAccessor, not IServiceProvider * Ocelot's rate-limiting headers have become legacy * Headers definition life hack * A good StackOverflow link --------- Co-authored-by: Jolanta Łukawska <[email protected]> Co-authored-by: Raman Maksimchuk <[email protected]>
Co-authored-by: Raman Maksimchuk <[email protected]>
59ef966
to
e8987ad
Compare
@yjorayev @raman-m public class CustomRateLimiterMiddleware
{
private readonly RequestDelegate _next;
private readonly TokenBucketRateLimiter _rateLimiter;
public CustomRateLimiterMiddleware(RequestDelegate next)
{
_next = next;
// Create a rate limiter with a custom configuration
_rateLimiter = new TokenBucketRateLimiter(
new TokenBucketRateLimiterOptions
{
TokenLimit = 10,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 5,
ReplenishmentPeriod = TimeSpan.FromSeconds(1),
TokensPerPeriod = 1,
AutoReplenishment = true
});
}
public async Task InvokeAsync(HttpContext context)
{
// Attempt to lease a token for each request
var lease = await _rateLimiter.WaitAsync();
if (lease.IsAcquired)
{
try
{
await _next(context);
}
finally
{
lease.Dispose();
}
}
else
{
context.Response.StatusCode = StatusCodes.Status429TooManyRequests;
}
}
} |
@ggnaegi, @raman-m I think we can make this work. Couple things:
What do you think? |
Yes, that's the main concern; we essentially need to reinvent the wheel for the middleware part. I've tried to "dynamize" the policies myself, but I believe it’s not possible. However, at least we can retain Microsoft's RateLimiter implementations and replace the current ones. I’d go down that path, and if you look at the code, there isn’t much to modify. |
No objections!
I don't recommend copying and pasting code from the MS codebase for long-term purposes because the internal implementation can change with .NET version updates. However, it can be acceptable in the short term until we develop a more practical design solution based on Let's hack this private method?! 😎 Why not to call it with a help of reflection technology? 🙊 |
I disagree about calling private methods, because private method signature can(probably will) change even with minor version upgrades. What are the advantages of calling the private method via reflection in contrast to my original proposal in this PR? I understand that my proposal is also sort of a hack, but it only interacts with public methods and classes which is less likely to change. |
@yjorayev @raman-m Mmmh, i haven't read this bit of trolling from Raman. My point was: Use the classes FixedWindowRateLimiter, SlidingWindowRateLimiter etc. and avoid our constructs that are less reliable. |
New Feature
Closes #2138
Discussion thread
Proposed Changes
ocelot.json
. It defaults to Ocelot's existing rate limiter if not specifiedDocumentation block