Calls-per-day rate limiting in ASP.NET MVC 3?
I've seen Jarrod Dixon's solution (B开发者_C百科est way to implement request throttling in ASP.NET MVC?) for implementing calls-per-second rate limiting. I'm now trying to figure out how to build a similar filter for N-calls-per-day.
I am building a developer API where free accounts get ~100 calls per day and paid accounts get a higher rate limit. What's the best way to do calls-per-day rate limiting in MVC 3?
I don't think that an in-memory structure would suffice here, due to the long duration that you need to measure. IIS recycling would be problematic in this case. As such, I'd recommend recording user access to the resource in the DB and only allowing a count of 100 in the last 24 hours.
On the other hand, here's our implementation of a leaky bucket limiter (which is more handy for short term limiting where failure is relatively unimportant). Using .NET 4 concurrent collections might improve on the somewhat brute-force locking in this implementation:
public class RateLimiter
{
private readonly double numItems;
private readonly double ratePerSecond;
private readonly Dictionary<object, RateInfo> rateTable =
new Dictionary<object, RateInfo>();
private readonly object rateTableLock = new object();
private readonly double timePeriod;
public RateLimiter(double numItems, double timePeriod)
{
this.timePeriod = timePeriod;
this.numItems = numItems;
ratePerSecond = numItems / timePeriod;
}
public double Count
{
get
{
return numItems;
}
}
public double Per
{
get
{
return timePeriod;
}
}
public bool IsPermitted(object key)
{
RateInfo rateInfo;
var permitted = true;
var now = DateTime.UtcNow;
lock (rateTableLock)
{
var expiredKeys =
rateTable
.Where(kvp =>
(now - kvp.Value.LastCheckTime)
> TimeSpan.FromSeconds(timePeriod))
.Select(k => k.Key)
.ToArray();
foreach (var expiredKey in expiredKeys)
{
rateTable.Remove(expiredKey);
}
var dataExists = rateTable.TryGetValue(key,
out rateInfo);
if (dataExists)
{
var timePassedSeconds = (now - rateInfo.LastCheckTime).TotalSeconds;
var newAllowance =
Math.Min(
rateInfo.Allowance
+ timePassedSeconds
* ratePerSecond,
numItems);
if (newAllowance < 1d)
{
permitted = false;
}
else
{
newAllowance -= 1d;
}
rateTable[key] = new RateInfo(now,
newAllowance);
}
else
{
rateTable.Add(key,
new RateInfo(now,
numItems - 1d));
}
}
return permitted;
}
public void Reset(object key)
{
lock (rateTableLock)
{
rateTable.Remove(key);
}
}
private struct RateInfo
{
private readonly double allowance;
private readonly DateTime lastCheckTime;
public RateInfo(DateTime lastCheckTime, double allowance)
{
this.lastCheckTime = lastCheckTime;
this.allowance = allowance;
}
public DateTime LastCheckTime
{
get
{
return lastCheckTime;
}
}
public double Allowance
{
get
{
return allowance;
}
}
}
}
精彩评论