Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/301.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 如何在Web Api中限制请求?_C#_.net_Asp.net Web Api_Throttling - Fatal编程技术网

C# 如何在Web 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

我试图通过以下方式实现请求限制:

我已将该代码拉入解决方案,并使用以下属性装饰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) {...}

这会编译,但属性的代码不会被命中,并且限制也不起作用。但是我没有发现任何错误。我遗漏了什么?

使用操作筛选器中的语句再次检查
。使用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;
            }
        }
    }