it-swarm.com.de

Wie drossle ich Anfragen in einer Web-API?

Ich versuche, die Anforderungseinschränkung über Folgendes zu implementieren: 

Bester Weg, um die Anforderungseinschränkung in ASP.NET MVC zu implementieren?

Ich habe diesen Code in meine Lösung aufgenommen und einen API-Controller-Endpunkt mit dem Attribut versehen: 

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

Dies wird kompiliert, aber der Code des Attributs wird nicht betroffen, und die Drosselung funktioniert nicht. Ich bekomme aber keine Fehler. Was vermisse ich?

37
RobVious

Sie scheinen verwirrende Aktionsfilter für einen ASP.NET-MVC-Controller und Aktionsfilter für einen ASP.NET-Web-API-Controller zu sein. Das sind 2 völlig unterschiedliche Klassen:

Anscheinend handelt es sich bei Ihrer Aktion um eine Web-API-Controller-Aktion (eine, die in einem Controller deklariert ist, der von ApiController abgeleitet ist). Wenn Sie also benutzerdefinierte Filter darauf anwenden möchten, müssen sie von System.Web.Http.Filters.ActionFilterAttribute abgeleitet werden.

Lassen Sie uns also den Code für die Web-API anpassen:

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

woher die GetClientIp-Methode stammt von this post .

Jetzt können Sie dieses Attribut für Ihre Web-API-Controller-Aktion verwenden.

43
Darin Dimitrov

Die vorgeschlagene Lösung ist nicht genau. Dafür gibt es mindestens fünf Gründe.

  1. Der Cache bietet keine Verriegelungssteuerung zwischen verschiedenen Threads. Daher können mehrere Anforderungen gleichzeitig verarbeitet werden, wodurch zusätzliche Aufrufe eingeleitet werden, die die Drossel überspringen. 
  2. Der Filter wird innerhalb der Web-API-Pipeline "zu spät im Spiel" verarbeitet. Es werden also viele Ressourcen ausgegeben, bevor Sie entscheiden, dass die Anforderung nicht verarbeitet werden soll. Der DelegatingHandler sollte verwendet werden, da er so eingestellt werden kann, dass er zu Beginn der Web-API-Pipeline ausgeführt wird und die Anforderung abgeschnitten wird, bevor weitere Arbeit ausgeführt wird.
  3. Der Http-Cache selbst ist eine Abhängigkeit, die bei neuen Laufzeiten möglicherweise nicht verfügbar ist, z. B. selbst gehostete Optionen. Am besten vermeiden Sie diese Abhängigkeit.
  4. Der Cache im obigen Beispiel garantiert nicht das Überleben zwischen den Aufrufen, da er aufgrund von Speicherdruck möglicherweise entfernt wird, insbesondere wenn er eine geringe Priorität hat.
  5. Obwohl es sich nicht um ein schlechtes Problem handelt, scheint es nicht die beste Option zu sein, den Antwortstatus auf "Konflikt" zu setzen. Es ist besser, stattdessen "429 - zu viele Anfragen" zu verwenden.

Es gibt viele weitere Probleme und versteckte Hindernisse, die bei der Implementierung der Drosselung gelöst werden müssen. Es stehen kostenlose Open Source-Optionen zur Verfügung. Ich empfehle beispielsweise https://throttlewebapi.codeplex.com/ .

47
lenny12345

WebApiThrottle ist jetzt ganz der Champion in diesem Bereich. 

Es ist super einfach zu integrieren. Fügen Sie einfach Folgendes zu App_Start\WebApiConfig.cs hinzu:

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

Es ist auch als Nuget mit demselben Namen erhältlich.

27
Korayem

Überprüfen Sie die using-Anweisungen in Ihrem Aktionsfilter. Stellen Sie bei der Verwendung eines API-Controllers sicher, dass Sie das ActionFilterAttribute in System.Web.Http.Filters und nicht das in System.Web.Mvc referenzieren.

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

Ich verwende ThrottleAttribute, um die Aufrufrate meiner API zum Senden von Kurznachrichten zu begrenzen, aber ich habe festgestellt, dass sie manchmal nicht funktioniert. API wurde oft aufgerufen, bis die Drosselungslogik funktioniert. Schließlich verwende ich System.Web.Caching.MemoryCache anstelle von HttpRuntime.Cache und das Problem scheint gelöst zu sein.

if (MemoryCache.Default[key] == null)
{
    MemoryCache.Default.Set(key, true, DateTime.Now.AddSeconds(Seconds));
    allowExecute = true;
}
2
Bruce

Meine 2 Cent sind einige zusätzliche Informationen für 'Schlüssel' über die Anforderungsinformationen zu Parametern, sodass unterschiedliche Parameteranfragen von derselben IP-Adresse aus zulässig sind.

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

Meine kleine Sorge um 'clientIP': Kann es sein, dass zwei verschiedene Benutzer denselben ISP verwenden, der dieselbe 'clientIP' hat? Wenn ja, dann kann ein Client falsch gedrosselt werden.

0
RyanShao