如何在Web Api中限制请求?

时间:2022-08-17 20:04:06

I'm trying to implement request throttling via the following:

我正在尝试通过以下方式实现请求限制:

Best way to implement request throttling in ASP.NET MVC?

在ASP.NET MVC中实现请求限制的最佳方法?

I've pulled that code into my solution and decorated an API controller endpoint with the attribute:

我已将该代码提取到我的解决方案中,并使用以下属性修饰了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) {...}

This compiles but the attribute's code doesn't get hit, and the throttling doesn't work. I don't get any errors though. What am I missing?

这会编译,但属性的代码不会被命中,并且限制不起作用。我没有收到任何错误。我错过了什么?

6 个解决方案

#1


42  

You seem to be confusing action filters for an ASP.NET MVC controller and action filters for an ASP.NET Web API controller. Those are 2 completely different classes:

您似乎混淆了ASP.NET MVC控制器的动作过滤器和ASP.NET Web API控制器的动作过滤器。这是两个完全不同的类:

It appears that what you have shown is a Web API controller action (one that is declared inside a controller deriving from ApiController). So if you want to apply custom filters to it, they must derive from System.Web.Http.Filters.ActionFilterAttribute.

看来你所展示的是一个Web API控制器动作(一个在ApiController派生的控制器中声明的动作)。因此,如果要对其应用自定义过滤器,则必须从System.Web.Http.Filters.ActionFilterAttribute派生。

So let's go ahead and adapt the code for Web API:

那么让我们继续并调整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())
            );
        }
    }
}

where the GetClientIp method comes from this post.

GetClientIp方法来自这篇文章。

Now you can use this attribute on your Web API controller action.

现在,您可以在Web API控制器操作上使用此属性。

#2


43  

The proposed solution is not accurate. There are at least 5 reasons for it.

建议的解决方案并不准确。至少有5个理由。

  1. The cache does not provide interlocking control between different threads, therefore multiple requests can be process at the same time introducing extra calls skipping through the throttle.
  2. 缓存不提供不同线程之间的互锁控制,因此可以同时处理多个请求,从而引入跳过限制的额外调用。
  3. The Filter is being processed 'too late in the game' within web API pipeline, so lots of resources are being spent before you decide that request should not be processed. The DelegatingHandler should be used because it can be set to run at the beginning of the Web API pipeline and cutting off the request prior doing any additional work.
  4. 过滤器正在Web API管道中“在游戏中太晚”处理,因此在您决定不应该处理请求之前,会花费大量资源。应该使用DelegatingHandler,因为它可以设置为在Web API管道的开头运行,并在执行任何其他工作之前切断请求。
  5. The Http cache itself is dependency that might not be available with new runtimes, like self-hosted options. It is best to avoid this dependency.
  6. Http缓存本身是新的运行时可能无法使用的依赖项,例如自托管选项。最好避免这种依赖。
  7. Cache in the above example does not guarantee its survival between the calls as it might be removed due to memory pressure, especially being low priority.
  8. 上述示例中的高速缓存不保证其在调用之间的存活,因为它可能由于内存压力而被移除,尤其是低优先级。
  9. Although it is not too bad issue, setting response status to 'conflict' does not seem to be the best option. It is better to use '429-too many requests' instead.
  10. 虽然问题不是太糟糕,但将响应状态设置为“冲突”似乎并不是最佳选择。最好使用'429-too many requests'代替。

There are many more issues and hidden obstacles to solve while implementing the throttling. There are free open source options available. I recommend to look at https://throttlewebapi.codeplex.com/, for example.

在实施限制时,还有许多问题和隐藏的障碍需要解决。有免费的开源选项。我建议您查看https://throttlewebapi.codeplex.com/。

#3


24  

WebApiThrottle is quite the champ now in this area.

WebApiThrottle现在是这个领域的冠军。

It's super easy to integrate. Just add the following to App_Start\WebApiConfig.cs:

它非常容易集成。只需将以下内容添加到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()
});

It's available as a nuget too with the same name.

它也可以作为具有相同名称的nuget使用。

#4


3  

Double check the using statements in your action filter. As you're using an API controller, ensure that you are referencing the ActionFilterAttribute in System.Web.Http.Filters and not the one in System.Web.Mvc.

仔细检查操作过滤器中的using语句。在使用API​​控制器时,请确保引用System.Web.Http.Filters中的ActionFilterAttribute而不是System.Web.Mvc中的ActionFilterAttribute。

using System.Web.Http.Filters;

#5


2  

I am using ThrottleAttribute to limit the calling rate of my short-message sending API, but I found it not working sometimes. API may been called many times until the throttle logic works, finally I am using System.Web.Caching.MemoryCache instead of HttpRuntime.Cache and the problem seems to solved.

我使用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;
}

#6


0  

My 2 cents is add some extra info for 'key' about the request info on parameters, so that different paramter request is allowed from the same IP.

我的2美分是为参数的请求信息添加一些额外的“密钥”信息,以便允许来自同一IP的不同参数请求。

key = Name + clientIP + actionContext.ActionArguments.Values.ToString()

