Как дросселировать запросы в веб-Api?
Я пытаюсь реализовать дросселирование запросов через следующее:
лучший способ реализовать дросселирование запросов в ASP.NET MVC?
Я вытащил этот код в свое решение и украсил конечную точку контроллера API атрибутом:
[Route("api/dothis/{id}")]
[AcceptVerbs("POST")]
[Throttle(Name = "TestThrottle", Message = "You must wait {n} seconds before accessing this url again.", Seconds = 5)]
[Authorize]
public HttpResponseMessage DoThis(int id) {...}
это компилируется, но код атрибута не попадает, и регулирование не работает. Я не получаю никаких ошибок. Что я упускаю?
6 ответов
вы, кажется, путаете фильтры действий для ASP.NET контроллер MVC и фильтры действий для ASP.NET контроллер веб-API. Это 2 совершенно разных класса:
- для ASP.NET MVC:
System.Web.Mvc.ActionFilterAttribute
- > вот что вы получили по ссылке - для ASP.NET Web API:
System.Web.Http.Filters.ActionFilterAttribute
- > это то, что вам нужно реализовать
похоже, что то, что вы показали, является действием контроллера веб-API (тот, который объявлено внутри контроллера, производного от ApiController
). Поэтому, если вы хотите применить к нему пользовательские фильтры, они должны быть производными от System.Web.Http.Filters.ActionFilterAttribute
.
Итак, давайте продолжим и адаптируем код для Web API:
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(HttpActionContext actionContext)
{
var key = string.Concat(Name, "-", GetClientIp(actionContext.Request));
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.";
}
actionContext.Response = actionContext.Request.CreateResponse(
HttpStatusCode.Conflict,
Message.Replace("{n}", Seconds.ToString())
);
}
}
}
здесь GetClientIp
метод исходит из this post
.
теперь вы можете использовать этот атрибут в своем действии контроллера веб-API.
предложенное решение не является точным. Есть как минимум 5 причин.
- кэш не обеспечивает управление блокировкой между различными потоками, поэтому несколько запросов могут быть обработаны одновременно, вводя дополнительные вызовы, пропуская через дроссель.
- фильтр обрабатывается "слишком поздно в игре" в конвейере веб-API, поэтому много ресурсов тратится, прежде чем вы решите, что запрос не должен быть обработан. Этот DelegatingHandler следует использовать, поскольку его можно настроить на запуск в начале конвейера веб-API и отключение запроса перед выполнением любой дополнительной работы.
- сам http-кэш является зависимостью, которая может быть недоступна с новыми временами выполнения, такими как параметры самостоятельного размещения. Лучше всего избегать этой зависимости.
- кэш в приведенном выше примере не гарантирует его выживание между вызовами, поскольку он может быть удален из-за давления памяти, особенно низкого приоритет.
- хотя это не слишком плохая проблема, установка статуса ответа на "конфликт" не кажется лучшим вариантом. Вместо этого лучше использовать "429-слишком много запросов".
есть еще много проблем и скрытых препятствий для решения при реализации дросселирования. Доступны бесплатные опции с открытым исходным кодом. Я рекомендую посмотреть наhttps://throttlewebapi.codeplex.com/, например.
WebApiThrottle является довольно чемпионом в этой области.
очень легко интегрировать. Просто добавьте следующее в App_Start\WebApiConfig.cs
:
config.MessageHandlers.Add(new ThrottlingHandler()
{
// Generic rate limit applied to ALL APIs
Policy = new ThrottlePolicy(perSecond: 1, perMinute: 20, perHour: 200)
{
IpThrottling = true,
ClientThrottling = true,
EndpointThrottling = true,
EndpointRules = new Dictionary<string, RateLimits>
{
//Fine tune throttling per specific API here
{ "api/search", new RateLimits { PerSecond = 10, PerMinute = 100, PerHour = 1000 } }
}
},
Repository = new CacheRepository()
});
Он доступен как nuget тоже с тем же именем.
проверьте using
операторы в фильтре действий. При использовании контроллера API убедитесь, что вы ссылаетесь на ActionFilterAttribute в System.Web.Http.Filters
и не один System.Web.Mvc
.
using System.Web.Http.Filters;
Я использую ThrottleAttribute
чтобы ограничить скорость вызова моего API отправки коротких сообщений, но я обнаружил, что он иногда не работает. API может вызываться много раз, пока не сработает логика дроссельной заслонки, наконец, я использую System.Web.Caching.MemoryCache
вместо HttpRuntime.Cache
и проблема, кажется, решена.
if (MemoryCache.Default[key] == null)
{
MemoryCache.Default.Set(key, true, DateTime.Now.AddSeconds(Seconds));
allowExecute = true;
}
мои 2 цента добавляют некоторую дополнительную информацию для "ключа" о информации запроса о параметрах, так что другой запрос paramter разрешен с того же IP.
key = Name + clientIP + actionContext.ActionArguments.Values.ToString()
кроме того, моя небольшая забота о "clientIP", возможно ли, что два разных пользователя используют один и тот же ISP имеют один и тот же "clientIP"? Если да, то один клиент мой будет задушен неправильно.