ejemplo - web api json c#
¿Cómo estrangular las solicitudes en una API web? (6)
Estoy usando ThrottleAttribute
para limitar la tasa de llamadas de mi API de envío de mensajes cortos, pero a veces no funciona. Es posible que se haya invocado API muchas veces hasta que funcione la lógica del acelerador, finalmente estoy usando System.Web.Caching.MemoryCache
lugar de HttpRuntime.Cache
y el problema parece resuelto.
if (MemoryCache.Default[key] == null)
{
MemoryCache.Default.Set(key, true, DateTime.Now.AddSeconds(Seconds));
allowExecute = true;
}
Estoy tratando de implementar la aceleración de solicitudes a través de lo siguiente:
¿La mejor forma de implementar la aceleración de solicitudes en ASP.NET MVC?
Inserté ese código en mi solución y decoré un punto final de controlador API con el atributo:
[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) {...}
Esto se compila, pero el código del atributo no se golpea, y la aceleración no funciona. No obstante, no tengo ningún error. ¿Qué me estoy perdiendo?
La solución propuesta no es precisa. Hay al menos 5 razones para ello.
- La caché no proporciona control de enclavamiento entre diferentes subprocesos, por lo tanto, se pueden procesar múltiples solicitudes al mismo tiempo, introduciendo llamadas extra omitiendo el acelerador.
- El filtro se está procesando "demasiado tarde en el juego" dentro de la interconexión API web, por lo que se están gastando muchos recursos antes de decidir que la solicitud no se debe procesar. Se debe usar DelegatingHandler porque se puede configurar para que se ejecute al principio de la interconexión de API web y cortando la solicitud antes de realizar cualquier trabajo adicional.
- La caché Http en sí misma es una dependencia que podría no estar disponible con los nuevos tiempos de ejecución, como las opciones autohospedadas. Lo mejor es evitar esta dependencia.
- La memoria caché en el ejemplo anterior no garantiza su supervivencia entre las llamadas, ya que podría eliminarse debido a la presión de la memoria, especialmente siendo de baja prioridad.
- Aunque no es un problema tan grave, la configuración del estado de respuesta a ''conflicto'' no parece ser la mejor opción. Es mejor usar ''429-demasiadas solicitudes'' en su lugar.
Hay muchos más problemas y obstáculos ocultos que resolver al implementar la aceleración. Hay opciones gratuitas de código abierto disponibles. Recomiendo mirar https://throttlewebapi.codeplex.com/ , por ejemplo.
Mis 2 centavos es agregar algo de información adicional para ''clave'' sobre la información de solicitud de parámetros, de modo que se permita otra solicitud de parámetro desde la misma IP.
key = Name + clientIP + actionContext.ActionArguments.Values.ToString()
Además, mi pequeña preocupación sobre el ''clienteIP'', ¿es posible que dos usuarios diferentes usen el mismo ISP que tenga el mismo ''clientIP''? Si es así, entonces un cliente puede ser estrangulado erróneamente.
Parece que está confundiendo los filtros de acción para un controlador ASP.NET MVC y los filtros de acción para un controlador ASP.NET Web API. Esas son 2 clases completamente diferentes:
- Para ASP.NET MVC:
System.Web.Mvc.ActionFilterAttribute
-> eso es lo que obtuvo del enlace - Para ASP.NET Web API:
System.Web.Http.Filters.ActionFilterAttribute
-> eso es lo que necesita implementar
Parece que lo que ha mostrado es una acción de controlador Web API (una que se declara dentro de un controlador derivado de ApiController
). Por lo tanto, si desea aplicarle filtros personalizados, estos deben derivarse de System.Web.Http.Filters.ActionFilterAttribute
.
Así que avancemos y adaptemos el código para la 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())
);
}
}
}
donde el método GetClientIp
proviene de this post
.
Ahora puede usar este atributo en su acción de controlador de API web.
Verifique las instrucciones de using
en su filtro de acción. Como está utilizando un controlador API, asegúrese de hacer referencia al ActionFilterAttribute en System.Web.Http.Filters
y no al de System.Web.Mvc
.
using System.Web.Http.Filters;
github.com/stefanprodan/WebApiThrottle es todo un campeón ahora en esta área.
Es súper fácil de integrar. Simplemente agregue lo siguiente a 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()
});
Está disponible también como nuget con el mismo nombre.