it-swarm.com.ru

Как регулировать запросы в веб-интерфейсе?

Я пытаюсь реализовать регулирование запросов с помощью следующего: 

Лучший способ реализовать регулирование запросов в 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) {...}

Это компилируется, но код атрибута не срабатывает, а регулирование не работает. Я не получаю никаких ошибок, хотя. Что мне не хватает?

37
RobVious

Вы, кажется, путаете фильтры действий для контроллера ASP.NET MVC и фильтры действий для контроллера ASP.NET Web API. Это 2 совершенно разных класса:

Похоже, что то, что вы показали, является действием контроллера Web API (которое объявлено внутри контроллера, полученного из ApiController). Поэтому, если вы хотите применить к нему пользовательские фильтры, они должны быть получены из System.Web.Http.Filters.ActionFilterAttribute.

Итак, давайте продолжим и адаптируем код для веб-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 .

Теперь вы можете использовать этот атрибут в вашем действии контроллера Web API.

43
Darin Dimitrov

Предлагаемое решение не является точным. На это есть как минимум 5 причин.

  1. Кэш не обеспечивает управление блокировкой между различными потоками, поэтому несколько запросов могут обрабатываться одновременно, что приводит к пропуску дополнительных вызовов через дроссель. 
  2. Фильтр обрабатывается «слишком поздно в игре» в конвейере веб-API, поэтому тратится много ресурсов, прежде чем вы решите, что запрос не должен обрабатываться. DelegatingHandler должен использоваться, потому что он может быть настроен на запуск в начале конвейера Web API и отключение запроса перед выполнением любой дополнительной работы.
  3. Сам по себе Http-кеш является зависимостью, которая может быть недоступна в новых средах выполнения, например, в параметрах собственного размещения. Лучше всего избегать этой зависимости.
  4. Кэш в приведенном выше примере не гарантирует его выживание между вызовами, поскольку он может быть удален из-за нехватки памяти, особенно с низким приоритетом.
  5. Хотя это не так уж и плохо, установка статуса ответа на «конфликт» не кажется наилучшим вариантом. Вместо этого лучше использовать «429 - слишком много запросов».

Есть еще много проблем и скрытых препятствий, которые необходимо решить при реализации регулирования. Доступны бесплатные варианты с открытым исходным кодом. Я рекомендую посмотреть на https://throttlewebapi.codeplex.com/ , например.

47
lenny12345

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 с тем же именем.

27
Korayem

Дважды проверьте операторы using в вашем фильтре действий. Поскольку вы используете контроллер API, убедитесь, что вы ссылаетесь на ActionFilterAttribute в System.Web.Http.Filters и не тот, что в System.Web.Mvc.

using System.Web.Http.Filters;
4
Ant P

Я использую 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
Bruce

Мои 2 цента - это добавить некоторую дополнительную информацию для «ключа» об информации запроса о параметрах, чтобы разрешить разные запросы параметров с одного и того же IP.

key = Name + clientIP + actionContext.ActionArguments.Values.ToString()

Кроме того, мое небольшое беспокойство по поводу «clientIP», возможно ли, что два разных пользователя, использующие один и тот же провайдер, имеют один и тот же «clientIP»? Если да, то один клиент может быть неправильно задушен.

0
RyanShao