miércoles, 23 de octubre de 2013

Patrón Post/Redirect/Get (PRG)

Este patrón indica una forma de diseñar una aplicación web para evitar el problema del doble envío de un formulario al recargar una página. Este problema también se conoce como doble POST y es la causa de que, a veces, al recargar una página el navegador muestre el siguiente mensaje:

Mostrar una pantalla como esta, ante una acción que el usuario considera normal, es un problema grave de usabilidad. Ademas de la presentación de este mensaje, el doble POST puede duplicar en el servidor una acción que ya se había realizado, como una compra, una trasferencia, el envío de un mensaje, etc.

Vamos a ver en detalle porqué se produce este problema y la solución que propone el patrón Post/Redirect/Get

El Problema

Vamos a suponer las siguientes acciones:

  • El usuario rellena un formulario para hacer una compra.
  • Pulsa el botón 'Enviar' para realizar la compra.
  • El servidor recibe la petición, hace el cargo en la cuenta del usuario y formaliza el pedido guardandolo en la base de datos.
  • Se muestra al usuario una página de 'Compra Realizada' con un resumen de los datos de de la compra.

Este proceso es muy habitual en las aplicaciones web. La secuencia puede darse en muchos casos: una página de compra, una página para enviar un mensaje a un foro, una página para dar de alta a un usuario, etc.

La comunicación entre cliente y servidor puede verse en la siguiente imagen (sacada de Wikipedia):

En general, cuando una petición va a modificar algo en el servidor se envía mediante un mensaje POST de HTTP, como vemos en el dibujo. El problema es que si el usuario decide recargar la página (quizá no se ha mostrado bien el resumen de la compra), el navegador tiene que volver a enviar la petición que devuelve esa página. Como esa petición es un POST, implica modificar algo en el servidor, en este caso realizar de nuevo la compra. Por eso los navegadores muestran el mensaje de aviso.

Naturalmente la aplicación en el servidor puede comprobar si esta petición ya se ha realizado y evitar la duplicación, pero esto no siempre se hace. El mensaje del navegador aparecería en cualquier caso, empeorando la experiencia de usuario.

Resumiendo: Una recarga de la página puede causar la duplicación de una acción en el servidor.

La Solución

La solución que propone el patrón Post/Redirect/Get es evitar que la página que se presenta al usuario despues de la acción sea el resultado directo del POST. Se devuelve un codigo de redirección que obliga al navegador a pedir la página mediante un nuevo GET. La secuencia sería la siguiente:

En este caso, despues del POST el servidor no envía la página de 'Compra Realizada', envía una respuesta indicando una redirección, mediante las siguientes cabeceras:

HTTP/1.1 303 See Other
Location: http://comprando.com/compraRealizada

Al recibir esta respuesta el navegador automáticamente hace una nueva petición (GET) a la dirección que se indica en cabecera Location. Como respuesta a este GET, el servidor devuelve la página de 'Compra Realizada'.

Ahora, si el usuario decide recargar la página, el navegador vuelve a enviar la petición GET y la carga de nuevo. Ya no se envía el POST original y no hay peligro de modificación de datos en el servidor.

Precisamente la redirección 303 se creó para este propósito. En la especificación HTTP 1.1 podemos leer:

303 "See Other" The response to the request can be found under a different URI and SHOULD be retrieved using a GET method on that resource. This method exists primarily to allow the output of a POST-activated script to redirect the user agent to a selected resource. The new URI is not a substitute reference for the originally requested resource. The 303 response MUST NOT be cached, but the response to the second (redirected) request might be cacheable.

¿Que pasa si pulsamos 'Back' en el navegador?

Cuando estamos en la página de 'Compra Realizada', si pulsamos 'atras' en el navegador, iríamos a la página original del formulario, que normalmente se leerá de la cache del navegador. Esta acción no envía de nuevo el formulario.

Si despues pulsamos la flecha "adelante" en el navegador iremos de nuevo a la página de resultado de la compra, pero esta acción no envía de nuevo el formulario.

Por supuesto, si cuando estamos en la página del formulario pulsamos el botón de 'Envíar' sí se enviará de nuevo. Este caso sólo puede protegerse en la parte del servidor. Podríamos por ejemplo, crear una cookie con información de la transacción y descartarla si llega de nuevo en una rango temporal de segundos.

No hay comentarios:

Publicar un comentario