Request throttling in ASP.NET Web API

Real world scenario

When you start to build real world REST API you will surely try to limit access to you API. You can limit number of concurrent requests by using http://www.iis.net/download/dynamiciprestrictions
or some other tools.

In this post I will try to limit access to my ASP.NET Web API by using my own code building my own custom DelegatingHandler.
 

 

What you will see here

In this code bellow is ThrottleRateHandler that will be used to limit number of calls to your API in hour. Also these same code will limit how many times you can call API in seconds.

These class is using code similar as StackOverflow has used for their throttle rate handler.
Also these code is based on Token authorization approach.

I will also use HTTPRuntime.Cache to implement this functionality. I plan for my real world REST API to use MemBase caching. So question for my readers: Is it smart to replace HTTPRuntime.Cache with MemBase later ?

Here is code for ThrottleRateHandler :

public class ThrottleRateHandler : DelegatingHandler
    {
        protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
        {
            string query = request.RequestUri.Query;
            string accessToken = HttpUtility.ParseQueryString(query).Get("accessToken");
            double seconds = Convert.ToDouble(WebConfigurationManager.AppSettings["throttleTimeoutForMethodCall"]);
            int numberRequests = Convert.ToInt32(WebConfigurationManager.AppSettings["tokenRequestsPerHour"]);

            if (accessToken != null)
            {
                var throttleKey = string.Format(
                    "{0}-{1}", request.RequestUri.AbsolutePath, accessToken);
                var tokenKey = accessToken;
                var allowExecute = false;

                if (HttpRuntime.Cache[throttleKey] == null)
                {
                    // this check was added for integration testing
                    if (!seconds.Equals(0.0))
                    {
                        HttpRuntime.Cache.Add(
                            throttleKey,
                            true,
                            null,
                            DateTime.Now.AddSeconds(seconds),
                            Cache.NoSlidingExpiration,
                            CacheItemPriority.Low,
                            null);
                    }

                    allowExecute = true;
                }

                if (allowExecute)
                {
                    var hit = (HitInfo)(HttpRuntime.Cache[tokenKey] jQuery152020203585348297026_1330795835814 new HitInfo());

                    if (hit.Hits > numberRequests)
                    {
                        allowExecute = false;
                    }
                    else
                    {
                        hit.Hits++;
                    }

                    if (hit.Hits == 1)
                    {
                        HttpRuntime.Cache.Add(
                            tokenKey,
                            hit,
                            null,
                            DateTime.Now.AddMinutes(60),
                            Cache.NoSlidingExpiration,
                            CacheItemPriority.AboveNormal,
                            null);
                    }

                    if (!allowExecute)
                    {
                        return this.ReturnException(string.Format("You can call API {0} times per hour", numberRequests));
                    }
                }
                else
                {
                   return this.ReturnException(string.Format("You can call API every {0} seconds", seconds));
                }
            }
            else
            {
                var response = new HttpResponseMessage
                {
                    Content =
                        new StringContent("You must supply valid token to access method!"),
                    StatusCode = HttpStatusCode.Unauthorized
                };
                return Task<HttpResponseMessage>.Factory.StartNew(() => response);
            }

            return base.SendAsync(request, cancellationToken);
        }

        private Task<HttpResponseMessage> ReturnException(string message)
        {
            var response = new HttpResponseMessage
            {
                Content = new StringContent(message),
                StatusCode = HttpStatusCode.Conflict
            };
            return Task<HttpResponseMessage>.Factory.StartNew(() => response);
        }

        public class HitInfo
        {
            public HitInfo()
            {
                Hits = 1;
            }

            public string Token { get; set; }
            public int Hits { get; set; }
        }
    }

 

If you want to learn more about ASP.NET Web API I recommend a great book that I love written by real experts:

BookWebAPIEvolvable
Designing Evolvable Web APIs with ASP.NET
 
 

Comments are closed.