Comment limiter les demandes dans une Api Web?

J'essaie d'implémenter la limitation des requêtes via ce qui suit:

Meilleure façon d'implémenter la limitation des requêtes dans ASP.NET MVC?

J'ai tiré ce code dans ma solution et décoré un point de terminaison de contrôleur API avec l'attribut:

[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) {...}

Cela compile mais le code de l'attribut n'est pas touché, et la limitation ne fonctionne pas. Je ne reçois pas toutes les erreurs si. Ce qui me manque?

35
demandé sur Community 2013-12-28 21:25:19

6 réponses

Vous semblez confondre les filtres d'action pour un ASP.NET contrôleur MVC et filtres d'action pour un ASP.NET contrôleur D'API Web. Ce sont 2 classes complètement différentes:

Il semble que ce que vous avez montré est une action de contrôleur D'API Web (qui est déclarée dans un contrôleur dérivé de ApiController). Donc, si vous voulez lui appliquer des filtres personnalisés, ils doivent dériver de System.Web.Http.Filters.ActionFilterAttribute.

Alors allons-y et adaptons le code pour L'API Web:

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())
            );
        }
    }
}

, la GetClientIp méthode vient de this post.

Maintenant, vous pouvez utiliser cet attribut sur votre action de contrôleur D'API Web.

42
répondu Darin Dimitrov 2017-05-23 11:54:34

La solution proposée n'est pas exacte. Il y a au moins 5 Raisons à cela.

  1. le cache ne fournit pas de contrôle de verrouillage entre différents threads, donc plusieurs demandes peuvent être traitées en même temps en introduisant des appels supplémentaires en sautant à travers la manette des gaz.
  2. le filtre est traité "trop tard dans le jeu" dans le pipeline de L'API web, donc beaucoup de ressources sont dépensées avant de décider que la demande ne doit pas être traitée. Le DelegatingHandler doit être utilisé car il peut être configuré pour s'exécuter au début du pipeline D'API Web et couper la requête avant d'effectuer tout travail supplémentaire.
  3. le cache Http lui - même est une dépendance qui peut ne pas être disponible avec les nouveaux runtimes, comme les options auto-hébergées. Il est préférable d'éviter cette dépendance.
  4. Cache dans l'exemple ci-dessus ne garantit pas sa survie entre les appels car il pourrait être supprimé en raison de la pression de la mémoire, en particulier étant faible priorité.
  5. Bien qu'il ne soit pas dommage problème, définir l'état de la réponse sur "conflit" ne semble pas être la meilleure option. Il est préférable d'utiliser '429-trop de requêtes à la place.

Il y a beaucoup plus de problèmes et d'obstacles cachés à résoudre lors de la mise en œuvre de la limitation. Il existe des options open source gratuites disponibles. Je recommande de regarder https://throttlewebapi.codeplex.com/, par exemple.

44
répondu lenny12345 2014-05-20 11:57:05

WebApiThrottle est tout à fait le champion maintenant dans ce domaine.

Il est super facile à intégrer. Ajoutez simplement ce qui suit à 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()
});

Il est également disponible en tant que nuget avec le même nom.

24
répondu Korayem 2016-08-25 10:59:21

Vérifiez les instructions using dans votre filtre d'action. Comme vous utilisez un contrôleur D'API, assurez-vous que vous référencez le ActionFilterAttribute dans System.Web.Http.Filters et Pas celui dans System.Web.Mvc.

using System.Web.Http.Filters;
3
répondu Ant P 2013-12-28 17:29:26

J'utilise ThrottleAttribute pour limiter le taux d'appel de mon API d'envoi de messages courts, mais je l'ai trouvé ne fonctionnant pas parfois. API peut être appelé plusieurs fois jusqu'à ce que la logique de l'accélérateur fonctionne, enfin j'utilise System.Web.Caching.MemoryCache au lieu de HttpRuntime.Cache et le problème semble résolu.

if (MemoryCache.Default[key] == null)
{
    MemoryCache.Default.Set(key, true, DateTime.Now.AddSeconds(Seconds));
    allowExecute = true;
}
2
répondu Bruce 2015-06-09 10:08:06

Mes 2 cents est d'ajouter quelques informations supplémentaires pour ' key ' sur les informations de demande sur les paramètres, de sorte que la demande de paramter différente est autorisée à partir de la même adresse IP.

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

Aussi, ma petite préoccupation au sujet du 'clientIP' , est-il possible que deux utilisateurs différents utilisent le même FAI a le même 'clientIP'? Si oui, alors un client mon être étranglé à tort.

0
répondu RyanShao 2016-04-20 02:13:45