Rectangle 27 0

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


/// <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 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(ActionExecutingContext c)
    {
        var key = string.Concat(Name, "-", c.HttpContext.Request.UserHostAddress);
        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.";

            c.Result = new ContentResult { Content = Message.Replace("{n}", Seconds.ToString()) };
            // see 409 - http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
            c.HttpContext.Response.StatusCode = (int)HttpStatusCode.Conflict;
        }
    }
}
[Throttle(Name="TestThrottle", Message = "You must wait {n} seconds before accessing this url again.", Seconds = 5)]
public ActionResult TestThrottle()
{
    return Content("TestThrottle executed");
}

Sign up for our newsletter and get our top new questions delivered to your inbox (see an example).

@BrettRobi, I'm pretty sure they have server affinity based on the users IP address. So they will likely still be hitting the same server.

@Pure.Krome - yes, it could be. When retrieving the client IP, we use a helper function that checks both the REMOTE_ADDR and HTTP_X_FORWARDED_FOR server variables and sanitizes appropriately.

Feel free to give feedback on this method; when we make Stack Overflow better, you get your Ewok fix even faster :)

For those of you who care and have read this far down in the comment stream...we ended up writting our own redirects that clear out the throttle cache key before redirecting. This way all redirects pass through the code to remove the key and none of them trigger the Throttle attribute.

Here's a generic version of what we've been using on Stack Overflow for the past year:

The ASP.NET Cache works like a champ here - by using it, you get automatic clean-up of your throttle entries. And with our growing traffic, we're not seeing that this is an issue on the server.

quick question - you're using the c.HttpContext.Request.UserHostAddress value as part of the key. Is that value possible empty or null or all the same value? (ie, if u're using a load balancer and it's the IP of that machine .. not the real clients) Like, do proxy's or load balancers (ie an BIG IP F5) put the same data in there and you need to check for X-Forwarded-For also or something?

Note
Rectangle 27 0

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


We use the technique borrowed from this URL http://www.codeproject.com/KB/aspnet/10ASPNetPerformance.aspx, not for throttling, but for a poor man's Denial Of Service (D.O.S). This is also cache-based, and may be similar to what you are doing. Are you throttling to prevent D.O.S. attacks? Routers can certainly be used to reduce D.O.S; do you think a router could handle the throttling you need?

Note
Rectangle 27 0

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


X concurrent connections in Y milliseconds
X requests in Y milliseconds
Y milliseconds

"The Dynamic IP Restrictions for IIS 7.0 is a module that provides protection against denial of service and brute force attacks on web server and web sites. Such protection is provided by temporarily blocking IP addresses of the HTTP clients who make unusually high number of concurrent requests or who make large number of requests over small period of time." http://learn.iis.net/page.aspx/548/using-dynamic-ip-restrictions/

Do you know if it caused any problems with crawlers like the Googlebot?

I would love to use this, but it does NOT allow you to throttle by <location>. It's every request for the app or none.

Microsoft has a new extension for IIS 7 called Dynamic IP Restrictions Extension for IIS 7.0 - Beta.

Note
Rectangle 27 0

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


X concurrent connections in Y milliseconds
X requests in Y milliseconds
Y milliseconds

"The Dynamic IP Restrictions for IIS 7.0 is a module that provides protection against denial of service and brute force attacks on web server and web sites. Such protection is provided by temporarily blocking IP addresses of the HTTP clients who make unusually high number of concurrent requests or who make large number of requests over small period of time." http://learn.iis.net/page.aspx/548/using-dynamic-ip-restrictions/

Do you know if it caused any problems with crawlers like the Googlebot?

I would love to use this, but it does NOT allow you to throttle by <location>. It's every request for the app or none.

Microsoft has a new extension for IIS 7 called Dynamic IP Restrictions Extension for IIS 7.0 - Beta.

Note
Rectangle 27 0

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


/// <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 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(ActionExecutingContext c)
    {
        var key = string.Concat(Name, "-", c.HttpContext.Request.UserHostAddress);
        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.";

            c.Result = new ContentResult { Content = Message.Replace("{n}", Seconds.ToString()) };
            // see 409 - http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
            c.HttpContext.Response.StatusCode = (int)HttpStatusCode.Conflict;
        }
    }
}
[Throttle(Name="TestThrottle", Message = "You must wait {n} seconds before accessing this url again.", Seconds = 5)]
public ActionResult TestThrottle()
{
    return Content("TestThrottle executed");
}

@BrettRobi, I'm pretty sure they have server affinity based on the users IP address. So they will likely still be hitting the same server.

@Pure.Krome - yes, it could be. When retrieving the client IP, we use a helper function that checks both the REMOTE_ADDR and HTTP_X_FORWARDED_FOR server variables and sanitizes appropriately.

Feel free to give feedback on this method; when we make Stack Overflow better, you get your Ewok fix even faster :)

For those of you who care and have read this far down in the comment stream...we ended up writting our own redirects that clear out the throttle cache key before redirecting. This way all redirects pass through the code to remove the key and none of them trigger the Throttle attribute.

Here's a generic version of what we've been using on Stack Overflow for the past year:

The ASP.NET Cache works like a champ here - by using it, you get automatic clean-up of your throttle entries. And with our growing traffic, we're not seeing that this is an issue on the server.

quick question - you're using the c.HttpContext.Request.UserHostAddress value as part of the key. Is that value possible empty or null or all the same value? (ie, if u're using a load balancer and it's the IP of that machine .. not the real clients) Like, do proxy's or load balancers (ie an BIG IP F5) put the same data in there and you need to check for X-Forwarded-For also or something?

Note
Rectangle 27 0

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


We use the technique borrowed from this URL http://www.codeproject.com/KB/aspnet/10ASPNetPerformance.aspx, not for throttling, but for a poor man's Denial Of Service (D.O.S). This is also cache-based, and may be similar to what you are doing. Are you throttling to prevent D.O.S. attacks? Routers can certainly be used to reduce D.O.S; do you think a router could handle the throttling you need?

Note