Rate limiting is a technique used to restrict the number of requests allowed to a particular resource in a specific time window to thwart DDoS attacks and API abuses. When a rate limiting threshold is reached, subsequent requests to the resource are disallowed, delayed, or throttled.
In a previous article, I discussed how to get started with the new rate limiting middleware in ASP.NET Core 7. In this article, I’ll examine the four different rate limiter algorithms available in ASP.NET Core 7: fixed window, sliding window, token bucket, and concurrency.
To use the code examples provided in this article, you should have Visual Studio 2022 installed in your system. If you don’t already have a copy, you can download Visual Studio 2022 here.
Create an ASP.NET Core minimal Web API project in Visual Studio 2022
First off, let’s create an ASP.NET Core 7 minimal Web API project in Visual Studio 2022. Follow these steps:
- Launch the Visual Studio 2022 IDE.
- Click on “Create new project.”
- In the “Create new project” window, select “ASP.NET Core Web API” from the list of templates displayed.
- Click Next.
- In the “Configure your new project” window, specify the name and location for the new project.
- Optionally check the “Place solution and project in the same directory” check box, depending on your preferences.
- Click Next.
- In the “Additional Information” window shown next, select “NET 7.0 (Current)” as the framework. Uncheck the check box that says “Use controllers…” as we’ll be using minimal APIs in this example. Leave the “Authentication Type” set to “None” (default).
- Ensure that the check boxes “Enable Docker,” “Configure for HTTPS,” and “Enable Open API Support” are all unchecked. We won’t be using any of those features here.
- Click Create.
We’ll use this ASP.NET Core 7 Web API project to create a minimal API and implement the four different rate limiter algorithms in the sections below.
Implement a controller in ASP.NET Core 7
Let’s implement a controller to demonstrate how rate limiting works in ASP.NET Core. To do this, select the Controllers solution folder of your project and then click Add -> Controller. Select the “API Controller - Empty” template from the list of templates displayed in the Add Scaffold window and enter a name for the controller when prompted. Then replace the default code with the following.
[Route("api/[controller]")] [ApiController] public class DefaultController : ControllerBase { [HttpGet] public string Get() { return "Hello World"; } [HttpGet("{id}")] public string Get(int id) { return $"The value of id is: {id}"; } }
The RateLimiter abstract base class in ASP.NET Core 7
Prior to ASP.NET Core 7, rate limiting was available as part of the Microsoft.AspNetCore.RateLimiting namespace. Rate limiting in ASP.NET Core 7 is now available as part of the System.Threading.RateLimiting namespace.
The main type is the abstract base class RateLimiter, which has several amazing features. The RateLimiter abstract class looks like this:
public abstract class RateLimiter : IAsyncDisposable, IDisposable { public abstract RateLimiterStatistics? GetStatistics(); public abstract TimeSpan? IdleDuration { get; } public RateLimitLease AttemptAcquire(int permitCount = 1); protected abstract RateLimitLease AttemptAcquireCore(int permitCount); public ValueTask<RateLimitLease> AcquireAsync(int permitCount = 1, CancellationToken cancellationToken = default); protected abstract ValueTask<RateLimitLease> AcquireAsyncCore(int permitCount, CancellationToken cancellationToken); protected virtual void Dispose(bool disposing); public void Dispose(); protected virtual ValueTask DisposeAsyncCore(); public async ValueTask DisposeAsync(); }
Note that only the method declarations have been provided here. The method definitions have been omitted for brevity.
Configure rate limiting in ASP.NET Core 7
To configure the rate limiting middleware in ASP.NET Core 7, you use the AddRateLimiter method. To add the rate limiting middleware to your ASP.NET Core 7 application, you first add the required services to the container as shown in the code snippet given below.
builder.Services.AddRateLimiter(options =>{ //Write your code to configure the middleware here});
To add the middleware to the pipeline, you call the UseRateLimiter extension method as shown below.
app.UseRateLimiter();
You can configure RateLimiter with several options, including the maximum number of requests allowed, the response status code, and a time window. You can also define the rate limit based on the HTTP method, the client IP address, and other factors. In addition, you can queue requests instead of rejecting them.
Rate limiter algorithms in ASP.NET Core 7
The System.Threading.RateLimiting package provides support for the following algorithmic models:
- Fixed window
- Sliding window
- Token bucket
- Concurrency
Fixed window
The fixed window algorithm allows a fixed number of requests within a specific time window, and all subsequent requests are throttled. Based on the rate-limiting requirement, this algorithm divides time into fixed windows. For example, assume you want to allow 10 requests per minute. Once this limit is reached, the subsequent requests will be rejected until the window resets.
The following code snippet shows how you can configure the fixed window rate limiter in the Program.cs file in ASP.NET Core 7.
builder.Services.AddRateLimiter(options => { options.RejectionStatusCode = 429; options.AddFixedWindowLimiter(policyName: "fixed", options => { options.PermitLimit = 3; options.Window = TimeSpan.FromSeconds(10); options.AutoReplenishment = true; options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst; options.QueueLimit = 2; }); });
The AddLimiter method is used to add rate-limiting services to the services container. The AddFixedWindowLimiter method is used to add a fixed window policy. The policy name is specified here as "fixed". Note the values of the PermitLimit and Window properties. By setting PermitLimit to 3 and Window to 10, you allow a maximum of three requests every 10 seconds.
When you run the application and call an endpoint more frequently than the permitted limit, HTTP Status Code 503 “Service unavailable” will be returned by default. Simply change the RejectionStatusCode to return a different status code. In the example above, the RejectionStatusCode property is set to return HTTP Status Code 429 “Too Many Requests.”
Additionally, the QueueProcessingOrder is specified as OldestFirst, and the QueueLimit is set to 2. Hence, the subsequent two requests will be throttled and stored in a queue whenever the window limit is exceeded. Then the oldest request will be picked from the queue and processed.
Sliding window
Like the fixed window, the sliding window algorithm allows a fixed number of requests per time window. The difference is that a sliding window divides the time window into segments. At each interval of a segment, the window slides by one segment.
The segment interval is equal to the window time divided by the number of segments per window. So if your window is 60 seconds, and you specify two segments, the time window will slide every 30 seconds.
The following code snippet illustrates how you can configure the sliding window rate limiter in the Program.cs file in ASP.NET Core 7.
builder.Services.AddRateLimiter(options => { options.RejectionStatusCode = 429; options.AddSlidingWindowLimiter(policyName: "sliding", options => { options.PermitLimit = 30; options.Window = TimeSpan.FromSeconds(60); options.SegmentsPerWindow = 2; options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst; options.QueueLimit = 2; }); });
The SegmentsPerWindow property is used to specify the number of segments in the time window.
Token bucket
In the token bucket algorithm, each token in the bucket represents a request. A token is removed from the bucket whenever a request is served. If the bucket becomes empty, the next request is rejected or throttled. As time passes, the bucket refills at a fixed rate.
Consider an example where a bucket has a limit of 10 tokens. When a request comes in, and a token is available, the request will be served and the token count decreased. If the token limit is exceeded and there are no tokens left, requests will be rejected or throttled.
The following code example shows how you can configure the token rate limiter in the Program.cs file in ASP.NET Core 7.
builder.Services.AddRateLimiter(options => { options.RejectionStatusCode = 429; options.AddTokenBucketLimiter(policyName: "token", options => { options.TokenLimit = 2; options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst; options.QueueLimit = 2; options.ReplenishmentPeriod = TimeSpan.FromSeconds(10); options.TokensPerPeriod = 2; options.AutoReplenishment = true; }); });
The TokenLimit property specifies the maximum number of tokens the bucket can store at any given time.
Concurrency
A concurrency limiter controls the maximum number of simultaneous requests to a resource. If you set a limit of 10, for example, only the first 10 requests will be granted access to the resource at a given point of time. Whenever a request completes, it opens a slot for a new request.
The following code snippet shows how you can configure the concurrency rate limiter in ASP.NET Core 7.
builder.Services.AddRateLimiter(options => { options.RejectionStatusCode = 429; options.AddConcurrencyLimiter(policyName: "concurrency", options => { options.PermitLimit = 3; options.QueueProcessingOrder = QueueProcessingOrder.OldestFirst; options.QueueLimit = 2; }); });
Enable or disable rate limiting in ASP.NET Core 7
You can apply rate limiting to a controller, an action method, or a Razor page. The [EnableRateLimiting] and [DisableRateLimiting] attributes can be used to enable or disable rate limiting in ASP.NET Core 7.
The following code listing shows how you can apply the "fixed" rate limiter to the DefaultController we created earlier and disable rate limiting in an action method of the same controller.
[Route("api/[controller]")] [ApiController] [EnableRateLimiting("fixed")] public class DefaultController : ControllerBase { [HttpGet] public string Get() { return "Hello World"; } [HttpGet("{id}")] [DisableRateLimiting] public string Get(int id) { return $"The value of id is: {id}"; } }
Rate limiting has a number of benefits. It can protect your applications or APIs from denial-of-service and other malicious attacks, as well as from non-malicious overuse. By reducing the volume of requests in a specific time window and thereby, it also reduces network traffic and lowers infrastructure costs. Finally, it can even improve the performance of your application by ensuring fair usage of available resources.
In a subsequent article on this topic, I will discuss how we can implement custom rate limiting policies in ASP.NET Core 7.