When building APIs, regardless of the framework and language you are using, you may want to limit the number of requests an API accepts within a window of time. This is done by rate-limiting strategy which helps to limit the network traffic. In this article, I am going to implement a very simple rate-limiting middleware in ASP.NET Core.
Definitions
We will create the following middlewares and classes to enable request throttling in our App.
RateLimitMiddlware:
This Middleware aborts an API call if a client exceeds the number of requests the API receives within a time frame in which we considered for the executing API through decorating them by a class (i.e., RateLimitDecorator) that describes a rate-limiting strategy.
RateLimitDecorator:
We decorate our APIs with this class to choose a strategy needed for request throttling.
Implementation
First off, create an ASP.Net Core Web API.
Then add a class named RateLimitMiddlware.cs as follows:
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Controllers;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
namespace dotnetcore_rate_limiting
{
public class RateLimitMiddlware
{
private readonly RequestDelegate _next;
static readonly ConcurrentDictionary<string, DateTime?>
ApiCallsInMemory = new();
public RateLimitMiddlware(RequestDelegate next)
{
_next=next;
}
public async Task InvokeAsync(HttpContext context)
{
var endpoint = context.GetEndpoint();
varcontrollerActionDescriptor = endpoint?
.Metadata.GetMetadata<ControllerActionDescriptor>();
if (controllerActionDescriptor is null)
{
await _next(context);
return;
}
var apiDecorator = (RateLimitDecorator)
controllerActionDescriptor.MethodInfo
.GetCustomAttributes(true)
.SingleOrDefault(w => w.GetType() ==
typeof(RateLimitDecorator));
if (apiDecorator is null)
{
await _next(context);
return;
}
string key = GetCurrentClientKey(apiDecorator,
context);
var previousApiCall = GetPreviousApiCallByKey(key);
if (previousApiCall != null)
{
if (DateTime.Now<previousApiCall.Value.
AddSeconds(5))
{
context.Response.StatusCode =
(int)HttpStatusCode.TooManyRequests;
return;
}
}
UpdateApiCallFor(key);
await _next(context);
}
/// <summary>
/// We store the time that a client made a call to the
current API
/// </summary>
/// <paramname="key"></param>
private void UpdateApiCallFor(string key)
{
ApiCallsInMemory.TryRemove(key, out_);
ApiCallsInMemory.TryAdd(key, DateTime.Now);
}
private DateTime? GetPreviousApiCallByKey(string key)
{
ApiCallsInMemory.TryGetValue(key, outDateTime?
value);
return value;
}
/// <summary>
/// Makes key based on the specified strategy for the
current API
/// </summary>
/// <paramname="apiDecorator"></param>
/// <paramname="context"></param>
/// <returns></returns>
private static string GetCurrentClientKey
(
RateLimitDecorator apiDecorator,
HttpContext context)
{
var keys = new List<string>
{
context.Request.Path
};
if (apiDecorator.StrategyType == StrategyTypeEnum.
IpAddress)
keys.Add(GetClientIpAddress(context));
// TODO: Implement other strategies.
return string.Join('_', keys);
}
/// <summary>
/// Returns the client's Ip Address
/// </summary>
/// <paramname="context"></param>
/// <returns></returns>
private static string GetClientIpAddress(HttpContext
context)
{
// Check this out
// https://stackoverflow.com/questions/28664686/how-
do-i-get-client-ip-address-in-asp-net-core
return context.Connection.RemoteIpAddress.ToString();
}
}
}
Then, create the RateLimitDecorator attribute to be used by your APIs. Currently, It contains a StrategyType property only.
using System;
namespace dotnetcore_rate_limiting
{
[AttributeUsage(AttributeTargets.Method)]
public class RateLimitDecorator : Attribute
{
public StrategyTypeEnum StrategyType { get; set; }
}
}
namespace dotnetcore_rate_limiting
{
public enum StrategyTypeEnum
{
IpAddress,
PerUser,
PerApiKey
}
}
Now, let’s config our startup class to use the implemented RateLimitMiddleware:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// Other middlewares ...
app.UseMiddleware<RateLimitMiddlware>();
app.UseEndpoints(endpoints=>
{
endpoints.MapControllers();
});
}
It is time to decorate our API with RateLimitDecorator to limit the number of requests it receives.
[HttpGet] [RateLimitDecorator(StrategyType=StrategyTypeEnum.IpAddress)]
public IEnumerable<WeatherForecast> Get()
{
var rng = new Random();
return Enumerable.Range(1, 5).Select(index => new
WeatherForecast
{
Date=DateTime.Now.AddDays(index),
TemperatureC=rng.Next(-20, 55),
Summary=Summaries[rng.Next(Summaries.Length)]
})
.ToArray(); }
Congrats! You limited the Get API to be executable every 5 seconds based on the clients’ Ip Addresses.
If you try to execute the API twice or more within 5 seconds, the middleware comes into play and returns 429 status code, which is Too many requests.
Source : Medium - AhmadReza Payan
The Tech Platform
Comments