在访问 Web API 需要授权的场景中,使用 ActionFilterAttribute 的建议实际上不会限制客户端访问未被授权的 API。客户端可以继续调用 API 而没有任何限制。
WebApiThrottling 项目使用 DelegatingHandler 来解决这个问题。以下是一个示例 DelegatingHandler,它基本上做了与其他使用 ActionFilterAttribute 的答案相同的事情。添加的好处是它适用于已经授权和未经授权的客户端。
public enum TimeUnit
{
Minute = 60,
Hour = 3600,
Day = 86400
}
public class ThrottleHandler : DelegatingHandler
{
private class Error
{
public string Message;
}
private TimeUnit _timeUnit;
private int _count;
public ThrottleHandler(TimeUnit unit, int count)
{
_timeUnit = unit;
_count = count;
}
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
var seconds = Convert.ToInt32(TimeUnit);
var key = string.Join(
"-",
seconds,
request.Method,
request.RequestUri.AbsolutePath,
GetClientIpAddress(request)
);
var cnt = 1;
if (HttpRuntime.Cache[key] != null)
{
cnt = (int)HttpRuntime.Cache[key] + 1;
}
HttpRuntime.Cache.Insert(
key,
cnt,
null,
DateTime.UtcNow.AddSeconds(seconds),
Cache.NoSlidingExpiration,
CacheItemPriority.Low,
null
);
if (cnt > _count)
{
var response = request.CreateResponse((HttpStatusCode)429, new Error() { Message = "API call quota exceeded! {Count} calls per {TimeUnit} allowed." });
return Task.FromResult(response);
}
return base.SendAsync(request, cancellationToken);
}
private string GetClientIpAddress(HttpRequestMessage request)
{
if (request.Properties.ContainsKey("MS_HttpContext"))
{
return ((HttpContextWrapper)request.Properties["MS_HttpContext"]).Request.UserHostAddress;
}
if (request.Properties.ContainsKey(RemoteEndpointMessageProperty.Name))
{
RemoteEndpointMessageProperty prop = (RemoteEndpointMessageProperty)request.Properties[RemoteEndpointMessageProperty.Name];
return prop.Address;
}
if (HttpContext.Current != null)
{
return HttpContext.Current.Request.UserHostAddress;
}
return String.Empty;
}
}