C# 如何在Web Api中限制请求?
我试图通过以下方式实现请求限制: 我已将该代码拉入解决方案,并使用以下属性装饰API控制器端点:C# 如何在Web Api中限制请求?,c#,.net,asp.net-web-api,throttling,C#,.net,Asp.net Web Api,Throttling,我试图通过以下方式实现请求限制: 我已将该代码拉入解决方案,并使用以下属性装饰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 HttpResponseM
[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) {...}
这会编译,但属性的代码不会被命中,并且限制也不起作用。但是我没有发现任何错误。我遗漏了什么?使用操作筛选器中的语句再次检查
。使用API控制器时,请确保引用的是System.Web.Http.Filters
中的ActionFilterAttribute,而不是System.Web.Mvc
中的ActionFilterAttribute
using System.Web.Http.Filters;
您似乎混淆了ASP.NET MVC控制器的操作过滤器和ASP.NET Web API控制器的操作过滤器。这是两个完全不同的类别:
- 对于ASP.NET MVC:->这是您从链接中获得的内容
- 对于ASP.NET Web API:->这就是您需要实现的
您所显示的似乎是一个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())
);
}
}
}
公共类ThrottleAttribute:ActionFilterAttribute
{
///
///此油门的唯一名称。
///
///
///我们将基于此名称和客户端IP插入缓存记录,例如“name-192.168.0.1”
///
公共字符串名称{get;set;}
///
///客户端在再次执行此修饰路由之前必须等待的秒数。
///
公共整数秒{get;set;}
///
///在限制时将发送到客户端的文本消息。您可以将标记{n}包含到
///在消息中显示此项。秒,例如“重试前等待{n}秒”。
///
公共字符串消息{get;set;}
公共重写无效OnActionExecuting(HttpActionContext actionContext)
{
var key=string.Concat(名称“-”,GetClientIp(actionContext.Request));
var allowExecute=false;
if(HttpRuntime.Cache[key]==null)
{
HttpRuntime.Cache.Add(键,
是的,//这是我们能得到的最小数据吗?
null,//没有依赖项
DateTime.Now.AddSeconds(秒),//绝对过期
Cache.NoSlidingExpiration,
缓存项优先级低,
null);//没有回调
allowExecute=true;
}
如果(!allowExecute)
{
if(string.IsNullOrEmpty(Message))
{
Message=“您只能每{n}秒执行一次此操作。”;
}
actionContext.Response=actionContext.Request.CreateResponse(
HttpStatusCode.Conflict,
Message.Replace(“{n}”,Seconds.ToString())
);
}
}
}
其中GetClientIp
方法来自何处
现在,您可以在Web API控制器操作中使用此属性。建议的解决方案不准确。至少有5个原因
缓存不提供不同线程之间的联锁控制,因此可以同时处理多个请求,从而引入额外的调用,通过节流跳过李>
在web API管道中处理过滤器“太晚了”,因此在您决定不处理该请求之前,需要花费大量资源。应该使用DelegatingHandler,因为可以将其设置为在Web API管道的开始处运行,并在执行任何其他工作之前切断请求
Http缓存本身是新运行时可能不可用的依赖项,如自托管选项。最好避免这种依赖关系
上例中的缓存不能保证在调用之间生存,因为它可能会由于内存压力而被删除,尤其是低优先级
虽然这不是一个太坏的问题,但将响应状态设置为“冲突”似乎不是最好的选择。最好改用“429太多请求”
在实施节流时,还有许多问题和隐藏的障碍需要解决。有免费的开源选项可用。例如,我建议看一下。我正在使用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;
}
我的2美分是添加一些额外的信息为'key'关于参数的请求信息,这样不同的参数请求是允许从同一IP
key = Name + clientIP + actionContext.ActionArguments.Values.ToString()
另外,我不太关心“clientIP”,是否有可能两个不同的用户使用同一个ISP拥有相同的“clientIP”?如果是的话,那么我的一个客户可能会被错误地扼杀。现在在这一领域相当抢手
它非常容易集成。只需将以下内容添加到App\u 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()
});
config.MessageHandlers.Add(新的ThrottlingHandler()
{
//适用于所有API的通用速率限制
策略=新的节流策略(持续时间:1,许可时间:20,每小时:200)
{
IpThrottling=true,
ClientThrottling=true,
EndpointThrottling=true,
EndpointRules=新字典
{
//此处根据特定API微调节流
{“api/search”,新费率限制{PerSecond=10,PerMinute=100,PerHour=1000}
}
},
Repository=newcacherepository()
});
它也可以作为名称相同的nuget提供。在.NET Core中很容易解决。在本例中,我使用了IMemoryCache,它是“每个服务的内存中”。怎么
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;
}
}
}
}
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;
}
}
}