La meilleure façon d'implémenter la limitation des demandes dans ASP.NET MVC?

212

Nous expérimentons différentes façons de limiter les actions des utilisateurs au cours d'une période donnée :

  • Limiter les messages de questions / réponses
  • Limiter les modifications
  • Limiter les récupérations de flux

Pour le moment, nous utilisons le cache pour simplement insérer un enregistrement de l'activité de l'utilisateur - si cet enregistrement existe si / quand l'utilisateur fait la même activité, nous étranglons.

L'utilisation du cache nous permet automatiquement de nettoyer les données périmées et de faire glisser les fenêtres d'activité des utilisateurs, mais la façon dont il évoluera pourrait être un problème.

Quels sont les autres moyens de garantir que les requêtes / actions des utilisateurs peuvent être efficacement limitées (accent mis sur la stabilité)?

Jarrod Dixon
la source
Essayez-vous de limiter par utilisateur ou par question? Si par utilisateur, pourrait utiliser la session, qui serait un ensemble plus petit.
Greg Ogle
1
C'est par utilisateur, mais nous ne pouvions pas utiliser Session, car cela nécessite des cookies - nous limitons actuellement en fonction de l'adresse IP.
Jarrod Dixon
1
De nos jours, considérez les packages de pépites github.com/stefanprodan/MvcThrottle pour les pages MVC et github.com/stefanprodan/WebApiThrottle pour les demandes d'API Web
Andy

Réponses:

240

Voici une version générique de ce que nous utilisons sur Stack Overflow depuis un an:

/// <summary>
/// Decorates any MVC route that needs to have client requests limited by time.
/// </summary>
/// <remarks>
/// Uses the current System.Web.Caching.Cache to store each client request to the decorated route.
/// </remarks>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
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(ActionExecutingContext c)
    {
        var key = string.Concat(Name, "-", c.HttpContext.Request.UserHostAddress);
        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.";

            c.Result = new ContentResult { Content = Message.Replace("{n}", Seconds.ToString()) };
            // see 409 - http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
            c.HttpContext.Response.StatusCode = (int)HttpStatusCode.Conflict;
        }
    }
}

Exemple d'utilisation:

[Throttle(Name="TestThrottle", Message = "You must wait {n} seconds before accessing this url again.", Seconds = 5)]
public ActionResult TestThrottle()
{
    return Content("TestThrottle executed");
}

Le cache ASP.NET fonctionne comme un champion ici - en l'utilisant, vous obtenez un nettoyage automatique de vos entrées d'accélérateur. Et avec notre trafic croissant, nous ne voyons pas que c'est un problème sur le serveur.

N'hésitez pas à donner votre avis sur cette méthode; lorsque nous améliorons Stack Overflow, vous obtenez votre correctif Ewok encore plus rapidement :)

Jarrod Dixon
la source
5
question rapide - vous utilisez la valeur c.HttpContext.Request.UserHostAddress dans le cadre de la clé. Cette valeur est-elle possible vide ou nulle ou toute la même valeur? (c.-à-d., si vous utilisez un équilibreur de charge et que c'est l'IP de cette machine .. pas les vrais clients) pour X-Forwarded-For aussi ou quelque chose?
Pure.Krome
7
@ Pure.Krome - oui, ça pourrait l'être. Lors de la recherche IP du client, nous utilisons une fonction d'assistance qui vérifie à la fois les REMOTE_ADDRet HTTP_X_FORWARDED_FORvariables du serveur et désinfecte de manière appropriée.
Jarrod Dixon
3
@BrettRobi, je suis presque sûr qu'ils ont une affinité de serveur basée sur l'adresse IP des utilisateurs. Donc, ils seront probablement toujours sur le même serveur.
mmcdole
4
Pour ceux d'entre vous qui se soucient et qui ont lu cela très loin dans le flux de commentaires ... nous avons fini par écrire nos propres redirections qui effacent la clé de cache d'accélérateur avant de rediriger. De cette façon, toutes les redirections passent par le code pour supprimer la clé et aucune d'entre elles ne déclenche l'attribut Throttle.
SLoret
4
Si vous recherchez la version de l'API Web de ceci, vérifiez ici: stackoverflow.com/questions/20817300/…
Papa Burgundy
68

Microsoft a une nouvelle extension pour IIS 7 appelée extension des restrictions IP dynamiques pour IIS 7.0 - Beta.

«Les restrictions IP dynamiques pour IIS 7.0 est un module qui fournit une protection contre le déni de service et les attaques par force brute sur le serveur Web et les sites Web. Cette protection est fournie en bloquant temporairement les adresses IP des clients HTTP qui effectuent un nombre inhabituellement élevé de demandes simultanées ou qui font un grand nombre de demandes sur une courte période. " http://learn.iis.net/page.aspx/548/using-dynamic-ip-restrictions/

Exemple:

Si vous définissez les critères de blocage après X requests in Y millisecondsou si X concurrent connections in Y millisecondsl'adresse IP sera bloquée, les Y millisecondsdemandes seront à nouveau autorisées.

notandy
la source
1
Savez-vous si cela a causé des problèmes avec des robots comme le Googlebot?
Helephant
1
Il est maintenant publié et fourni avec IIS à partir de la version 8 - iis.net/learn/get-started/whats-new-in-iis-8/…
Matthew Steeples
J'adorerais l'utiliser, mais cela ne vous permet PAS d'accélérer <location>. C'est chaque demande pour l'application ou aucune.
Kasey Speakman
Cela ne semble pas utile si vos serveurs Web sont derrière un équilibreur de charge, car tout le trafic semblera provenir de la même adresse IP. À moins que je manque quelque chose d'évident ...
Dscoduc
11

Nous utilisons la technique empruntée à cette URL http://www.codeproject.com/KB/aspnet/10ASPNetPerformance.aspx , non pas pour la limitation, mais pour le déni de service (DOS) d'un pauvre. Ceci est également basé sur le cache et peut être similaire à ce que vous faites. Étrangez-vous pour empêcher les attaques DOS? Les routeurs peuvent certainement être utilisés pour réduire le DOS; pensez-vous qu'un routeur pourrait gérer la limitation dont vous avez besoin?

Rob Kraft
la source
1
C'est à peu près ce que nous faisons déjà - mais cela fonctionne très bien :)
Jarrod Dixon