¿La mejor manera de implementar la limitación de solicitudes en ASP.NET MVC?

212

Estamos experimentando con varias formas de limitar las acciones del usuario en un período de tiempo determinado :

  • Limite las publicaciones de preguntas / respuestas
  • Limitar ediciones
  • Limite las recuperaciones de alimento

Por el momento, estamos usando la caché para simplemente insertar un registro de la actividad del usuario; si ese registro existe si / cuando el usuario realiza la misma actividad, aceleramos.

El uso de la caché nos da automáticamente ventanas de actividad de limpieza de datos obsoletas y deslizantes de los usuarios, pero la forma en que se escalará podría ser un problema.

¿Cuáles son algunas otras formas de garantizar que las solicitudes / acciones del usuario puedan ser efectivamente aceleradas (énfasis en la estabilidad)?

Jarrod Dixon
fuente
¿Estás tratando de limitar por usuario o por pregunta? Si es por usuario, podría usar sesión, que sería un conjunto más pequeño.
Greg Ogle el
1
Es por usuario, pero no pudimos usar Session, ya que eso requiere cookies; actualmente estamos limitando según la dirección IP.
Jarrod Dixon
1
Hoy en día, considere los paquetes nuget github.com/stefanprodan/MvcThrottle para páginas MVC y github.com/stefanprodan/WebApiThrottle para solicitudes de API web
Andy

Respuestas:

240

Aquí hay una versión genérica de lo que hemos estado usando en Stack Overflow durante el año pasado:

/// <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;
        }
    }
}

Uso de la muestra:

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

La caché de ASP.NET funciona como un campeón aquí: al usarla, obtienes una limpieza automática de tus entradas del acelerador. Y con nuestro tráfico creciente, no vemos que esto sea un problema en el servidor.

Siéntase libre de dar su opinión sobre este método; cuando mejoramos Stack Overflow, obtienes tu solución Ewok aún más rápido :)

Jarrod Dixon
fuente
55
pregunta rápida: está utilizando el valor c.HttpContext.Request.UserHostAddress como parte de la clave. ¿Es ese valor posible vacío o nulo o todo el mismo valor? (es decir, si está utilizando un equilibrador de carga y es la IP de esa máquina ... no los clientes reales) Al igual que, los proxy o los equilibradores de carga (es decir, un BIG IP F5) colocan los mismos datos allí y deben verificar para X-Fordered-For también o algo así?
Pure.Krome
77
@ Pure.Krome: sí, podría ser. Al recuperar la IP del cliente, utilizamos una función de ayuda que comprueba tanto la REMOTE_ADDRy HTTP_X_FORWARDED_FORvariables de servidor y desinfecta apropiadamente.
Jarrod Dixon
3
@BrettRobi, estoy bastante seguro de que tienen afinidad de servidor basada en la dirección IP de los usuarios. Por lo tanto, probablemente seguirán llegando al mismo servidor.
mmcdole
44
Para aquellos de ustedes que se preocupan y han leído hasta aquí en la secuencia de comentarios ... terminamos escribiendo nuestros propios redireccionamientos que borran la clave de caché del acelerador antes de redirigir. De esta forma, todos los redireccionamientos pasan a través del código para eliminar la clave y ninguno de ellos activa el atributo Acelerador.
SLoret
44
Si está buscando la versión API web de esto, consulte aquí: stackoverflow.com/questions/20817300/…
Papa Burgundy
68

Microsoft tiene una nueva extensión para IIS 7 llamada Dynamic IP Restrictions Extension para IIS 7.0 - Beta.

"Las restricciones dinámicas de IP para IIS 7.0 es un módulo que brinda protección contra la denegación de servicio y ataques de fuerza bruta en servidores web y sitios web. Dicha protección se proporciona mediante el bloqueo temporal de las direcciones IP de los clientes HTTP que realizan un número inusualmente elevado de solicitudes simultáneas o que hacen un gran número de solicitudes durante un período de tiempo reducido ". http://learn.iis.net/page.aspx/548/using-dynamic-ip-restrictions/

Ejemplo:

Si establece los criterios para bloquear después X requests in Y millisecondso X concurrent connections in Y millisecondsla dirección IP se bloqueará, Y millisecondsentonces las solicitudes se permitirán nuevamente.

notandy
fuente
1
¿Sabes si causó algún problema con los rastreadores como el robot de Google?
Helephant
1
Ahora se lanzó y se incluyó con IIS a partir de la versión 8 - iis.net/learn/get-started/whats-new-in-iis-8/…
Matthew Steeples
Me encantaría usar esto, pero NO te permite acelerar <location>. Es cada solicitud de la aplicación o ninguna.
Kasey Speakman
No parece ser útil si sus servidores web están detrás de un equilibrador de carga, ya que todo el tráfico parecerá provenir de la misma dirección IP. A menos que me estoy perdiendo algo obvio ...
Dscoduc
11

Utilizamos la técnica prestada de esta URL http://www.codeproject.com/KB/aspnet/10ASPNetPerformance.aspx , no para limitar, sino para la Denegación de servicio (DOS) de un pobre. Esto también está basado en caché y puede ser similar a lo que está haciendo. ¿Estás acelerando para prevenir ataques de DOS? Los enrutadores ciertamente pueden usarse para reducir DOS; ¿Crees que un enrutador podría manejar la aceleración que necesitas?

Rob Kraft
fuente
1
Eso es más o menos lo que ya estamos haciendo, pero está funcionando genial :)
Jarrod Dixon