Also, my little concern about the 'clientIP', is it possible that two different user use the same ISP has the same 'clientIP'? If yes, then one client my be throttled wrongly.

另外,我对'clientIP'的关注,是否有可能两个不同的用户使用相同的ISP具有相同的'clientIP'?如果是,那么我的一个客户就会被错误地限制。

#1


42  

You seem to be confusing action filters for an ASP.NET MVC controller and action filters for an ASP.NET Web API controller. Those are 2 completely different classes:

您似乎混淆了ASP.NET MVC控制器的动作过滤器和ASP.NET Web API控制器的动作过滤器。这是两个完全不同的类:

It appears that what you have shown is a Web API controller action (one that is declared inside a controller deriving from ApiController). So if you want to apply custom filters to it, they must derive from System.Web.Http.Filters.ActionFilterAttribute.

看来你所展示的是一个Web API控制器动作(一个在ApiController派生的控制器中声明的动作)。因此,如果要对其应用自定义过滤器,则必须从System.Web.Http.Filters.ActionFilterAttribute派生。

So let's go ahead and adapt the code for Web API:

那么让我们继续并调整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())
            );
        }
    }
}

where the GetClientIp method comes from this post.

GetClientIp方法来自这篇文章。

Now you can use this attribute on your Web API controller action.

现在,您可以在Web API控制器操作上使用此属性。

#2


43  

The proposed solution is not accurate. There are at least 5 reasons for it.

建议的解决方案并不准确。至少有5个理由。

  1. The cache does not provide interlocking control between different threads, therefore multiple requests can be process at the same time introducing extra calls skipping through the throttle.
  2. 缓存不提供不同线程之间的互锁控制,因此可以同时处理多个请求,从而引入跳过限制的额外调用。
  3. The Filter is being processed 'too late in the game' within web API pipeline, so lots of resources are being spent before you decide that request should not be processed. The DelegatingHandler should be used because it can be set to run at the beginning of the Web API pipeline and cutting off the request prior doing any additional work.
  4. 过滤器正在Web API管道中“在游戏中太晚”处理,因此在您决定不应该处理请求之前,会花费大量资源。应该使用DelegatingHandler,因为它可以设置为在Web API管道的开头运行,并在执行任何其他工作之前切断请求。
  5. The Http cache itself is dependency that might not be available with new runtimes, like self-hosted options. It is best to avoid this dependency.
  6. Http缓存本身是新的运行时可能无法使用的依赖项,例如自托管选项。最好避免这种依赖。
  7. Cache in the above example does not guarantee its survival between the calls as it might be removed due to memory pressure, especially being low priority.
  8. 上述示例中的高速缓存不保证其在调用之间的存活,因为它可能由于内存压力而被移除,尤其是低优先级。
  9. Although it is not too bad issue, setting response status to 'conflict' does not seem to be the best option. It is better to use '429-too many requests' instead.
  10. 虽然问题不是太糟糕,但将响应状态设置为“冲突”似乎并不是最佳选择。最好使用'429-too many requests'代替。

There are many more issues and hidden obstacles to solve while implementing the throttling. There are free open source options available. I recommend to look at https://throttlewebapi.codeplex.com/, for example.

在实施限制时,还有许多问题和隐藏的障碍需要解决。有免费的开源选项。我建议您查看https://throttlewebapi.codeplex.com/。

#3


24  

WebApiThrottle is quite the champ now in this area.

WebApiThrottle现在是这个领域的冠军。

It's super easy to integrate. Just add the following to App_Start\WebApiConfig.cs:

它非常容易集成。只需将以下内容添加到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()
});

It's available as a nuget too with the same name.

它也可以作为具有相同名称的nuget使用。

#4


3  

Double check the using statements in your action filter. As you're using an API controller, ensure that you are referencing the ActionFilterAttribute in System.Web.Http.Filters and not the one in System.Web.Mvc.

仔细检查操作过滤器中的using语句。在使用API​​控制器时,请确保引用System.Web.Http.Filters中的ActionFilterAttribute而不是System.Web.Mvc中的ActionFilterAttribute。

using System.Web.Http.Filters;

#5


2  

I am using ThrottleAttribute to limit the calling rate of my short-message sending API, but I found it not working sometimes. API may been called many times until the throttle logic works, finally I am using System.Web.Caching.MemoryCache instead of HttpRuntime.Cache and the problem seems to solved.

我使用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;
}

#6


0  

My 2 cents is add some extra info for 'key' about the request info on parameters, so that different paramter request is allowed from the same IP.

我的2美分是为参数的请求信息添加一些额外的“密钥”信息,以便允许来自同一IP的不同参数请求。

key = Name + clientIP + actionContext.ActionArguments.Values.ToString()

Also, my little concern about the 'clientIP', is it possible that two different user use the same ISP has the same 'clientIP'? If yes, then one client my be throttled wrongly.

另外,我对'clientIP'的关注,是否有可能两个不同的用户使用相同的ISP具有相同的'clientIP'?如果是,那么我的一个客户就会被错误地限制。