Come limitare le richieste in un Web Api?

Sto cercando di implementare la limitazione delle richieste tramite il seguente:

Il modo migliore per implementare la limitazione delle richieste in ASP.NET MVC?

Ho inserito il codice nella mia soluzione e decorato un endpoint del controller API con l’attributo:

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

Questo viene compilato ma il codice dell’attributo non viene colpito e la limitazione non funziona. Non ho alcun errore però. Cosa mi manca?

    Sembra che tu stia confondendo i filtri di azione per un controller ASP.NET MVC e filtri di azione per un controller API Web ASP.NET. Quelli sono 2 classi completamente diverse:

    • Per ASP.NET MVC: System.Web.Mvc.ActionFilterAttribute -> questo è ciò che hai ottenuto dal link
    • Per API Web ASP.NET: System.Web.Http.Filters.ActionFilterAttribute -> questo è ciò che è necessario implementare

    Sembra che ciò che è stato mostrato sia un’azione del controller API Web (una dichiarata all’interno di un controller derivante da ApiController ). Pertanto, se si desidera applicare filtri personalizzati, devono derivare da System.Web.Http.Filters.ActionFilterAttribute .

    Quindi andiamo avanti e adattiamo il codice per l’API Web:

     public class ThrottleAttribute : ActionFilterAttribute { ///  /// A unique name for this Throttle. ///  ///  /// We'll be inserting a Cache record based on this name and client IP, eg "Name-192.168.0.1" ///  public string Name { get; set; } ///  /// The number of seconds clients must wait before executing this decorated route again. ///  public int Seconds { get; set; } ///  /// 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, eg "Wait {n} seconds before trying again". ///  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()) ); } } } 

    dove il metodo GetClientIp proviene da this post .

    Ora è ansible utilizzare questo attributo sull’azione del controller API Web.

    La soluzione proposta non è accurata. Ci sono almeno 5 ragioni per questo.

    1. La cache non fornisce il controllo di interblocco tra diversi thread, pertanto è ansible elaborare più richieste contemporaneamente introducendo chiamate extra saltando attraverso il throttle.
    2. Il filtro viene elaborato “troppo tardi nel gioco” all’interno della pipeline dell’API web, quindi vengono spese molte risorse prima di decidere che la richiesta non debba essere elaborata. Il DelegatingHandler dovrebbe essere usato perché può essere impostato per essere eseguito all’inizio della pipeline dell’API Web e tagliare la richiesta prima di eseguire qualsiasi lavoro aggiuntivo.
    3. La stessa cache Http è una dipendenza che potrebbe non essere disponibile con nuovi runtime, come le opzioni self-hosted. È meglio evitare questa dipendenza.
    4. La cache nell’esempio precedente non garantisce la sua sopravvivenza tra le chiamate in quanto potrebbe essere rimossa a causa della pressione della memoria, in particolare la priorità bassa.
    5. Anche se non è un problema troppo brutto, impostare lo stato di risposta su “conflitto” non sembra essere l’opzione migliore. È preferibile utilizzare “429 – troppe richieste”.

    Ci sono molti altri problemi e ostacoli nascosti da risolvere durante l’implementazione della limitazione. Sono disponibili opzioni open source gratuite. Raccomando di consultare https://throttlewebapi.codeplex.com/ , ad esempio.

    WebApiThrottle è piuttosto il campione in questo settore.

    È super facile da integrare. Basta aggiungere quanto segue ad 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 { //Fine tune throttling per specific API here { "api/search", new RateLimits { PerSecond = 10, PerMinute = 100, PerHour = 1000 } } } }, Repository = new CacheRepository() }); 

    È disponibile anche come nuget con lo stesso nome.

    Controlla le istruzioni using nel tuo filtro azione. Poiché stai utilizzando un controller API, assicurati di fare riferimento a ActionFilterAttribute in System.Web.Http.Filters e non a quello in System.Web.Mvc .

     using System.Web.Http.Filters; 

    Sto usando ThrottleAttribute per limitare la frequenza di chiamata della mia API di invio di messaggi brevi, ma a volte ho riscontrato che non funzionava. L’API può essere stata chiamata molte volte fino a quando la logica del throttle funziona, finalmente sto usando System.Web.Caching.MemoryCache invece di HttpRuntime.Cache e il problema sembra risolto.

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

    I miei 2 centesimi aggiungono alcune informazioni aggiuntive per la ‘chiave’ sulle informazioni sulla richiesta sui parametri, in modo che la richiesta di parametri diversi sia consentita dallo stesso IP.

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

    Inoltre, la mia piccola preoccupazione per il ‘clientIP’, è ansible che due diversi utenti utilizzino lo stesso ISP con lo stesso ‘clientIP’? Se sì, allora un mio cliente verrà strozzato in modo errato.