Permalink
Browse files

added rate limiting attribute

  • Loading branch information...
1 parent 7b47904 commit 0ed98866dd940bfd91df6139ad751ed1f8cd7024 @nberardi nberardi committed Mar 17, 2011
@@ -130,6 +130,8 @@
<Compile Include="Web\Mvc\ServiceHelper.cs" />
<Compile Include="Web\Mvc\ServiceOnlyAttribute.cs" />
<Compile Include="Web\Mvc\StopwatchAttribute.cs" />
+ <Compile Include="Web\Mvc\RateLimitAttribute.cs" />
+ <Compile Include="Web\Mvc\RateLimitedResult.cs" />
<Compile Include="Web\Mvc\UnsupportedMediaTypeResult.cs" />
<Compile Include="Web\Mvc\XmlView.cs" />
</ItemGroup>
@@ -43,7 +43,6 @@ public override void ExecuteResult(ControllerContext context)
response.Write("<html><head><title>Unauthorized</title></head><h1>Unauthorized</h1></html>");
- response.Flush(); // TODO: remove this when you figure out a way to get around forms
response.End();
}
}
@@ -0,0 +1,106 @@
+using System;
+using System.Collections.Generic;
+using System.Web.Caching;
+using System.Web.Mvc;
+using ManagedFusion.Web.Mvc;
+
+namespace ManagedFusion.Web.Mvc
+{
+ public class RateLimitAttribute : ActionFilterAttribute
+ {
+ private TimeSpan _timeout;
+
+ public RateLimitAttribute()
+ {
+ Limit = 100;
+ _timeout = new TimeSpan(0, 1, 0);
+ }
+
+ public int Limit { get; set; }
+
+ public int Timeout
+ {
+ get { return Convert.ToInt32(_timeout.TotalSeconds); }
+ set { _timeout = new TimeSpan(0, 0, value); }
+ }
+
+ protected virtual void HandleRateLimitedRequest(ActionExecutingContext filterContext)
+ {
+ filterContext.Result = new RateLimitedResult();
+ }
+
+ public override void OnActionExecuting(ActionExecutingContext filterContext)
+ {
+ var httpContext = filterContext.HttpContext;
+ var response = httpContext.Response;
+ var cache = httpContext.Cache;
+ var user = httpContext.User;
+
+ if (!user.IsInRole("Admin"))
+ {
+ var key = user.Identity.ToString();
+ var throttle = cache[key] as ThrottleContext;
+
+ if (throttle == null)
+ {
+ throttle = new ThrottleContext(Limit, _timeout);
+ cache.Insert(key, throttle, null, Cache.NoAbsoluteExpiration, _timeout);
+ }
+
+ var success = throttle.Increment();
+
+ response.AppendHeader("X-RateLimit-Limit", Limit.ToString());
+ response.AppendHeader("X-RateLimit-Remaining", (Limit - throttle.Count).ToString());
+ response.AppendHeader("X-RateLimit-Timeout", _timeout.ToString());
+
+ if (!success)
+ HandleRateLimitedRequest(filterContext);
+ }
+
+ base.OnActionExecuting(filterContext);
+ }
+
+ private class ThrottleContext
+ {
+ private int _requestCount;
+ private TimeSpan _timeout;
+
+ private object _lock;
+ private Queue<DateTime> _requests;
+
+ public ThrottleContext(int requestCount, TimeSpan timeout)
+ {
+ _requestCount = requestCount;
+ _timeout = timeout;
+
+ _lock = new object();
+ _requests = new Queue<DateTime>(_requestCount);
+ }
+
+ public int Count
+ {
+ get { return _requests.Count; }
+ }
+
+ public bool Increment()
+ {
+ lock (_lock)
+ {
+ var now = DateTime.Now;
+
+ // dequeue all the requests that have exceeded the timeout
+ while (_requests.Count > 0 && (now - _requests.Peek()) > _timeout)
+ _requests.Dequeue();
+
+ if (_requests.Count < _requestCount)
+ {
+ _requests.Enqueue(now);
+ return true;
+ }
+
+ return false;
+ }
+ }
+ }
+ }
+}
@@ -0,0 +1,39 @@
+using System;
+using System.Web.Mvc;
+using System.Net;
+
+
+namespace ManagedFusion.Web.Mvc
+{
+ public class RateLimitedResult : ActionResult
+ {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="BasicAuthenticationResult"/> class.
+ /// </summary>
+ /// <param name="realm">The realm.</param>
+ public RateLimitedResult()
+ {
+ }
+
+ /// <summary>
+ /// Enables processing of the result of an action method by a custom type that inherits from <see cref="T:System.Web.Mvc.ActionResult"/>.
+ /// </summary>
+ /// <param name="context">The context within which the result is executed.</param>
+ public override void ExecuteResult(ControllerContext context)
+ {
+ if (context == null)
+ throw new ArgumentNullException("context");
+
+ // 401 is the HTTP status code for unauthorized access - setting this
+ // will cause the active authentication module to execute its default
+ // unauthorized handler
+ var response = context.HttpContext.Response;
+
+ response.Clear();
+ response.StatusCode = (int)HttpStatusCode.RequestTimeout;
+ response.StatusDescription = "Request Timeout";
+
+ response.End();
+ }
+ }
+}
@@ -25,7 +25,7 @@ public override void OnActionExecuted(ActionExecutedContext filterContext)
var httpContext = filterContext.HttpContext;
var response = httpContext.Response;
- response.AddHeader("X-Stopwatch", _stopwatch.Elapsed.ToString());
+ response.AddHeader("X-Runtime", _stopwatch.Elapsed.ToString());
}
}
}

0 comments on commit 0ed9886

Please sign in to comment.