HttpModule: AntiSpamModule

Nunca me gustan las definiciones de conceptos tan técnicos como es el caso del HttpModule. Primero porque no sé definir bien las cosas, y segundo porque aunque lo supiera definir bien, lo que más aclara sigue siendo un buen ejemplo.

Para que nos hagamos a una idea, con los HttpModule's vamos a poder manejar eventos que  suceden antes que el Pre_Init de la página. Esperando que no se me olvide ninguno, y sin entrar a comentar qué sucede en cada uno de ellos, este es el orden en que se ejecutan dicho eventos:
  1. BeginRequest
  2. AuthenticateRequest
  3. PostAuthenticateRequest
  4. PostAuthorizeRequest
  5. ResolveRequestCache
  6. PostResolveRequestCache
  7. PostMapRequestHandler
  8. AcquireRequestState
  9. PostAcquireRequestState
  10. PreRequestHandlerExecute
Bien utilizados los HttpModules son muy útiles.

Todo muy bonito y todo muy bien... pero hasta el momento he dicho todo pero no he dicho nada. Vamos con un ejemplo, pero además un ejemplo muy útil: el AntiSpammer.

Desde hace tiempo vengo teniendo problemas en los foros de algunas de mis Webs. Algunos robots Spammers escribían diariamente "basura". Analizándolo a grandes rasgos, lo que hacen estos robots es hacer peticiones POST a una url definida con unos campos definidos. Hasta ahora había probado varios métodos,
como cambiar el nombre de los Textbox (y por tanto cambiaba el identificador de los campos o cambiar el nombre de la página (con lo que el robot ya no lo enviaba al sitio correcto).

Ninguno de esos métodos me funcionaba, pues los robots eran inteligentes... o simplemente  cogían los "Id" de todos los "input type text" y mandaban la basura con su nombre. Bueno, no lo sé, cuando algún día decida hacerme spammer lo analizaré mejor .

La cuestión es que lo único que realmente me ha funcionado ha sido o poner un Captcha (los odio) o permitir únicamente la inserción a usuarios registrados... pero ninguna de las dos soluciones me gusta.

Así que con la "inspiración" de resolver ese problema, he hecho este HttpModule.

Hacer un HttpModule es mucho más sencillo de lo que se piensa. Lo primero es crear una clase con la Interfaz "IHttpModule", la cual te obliga a tener dos métodos: "Init" y "Dispose".

En el método "Init" se recoge un parámetro del tipo "HttpApplication", desde el cual se tiene acceso a todas las variables típicas que se tienen desde una Página .aspx cualquiera. En nuesgtro ejemplo nos va a interesar recoger el Request, el Response.

Vayamos con el código, y pasamos a explicar qué vamos a hacer para tratar de evitar el Spam:

AntiSpamModule.cs
using System;
using System.Web;

namespace Subgurim.Tools
{
    public class AntiSpamModule : IHttpModule
    {
        public void Init(HttpApplication app)
        {
            app.BeginRequest += new EventHandler(AntiSpamFilter);
        }

        private void AntiSpamFilter(object sender, EventArgs e)
        {
            HttpResponse response = ((HttpApplication)sender).Response;
            HttpRequest request = ((HttpApplication)sender).Request;

            // 1.- Me me están mandando datos mediante POST
            // 2.- La llamada no proviene de ninguna otra página
            // 3.- La llamada proviene de otra página, pero que no está dentro de mi dominio
            if ((request.Form.Count > 0) &&
                    ((request.UrlReferrer == null) ||
                    (!request.UrlReferrer.Authority.Equals(request.Url.Authority, StringComparison.InvariantCultureIgnoreCase))))
            {
                try
                {
                    response.End();
                }
                catch (System.Threading.ThreadAbortException ex)
                {
                    // No hacer nada
                }
            }
        }

        public void Dispose()
        {
        }
    }
}

En el método Init vamos a indicar que cuando se lance el evento "BeginRequest" (el primero de todos) se llame al método AntiSpamFilter.

Dentro del AntiSpamFilter recogemos (vemos que es muy sencillo) el Response y el Request, y aquí es donde trataremos de localizar a los robots Spammers con tres sencillas reglas:
  1. Me me están mandando datos mediante POST
  2. La llamada no proviene de ninguna otra página
  3. La llamada proviene de otra página, pero que no está dentro de mi dominio
En caso de ser así, haremos, sin contemplaciones un Response.End(), con lo que paramos el ciclo de vida, y de ahí no pasa nadie. Nos sólo evitamos que nos Spammeen, sino que le estamos ahorrando a nuestro servidor un montón de cosas.

Ponemos un try - catch para manejar el ThreadAbortException, que se lanza siempre que se hace un Response.End.

En lugar de un Response.End, podría ser buena idea lanzar una HttpException 404, engañando al robot haciéndole creer que la página no existe.

Ya tenemos programado nuestro HttpModule... como véis es muy sencillo y no tiene otra historia que un clase implementando una interfaz.

Ahora nos queda configurar nuestra aplicación para que, en cada llamada, ejecute ese HttpModule. Como siempre, lo haremos desde nuestro web.config. Dentro del elemento System.Web debemos poner el elemento httpModules:

    <system.web>
        <httpModules>
            <add name="AntiSpamModule" type="Subgurim.Tools.AntiSpamModule"/>
        </httpModules>
    </system.web>

Con "name" con el nombre de la clase y type con el namespace (si está dentro del directorio App_Code) o el nombre del assembly si lo tienes en otro proyecto.

OJO: esas tres reglas básicas no han sido testeadas al 100%. Debería funcionar sin problemas, pero no sé, por ejemplo, si los robots de los buscadores (como GoogleBot o YahooSlurp) utilizan o utilizarán llamadas POST con parámetros para indexar mejor los contenidos... vamos, que me lavo las manos de lo malo que pueda ocurriros (aunque repito que a mí me ha funcionado sin problemas).