viernes, 11 de octubre de 2013

Solución al doble envío de peticiones en aplicaciones web

Apache Tapestry
Las aplicaciones web pueden recibir información del usuario a través de formularios o en los parámetros de los enlaces. Los formularios o enlaces pueden desencadenar en la aplicación diversas acciones. Dada la naturaleza de las aplicaciones web en ciertas ocasiones la latencia de la red o el tiempo que toma procesar la petición un usuario puede tener la oportunidad de hacer clic varias veces en un botón o enlace también cuando ve que tarda mucho en obtener la respuesta o simplemente por su desconocimiento al estar acostumbrado a hacer doble clic al trabajar con otras aplicaciones o la interfaz de su sistema operativo. En ocasiones el problema puede producirse también haciendo un refrescar con el botón del navegador si no se hace un redirect después del envío del formulario (Tapestry nos evita esto ya que hace un redirect después de cada envío de un formulario o acción, lo que se conoce como redirect after post o post/redirect/get).

El caso del doble o N-clic puede ser un problema, imagina una aplicación que borra un registro de la base de datos o una aplicación que realiza el cobro de una tarjeta de crédito. Si el usuario tienen la oportunidad de hacer doble clic en el botón o enlace enviándo dos peticiones al servidor lo más probable es que solo una de ellas funcione, la segunda petición puede producir una excepción en el servidor o peor aún dependiendo de la acción un efecto indeseado. ¿Eres consciente y tienes en cuenta que puede pasar en tu aplicación si se da esta situación?

Una de las formas de evitar este problema en cierta medida es desactivando el botón o enlace una vez que se ha pulsado y el formulario se envía o se realiza la petición. Podemos hacer uso de un código javascript que en el momento de pulsar el botón o enlace impida que desencadene su acción por segunda vez, por ejemplo desactivándolo.

En Tapestry podemos usar un mixin para reutilizar este código en todos los sitios que necesitemos de una forma muy sencilla. En el contexto de Tapestry un mixin es un componente que no se usa de forma individual sino que se usa junto a otro componente y que añade cierta funcionalidad al componente asociado. En la aplicación de demostración JumpStart se puede ver el código del componente mixin ClickOnce que en ciertos escenarios puede evitar el problema del N-clic. Sin embargo, la solución del mixin ClickOnce es válido solo para peticiones no ajax ya que una vez que el botón o enlace es pulsado se desactiva completamente, en una petición ajax el botón debería volverse a activar una vez que la petición Ajax termina.

A continuación mostraré el código de un mixin que mejora el ClickOnce resolviendo el problema N-clic tanto para peticiones ajax como no ajax. Por una parte tendremos el código Java asociado al mixin.

Y el javascript como un módulo de RequireJS que produce la desactivación del enlace o botón en el cliente una vez que es pulsado y monitoriza las peticiones Ajax para bloquear una segunda petición al servidor.

Este código javascript podría ser aprovechado después de haberlo modificado ligeramente para ser usado con otro framework de aplicaciones.

Una vez desarrollado el mixin podemos usarlo en un botón o enlace de la siguiente forma, a partir de este momento dar solución a este problema nos cuesta 20 caracteres en cada enlace o botón, los necesarios para aplicar el mixin con el atributo t:mixins, pero no solo eso, más importante aún que el número de caracteres, aplicarlos no nos ofusca el código:

Esta solución funciona en el lado del cliente, con javascript desactivado o un usuario malicioso podría producir el doble envío del formularios de forma intencionada. Otra solución en el lado del servidor que se suele utilizar es un token de un solo uso en la que solo se procesa el formulario con el token correcto, el token se envía al cliente en un campo oculto y se guarda en sesión, una vez que el formulario se envía al servidor se comprueba que el token sea el guardado en sesión, esta solución realiza la comprobación en el servidor pero requiere del uso de la sesión para guardar el token. La solución mediante el token puede evitar el problema CSRF donde una petición maliciosa puede realizar una acción sin el consentimiento del usuario. En otra entrada explicaré como solucionar el problema CSRF con el framework Tapestry.

Esta es la sección de la aplicación del ejemplo funcionando donde puede probarse el mixin y ver la diferencia del comportamiento sin el mixin aplicado.

En este ejemplo también puede verse como con tapestry se pueden actualizar varias zonas (la zona de la cuenta) de una página de forma simultánea de una página web con una única petición Ajax.

Si te ha parecido interesante esta entrada puedes descargar el libro PlugIn Tapestry en el que explico más en detalle como desarrollar aplicaciones web en Tapestry y en el que descubrirás como resolver problemas comunes como este en las aplicaciones web de una forma tan buena como esta.

Si quieres probarlo en tu equipo lo puedes hacer de forma muy sencilla con los siguientes comandos y sin instalar nada. Si no dispones de git para clonar mi repositorio de GitHub puedes obtener el código fuente del repositorio en un archivo zip.

Referencia:
Libro PlugIn Tapestry
http://jumpstart.doublenegative.com.au/jumpstart/examples/javascript/creatingmixins1
http://stackoverflow.com/questions/17830171/mixins-in-tapestry5