我正在尝试通过以下方式实现请求限制:
在ASP.NET MVC中实现请求限制的最佳方法?
我已将该代码提取到我的解决方案中,并使用以下属性修饰了API控制器端点:
[Route("api/dothis/{id}")] [AcceptVerbs("POST")] [Throttle(Name = "TestThrottle", Message = "You must wait {n} seconds before accessing this url again.", Seconds = 5)] [Authorize] public HttpResponseMessage DoThis(int id) {...}
这会编译,但属性的代码不会被命中,并且限制不起作用.我没有收到任何错误.我错过了什么?
建议的解决方案不准确.至少有5个理由.
缓存不提供不同线程之间的互锁控制,因此可以同时处理多个请求,从而引入跳过限制的额外调用.
过滤器正在Web API管道中"在游戏中太晚"处理,因此在您决定不应该处理请求之前,需要花费大量资源.应该使用DelegatingHandler,因为它可以设置为在Web API管道的开头运行,并在执行任何其他工作之前切断请求.
Http缓存本身是新的运行时可能无法使用的依赖项,例如自托管选项.最好避免这种依赖.
上述示例中的缓存不保证其在调用之间的存活,因为它可能由于内存压力而被移除,尤其是低优先级.
虽然问题不是太糟糕,但将响应状态设置为"冲突"似乎并不是最佳选择.最好使用'429-too many requests'来代替.
在实施限制时,还有许多问题和隐藏的障碍需要解决.有免费的开源选项.我建议您查看https://throttlewebapi.codeplex.com/.
WebApiThrottle现在已成为该领域的冠军.
它非常容易集成.只需将以下内容添加到App_Start\WebApiConfig.cs
:
config.MessageHandlers.Add(new ThrottlingHandler() { // Generic rate limit applied to ALL APIs Policy = new ThrottlePolicy(perSecond: 1, perMinute: 20, perHour: 200) { IpThrottling = true, ClientThrottling = true, EndpointThrottling = true, EndpointRules = new Dictionary<string, RateLimits> { //Fine tune throttling per specific API here { "api/search", new RateLimits { PerSecond = 10, PerMinute = 100, PerHour = 1000 } } } }, Repository = new CacheRepository() });
它也可以作为一个同名的nuget使用.
您似乎混淆了ASP.NET MVC控制器的动作过滤器和ASP.NET Web API控制器的动作过滤器.这是两个完全不同的类:
对于ASP.NET MVC:System.Web.Mvc.ActionFilterAttribute
- >这就是你从链接中得到的
对于ASP.NET Web API:System.Web.Http.Filters.ActionFilterAttribute
- >这就是您需要实现的
看来,您所显示的是Web API控制器操作(在控制器内部声明的操作ApiController
).因此,如果您要对其应用自定义过滤器,则必须从中派生System.Web.Http.Filters.ActionFilterAttribute
.
那么让我们继续并调整Web API的代码:
public class ThrottleAttribute : ActionFilterAttribute { /// <summary> /// A unique name for this Throttle. /// </summary> /// <remarks> /// We'll be inserting a Cache record based on this name and client IP, e.g. "Name-192.168.0.1" /// </remarks> public string Name { get; set; } /// <summary> /// The number of seconds clients must wait before executing this decorated route again. /// </summary> public int Seconds { get; set; } /// <summary> /// A text message that will be sent to the client upon throttling. You can include the token {n} to /// show this.Seconds in the message, e.g. "Wait {n} seconds before trying again". /// </summary> public string Message { get; set; } public override void OnActionExecuting(HttpActionContext actionContext) { var key = string.Concat(Name, "-", GetClientIp(actionContext.Request)); var allowExecute = false; if (HttpRuntime.Cache[key] == null) { HttpRuntime.Cache.Add(key, true, // is this the smallest data we can have? null, // no dependencies DateTime.Now.AddSeconds(Seconds), // absolute expiration Cache.NoSlidingExpiration, CacheItemPriority.Low, null); // no callback allowExecute = true; } if (!allowExecute) { if (string.IsNullOrEmpty(Message)) { Message = "You may only perform this action every {n} seconds."; } actionContext.Response = actionContext.Request.CreateResponse( HttpStatusCode.Conflict, Message.Replace("{n}", Seconds.ToString()) ); } } }
其中GetClientIp
方法来自this post
.
现在,您可以在Web API控制器操作上使用此属性.