我正在尝试通过以下方式实现请求限制:
Best way to implement request throttling in 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) {...}
这会编译,但属性的代码不会被命中,并且限制不起作用。我没有收到任何错误。我错过了什么?
答案 0 :(得分:48)
建议的解决方案不准确。至少有5个理由。
在实施限制时,还有许多问题和隐藏的障碍需要解决。有免费的开源选项。我建议您查看https://throttlewebapi.codeplex.com/,例如。
答案 1 :(得分:43)
您似乎对ASP.NET MVC控制器的动作过滤器和ASP.NET Web API控制器的动作过滤器感到困惑。这是两个完全不同的类:
System.Web.Mvc.ActionFilterAttribute
- >这就是你从链接中获得的内容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控制器操作中使用此属性。
答案 2 :(得分:28)
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使用。
答案 3 :(得分:4)
仔细检查操作过滤器中的using
语句。当您使用API控制器时,请确保在System.Web.Http.Filters
中引用ActionFilterAttribute而在<{1}}中引用而不是。
System.Web.Mvc
答案 4 :(得分:2)
我使用ThrottleAttribute
来限制短消息发送API的调用率,但我发现它有时无法正常工作。在节流逻辑工作之前,可能会多次调用API,最后我使用的是System.Web.Caching.MemoryCache
而不是HttpRuntime.Cache
,问题似乎已经解决了。
if (MemoryCache.Default[key] == null)
{
MemoryCache.Default.Set(key, true, DateTime.Now.AddSeconds(Seconds));
allowExecute = true;
}
答案 5 :(得分:1)
我的2美分是为参数的请求信息添加一些额外的“密钥”信息,以便允许来自同一IP的不同参数请求。
key = Name + clientIP + actionContext.ActionArguments.Values.ToString()
另外,我对'clientIP'的关注,是否有可能两个不同的用户使用相同的ISP具有相同的'clientIP'?如果是,那么我的一个客户就会被错误地限制。
答案 6 :(得分:0)
在.NET Core中很容易解决。在这种情况下,我使用了“ IMemoryCache”,它是“每个服务的内存中”。但是,如果您希望基于Redis进行操作,例如只需将接口更改为IDistributedCache…(当然,请确保配置Redis)
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Caching.Memory;
using System;
using System.Net;
namespace My.ActionFilters
{
/// <summary>
/// Decorates any MVC route that needs to have client requests limited by time.
/// </summary>
/// <remarks>
/// Uses the current System.Web.Caching.Cache to store each client request to the decorated route.
/// </remarks>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class ThrottleFilterAttribute : ActionFilterAttribute
{
public ThrottleFilterAttribute()
{
}
/// <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(ActionExecutingContext c)
{
var memCache = (IMemoryCache)c.HttpContext.RequestServices.GetService(typeof(IMemoryCache));
var testProxy = c.HttpContext.Request.Headers.ContainsKey("X-Forwarded-For");
var key = 0;
if (testProxy)
{
var ipAddress = IPAddress.TryParse(c.HttpContext.Request.Headers["X-Forwarded-For"], out IPAddress realClient);
if (ipAddress)
{
key = realClient.GetHashCode();
}
}
if (key != 0)
{
key = c.HttpContext.Connection.RemoteIpAddress.GetHashCode();
}
memCache.TryGetValue(key, out bool forbidExecute);
memCache.Set(key, true, new MemoryCacheEntryOptions() { SlidingExpiration = TimeSpan.FromMilliseconds(Milliseconds) });
if (forbidExecute)
{
if (String.IsNullOrEmpty(Message))
Message = $"You may only perform this action every {Milliseconds}ms.";
c.Result = new ContentResult { Content = Message, ContentType = "text/plain" };
// see 409 - http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
c.HttpContext.Response.StatusCode = StatusCodes.Status409Conflict;
}
}
}
}
答案 7 :(得分:0)
对于 WebAPI 使用这个:
using Microsoft.Owin;
using System;
using System.Net;
using System.Net.Http;
using System.Web;
using System.Web.Caching;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
namespace MyProject.Web.Resources
{
public enum TimeUnit
{
Minute = 60,
Hour = 3600,
Day = 86400
}
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class ThrottleAttribute : ActionFilterAttribute
{
public TimeUnit TimeUnit { get; set; }
public int Count { get; set; }
public override void OnActionExecuting(HttpActionContext filterContext)
{
var seconds = Convert.ToInt32(TimeUnit);
var key = string.Join(
"-",
seconds,
filterContext.Request.Method,
filterContext.ActionDescriptor.ControllerDescriptor.ControllerName,
filterContext.ActionDescriptor.ActionName,
GetClientIpAddress(filterContext.Request)
);
// increment the cache value
var cnt = 1;
if (HttpRuntime.Cache[key] != null)
{
cnt = (int)HttpRuntime.Cache[key] + 1;
}
HttpRuntime.Cache.Insert(
key,
cnt,
null,
DateTime.UtcNow.AddSeconds(seconds),
Cache.NoSlidingExpiration,
CacheItemPriority.Low,
null
);
if (cnt > Count)
{
filterContext.Response = new HttpResponseMessage
{
Content = new StringContent("You are allowed to make only " + Count + " requests per " + TimeUnit.ToString().ToLower())
};
filterContext.Response.StatusCode = (HttpStatusCode)429; //To Many Requests
}
}
private string GetClientIpAddress(HttpRequestMessage request)
{
if (request.Properties.ContainsKey("MS_HttpContext"))
{
return IPAddress.Parse(((HttpContextBase)request.Properties["MS_HttpContext"]).Request.UserHostAddress).ToString();
}
if (request.Properties.ContainsKey("MS_OwinContext"))
{
return IPAddress.Parse(((OwinContext)request.Properties["MS_OwinContext"]).Request.RemoteIpAddress).ToString();
}
return String.Empty;
}
}
}
答案 8 :(得分:0)
您可以使用此代码
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
public class RateLimitAttribute : ActionFilterAttribute
{
public int Seconds { get; set; }
public override void OnActionExecuting(HttpActionContext actionContext)
{
var key =
$"{actionContext.ActionDescriptor.ControllerDescriptor.ControllerName}-{actionContext.ActionDescriptor.ActionName}-{actionContext.ControllerContext.RequestContext.Principal.Identity.Name}";
var allowExecute = false;
if (HttpRuntime.Cache[key] == null)
{
HttpRuntime.Cache.Add(key,
true,
null,
DateTime.Now.AddSeconds(Seconds),
Cache.NoSlidingExpiration,
CacheItemPriority.Low,
null);
allowExecute = true;
}
if (!allowExecute)
{
actionContext.Response.Content = new StringContent($"سرویس های اسکنر را تنها می توانید هر {Seconds} استفاده کنید");
actionContext.Response.StatusCode = HttpStatusCode.Conflict;
}
base.OnActionExecuting(actionContext);
}
}