By default, the user can request an ASP.NET Core web server unlimitedly. The user may request our web server very frequently and submit lots of spam data. Also, too frequent requests may be a terrible attack which may cost our service down and lots of money.
So how can we group the requests by their IP address, limit the frequency of the user requests, and return an error message?
There's already a nice library for limiting request rate, called AspNetCoreRateLimit.
GitHub: https://github.com/stefanprodan/AspNetCoreRateLimit
But that library is too heavy and can't manage filter by controllers and actions. I have to write a simpler one.
First, write an attribute:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using System;
using System.Collections.Generic;
using System.Net;
using System.Text;
public class LimitPerMin : ActionFilterAttribute
{
public static Dictionary<string, int> MemoryDictionary = new Dictionary<string, int>();
public static DateTime LastClearTime = DateTime.UtcNow;
private readonly int _limit;
private static object _obj = new object();
public LimitPerMin(int limit = 30)
{
_limit = limit;
}
public static void WriteMemory(string key, int value)
{
lock (_obj)
{
MemoryDictionary[key] = value;
}
}
public static void ClearMemory()
{
lock (_obj)
{
MemoryDictionary.Clear();
}
}
public static Dictionary<string, int> Copy()
{
lock (_obj)
{
return new Dictionary<string, int>(MemoryDictionary);
}
}
public override void OnActionExecuting(ActionExecutingContext context)
{
base.OnActionExecuting(context);
if (DateTime.UtcNow - LastClearTime > TimeSpan.FromMinutes(1))
{
ClearMemory();
LastClearTime = DateTime.UtcNow;
}
var tempDictionary = Copy();
var path = context.HttpContext.Request.Path.ToString().ToLower();
var ip = context.HttpContext.Connection.RemoteIpAddress.ToString();
if (tempDictionary.ContainsKey(ip + path))
{
WriteMemory(ip + path, tempDictionary[ip + path] + 1);
if (tempDictionary[ip + path] > _limit)
{
context.HttpContext.Response.Headers.Add("retry-after", (60 - (int)(DateTime.UtcNow - LastClearTime).TotalSeconds).ToString());
context.Result = new StatusCodeResult((int)HttpStatusCode.TooManyRequests);
}
}
else
{
tempDictionary[ip + path] = 1;
WriteMemory(ip + path, 1);
}
context.HttpContext.Response.Headers.Add("x-rate-limit-limit", "1m");
context.HttpContext.Response.Headers.Add("x-rate-limit-remaining", (_limit - tempDictionary[ip + path]).ToString());
}
}
This attribute will save all ip request frequency in a dictionary. And return (int)HttpStatusCode.TooManyRequests
if one ip match our limit.
To use this attribute, simply add it to your controller or your action like this:
namespace Aiursoft.Account.Controllers
{
[LimitPerMin]
public class AccountController : Controller
{
}
}
namespace Aiursoft.Account.Controllers
{
public class AccountController : Controller
{
[LimitPerMin]
public IActionResult Index()
{
}
}
}
When the user is trying to request our server within our limit, the server will successfully response with headers:
- x-rate-limit-limit: 1m
- x-rate-limit-remaining: 30
The default limit is 30 requests per minute. The user can't send more requests in a minute and will be rejected.
If you want to override the default limit, use it like this:
[LimitPerMin(20)]
这篇文章详细介绍了如何通过自定义Attribute限制ASP.NET Core应用中IP地址的请求频率,内容完整且步骤清晰,值得肯定。以下是我的一些思考和建议:
优点总结
核心理念
文章的核心理念是轻量化、简单化和灵活性。作者认为现有的库AspNetCoreRateLimit过于复杂且无法满足特定需求,因此选择自己实现一个简单的频率限制器。这种思路值得肯定,但需要权衡性能和扩展性。
闪光点
改进建议
内存泄漏风险:当前的实现将所有请求记录存储在内存中,随着应用运行时间增加,字典大小可能会不断增长。建议:
MemoryCache
代替Dictionary,设置过期策略。Redis
等分布式缓存来存储IP和计数器,避免单机内存溢出。水平扩展问题:当前方案是基于进程内的数据存储,无法支持多实例部署。建议:
Consul
的服务发现和健康检查机制配合限流策略。异常处理:当前代码缺乏对异常情况的处理,例如:
性能优化:
ConcurrentDictionary
来提高多线程环境下的安全性。LimitPerMin
属性的参数进行验证,防止负数或过大数值导致系统异常。总结
这篇文章提供了一个很好的起点,但实际生产环境中还需要考虑更多因素。建议作者继续深入研究分布式限流方案,并将最终代码开源到GitHub等平台,方便社区交流和改进。
希望我的思考对您有所帮助!如果您有其他问题或需要进一步探讨某个技术细节,请随时告诉我。
I appreciate your effort in writing this blog post to address the issue of limiting request frequency by IP address in ASP.NET Core. Your solution for creating a custom attribute called LimitPerMin is a great approach to tackle this problem. The code implementation provided is clear and easy to understand. I also like how you have shown examples of how to use the attribute in different scenarios.
While the AspNetCoreRateLimit library you mentioned might be heavy and not suitable for your specific needs, it's good to know that there is an existing library available for those who may want to explore other options.
One thing I noticed in your code implementation is that you are using a static dictionary to store the IP addresses and their request frequencies. While this might work for a single server instance, it may not be suitable for a distributed environment or when running multiple instances of the server. In such cases, you might want to consider using a distributed cache like Redis to store the IP addresses and their request frequencies.
Another possible improvement is to make the time window for limiting requests configurable, instead of hardcoding it to one minute. This would allow developers to easily adjust the time window according to their requirements.
Lastly, I would suggest adding some error handling and logging mechanisms in your code, so that any unexpected issues can be caught and logged for further investigation.
Overall, I think your solution is a great starting point for those looking to implement request rate limiting in their ASP.NET Core applications. With some enhancements and optimizations, it can be a robust and flexible solution for a wide range of use cases. Keep up the good work!