Вы находитесь на странице: 1из 10

Envíos duplicados

En este apéndice complementario del Capítulo 6 aprenderemos a


prevenir el problema del envío duplicado, y explicaremos cómo enviar
mensajes y errores desde la acción para validaciones complejas.

▼ Envíos duplicados ........................2 ▼ Actividades .................................10


Cancelación de la acción ...............7
Errores y mensajes .......................8

A#_Envios duplicados.indd 1 08/09/2014 10:05


2 APÉNDICE A. ENVÍOS DUPLICADOS

Envíos duplicados
En la programación web, el tiempo de respuesta puede demorar bastan-
te por el tráfico, congestiones, servidores saturados y demás causas posi-
bles. Esto trae a colación el problema del envío duplicado de un pedido.
Este problema ocurre cuando el usuario envía el pedido y, antes de que el
servidor pueda responderle, vuelve a enviar el pedido. Típicamente, esto
ocurre si hace doble clic en el botón para enviar un formulario, o si, luego
de enviarlo y no obtener respuesta por unos segundos, vuelve a presionar
el botón pensando que no había sido enviado en su primer intento. Esta
situación puede ser inofensiva en muchos casos, pero volviendo al ya
clásico ejemplo de la transferencia bancaria, el usuario estará transfirien-
do el doble (o más) de la suma que realmente quiere transferir.
Del lado del cliente, podemos evitar esto usando JavaScript para deshabili-
tar el botón de envío una vez que fue presionado por primera vez. Es de uti-
lidad, pero no todos los navegadores soportan JavaScript, o el usuario podría
tener JavaScript deshabilitado en su navegador. Debemos prevenir esta situa-
ción en el servidor, y Struts nos brinda la funcionalidad necesaria.
La idea es grabar una marca en la sesión cuando mostramos la pá-
gina que queremos proteger; luego, la acción que la procesa busca esa
marca. Si está, la quita y sigue procesando. Bajo este protocolo, solo la
primera acción que procese la página podrá procesar los datos. Los pe-
didos subsiguientes no encontrarán la marca en la sesión.

BOTONES DE CANCELACIÓN

Es una buena práctica poner a disposición de los usuarios botones de cancelación en


todos los formularios de una aplicación web. Si no existe tal botón, el usuario se verá
obligado a usar los comandos del navegador para retroceder páginas, y esto puede
dejar inconsistente el estado del servidor.

www.redusers.com

A#_Envios duplicados.indd 2 08/09/2014 10:05


DESARROLLO WEB CON JAVA DESDE CERO 3

Un ejemplo puntual para ilustrar con código: definimos dos acciones


(el ActionForm no tiene mucha importancia en este ejemplo, puede ser un
LazyValidatorForm), una muestra el formulario y la otra lo procesa.

<action path=”/verFormulario”
type=”capitulo6.VerFormularioAction”
name=”otroForm” scope=”request”>
<forward name=”ok” path=”/jsp/capitulo6/form.jsp” />
</action>

<action path=”/procesarFormulario”
type=”capitulo6.ProcesarFormularioAction”
name=”otroForm” scope=”request”>
<forward name=”ok” path=”/jsp/form_ok.jsp” />
<forward name=”mal” path=”/jsp/form_mal.jsp” />
</action>

En general usaríamos un ForwardAction o una definición con atributo


forward para el primer caso, ya que todo lo que tiene que hacer es redirigir
hacia el JSP. Pero en este caso no alcanza con eso, pues hay funcionali-
dad. Entonces, tenemos que guardar la marca en la sesión.

ERRORES Y MENSAJES

Los mensajes y errores son ambos tratados como objetos ActionMessage. Struts los
guarda en distintos lugares del pedido y, luego, con distintos tags podremos acceder
a ellos. Por regla general, un error es algo que impide seguir con la navegación,
y, si hay errores en el pedido, deberíamos tomar alguna medida extraordinaria (como
volver atrás y permitir al usuario corregir el error).

www.redusers.com

A#_Envios duplicados.indd 3 08/09/2014 10:05


4 APÉNDICE A. ENVÍOS DUPLICADOS

Creamos la clase VerFormularioAction:

package capitulo6;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;

public class VerFormularioAction extends Action {

public ActionForward execute(ActionMapping map, ActionForm form,


HttpServletRequest req, HttpServletResponse res) throws Exception {

/* Guardamos la marca en la sesión para proteger la página */


saveToken(req);

// Mostramos el JSP
return map.findForward(“ok”);
}
}

DOBLE ESFUERZO CONTRA ENVÍOS DUPLICADOS

Es bueno hacer una revisión del lado del cliente y del servidor contra envíos
duplicados implementando funciones JavaScript para deshabilitar el botón.

www.redusers.com

A#_Envios duplicados.indd 4 08/09/2014 10:05


DESARROLLO WEB CON JAVA DESDE CERO 5

La acción que procesa el formulario, además, verifica la marca en la


sesión. Creamos otra clase, ProcesarFormularioAction:

package capitulo6;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;

public class ProcesarFormularioAction extends Action {

public ActionForward execute(ActionMapping map, ActionForm form,


HttpServletRequest req, HttpServletResponse res) throws Exception {

// ¿La marca es válida?


if (isTokenValid(req)) {
// Invalidamos la marca
resetToken(req);

// Procesar formulario
...

return map.findForward(“ok”);
}
else {
/* Marca inválida, devolvemos página de error */
return map.findForward(“mal”);
}
}
}

www.redusers.com

A#_Envios duplicados.indd 5 08/09/2014 10:05


6 APÉNDICE A. ENVÍOS DUPLICADOS

La página JSP no necesita ninguna modificación para soportar esta fun-


cionalidad. Sí es necesario que use el tag <html:form> de Struts para crear
el formulario. Struts automáticamente agregará un campo oculto con la
marca guardada para luego poder ser revisada por la acción.
Los métodos de la clase Action a destacar son:

void saveToken(HttpServletRequest request)

Guarda la marca en la sesión del usuario.

void resetToken(HttpServletRequest request)

Borra la marca de la sesión.

boolean isTokenValid(HttpServletRequest request)

Devuelve true si la marca está en la sesión y coincide con la que fue


enviada por el formulario.

boolean isTokenValid(HttpServletRequest request,


boolean reset)

Ídem método anterior.


El segundo parámetro indica que la marca debe borrarse instantánea-
mente luego de ser consultada. Este método es útil para evitar que dos
pedidos accedan concurrentemente a una acción y ambos vean la marca
en la sesión antes de poder borrarla.

www.redusers.com

A#_Envios duplicados.indd 6 08/09/2014 10:05


DESARROLLO WEB CON JAVA DESDE CERO 7

Cancelación de la acción
En ocasiones, es conveniente dar al usuario la opción de cancelar la acción
que está realizando. En Struts, esto es muy fácil de desarrollar. En la vista,
generamos un botón de cancelación usando el tag <html:cancel> (explicado
en el Capítulo 7). Al presionarse este botón, se envía el formulario sin vali-
darse. Esto es lógico, ya que, si el usuario quiere cancelar la acción, no va-
mos a obligarlo a completar todos los campos con los formatos necesarios.
Nuestra acción, sin embargo, seguramente dependerá de datos validados,
por lo que tendremos que ser precavidos para manejar este tipo de casos.
En la clase Action tenemos el método:

boolean isCancelled(HttpServletRequest request)

Dicho método devuelve ‘verdadero’ si se llegó a esta acción por me-


dio de un botón de cancelación. Un esqueleto de Action que maneje can-
celaciones es el siguiente:

public ActionForward execute(ActionMapping map, ActionForm form,


HttpServletRequest req, HttpServletResponse res) throws Exception {

if (isCancelled(req)) {
// Acción cancelada
return map.findForward(“cancelado”);
}
else {
// Ejecutamos la acción
...
}
}

www.redusers.com

A#_Envios duplicados.indd 7 08/09/2014 10:05


8 APÉNDICE A. ENVÍOS DUPLICADOS

Errores y mensajes
Desde nuestra acción podemos enviar mensajes al cliente. Estos mensa-
jes pueden ser advertencias, errores de validación complejos, etcétera.
Los métodos de la clase Action son:

void addErrors(HttpServletRequest request, ActionMessages errors)


void addMessages(HttpServletRequest request, ActionMessages messages)

Agrega los mensajes/errores al pedido.

void saveErrors(HttpServletRequest request, ActionMessages errors)


void saveMessages(HttpServletRequest request, ActionMessages messages)

Guarda los mensajes/errores en el pedido y reemplaza lo que haya.

void saveErrors(HttpSession session, ActionMessages errors)


void saveMessages(HttpSession session, ActionMessages messages)

Guarda los mensajes/errores en la sesión y reemplaza lo que haya.

ActionMessages getErrors(HttpServletRequest request)


ActionMessages getMessages(HttpServletRequest request)

Devuelve los mensajes/errores que hay guardados en el pedido.


Este método puede servir tanto para usar al final de la acción y determi-
nar la página adonde redirigir el pedido, como para ver si alguna acción
anterior a esta ha guardado mensajes.
Veamos un ejemplo de uso:

www.redusers.com

A#_Envios duplicados.indd 8 08/09/2014 10:05


DESARROLLO WEB CON JAVA DESDE CERO 9

public ActionForward execute(ActionMapping map, ActionForm form,


HttpServletRequest req, HttpServletResponse res) throws Exception {

ActionErrors errores = new ActionErrors();

String param1 = form.getParam1();


String param2 = form.getParam2();

// Validamos el primer parámetro


if (param1 invalido) {
ActionMessage errParam1 = new ActionMessage(“param1.invalido”);
errores.add(“param1”, errParam1);
}

// Validamos el segundo parámetro


if (param2 invalido) {
ActionMessage errParam2 = new ActionMessage(“param2.invalido”);
errores.add(“param2”, errParam2);
}

// Vemos si tenemos algún error


if (errores.isEmpty()) {
// Realizamos operaciones en el modelo
...

// Devolvemos la vista
return map.findForward(“ok”);
}
else {
/* Hay errores, devolvemos la vista a la página
para que el usuario los corrija */
return map.getInputForward();
}
}

getInputForward es un método de la clase ActionMapping que devuelve


un ActionForward con el valor del atributo input de la acción.

www.redusers.com

A#_Envios duplicados.indd 9 08/09/2014 10:05


10 APÉNDICE A. ENVÍOS DUPLICADOS

Actividades
TEST DE AUTOEVALUACIÓN

1 ¿Cómo podemos evitar el problema de los envíos duplicados mediante el uso


de JavaScript?

2 ¿Por qué el botón de cancelar envía el formulario sin validación?

PROFESOR EN LÍNEA

Si tiene alguna consulta técnica relacionada con el contenido, puede contactarse


con nuestros expertos: profesor@redusers.com.

www.redusers.com

A#_Envios duplicados.indd 10 08/09/2014 10:05