Foros del Web » Programación para mayores de 30 ;) » Java »

Servlet de larga duración

Estas en el tema de Servlet de larga duración en el foro de Java en Foros del Web. Hola a todos, Os quería comentar un problema que tengo, el problema es que cuando me llega a un petición al servidor, tengo que hacer ...
  #1 (permalink)  
Antiguo 01/04/2011, 13:05
 
Fecha de Ingreso: junio-2009
Mensajes: 65
Antigüedad: 15 años, 6 meses
Puntos: 0
Servlet de larga duración

Hola a todos,

Os quería comentar un problema que tengo, el problema es que cuando me llega a un petición al servidor, tengo que hacer una operación de larga duración para ello tengo que contestar al cliente que esa operación se esta realizando, por ejemplo la ejecución de un problema concreto, y la ejecución en si la tiene que hacer un hilo.

El problema que tengo es que el hilo no puede escribir en el response... y no se como lo puedo hacer, por que una vez que el cliente envía el contenido, el hilo no puedo volver a contestar aunque le envie el reponse.

He estado leyendo y he visto algo de servlet de servicios, pero no se si se podrá arreglar mi problema con ello.

Muchas gracias a todos!
  #2 (permalink)  
Antiguo 01/04/2011, 22:33
 
Fecha de Ingreso: abril-2011
Mensajes: 14
Antigüedad: 13 años, 8 meses
Puntos: 0
Respuesta: Servlet de larga duración

Hola Moisesvs...

Bueno, hay un par de cosas a tener en cuenta:
  1. Si lo que necesitas es que quien te llama se quede esperando una respuesta, no utilices un hilo. Un hilo se utiliza cuando quien invoca no espera una respuesta inmediata, sino espera hacer un nuevo llamado para obtener la respuesta cuando el proceso "largo" sea terminado.
  2. Si la idea es que el proceso es muy largo, y no se debería quedar esperando (o hay riesgo de vencer la sesión o algo así) simplemente utiliza la técnica de enviar un token: al momento que te hacen la solicitud, generas el nuevo hilo con un "token" (puede ser un simple número entero, que tengas estático para que cada vez generes un nuevo valor sin importar la cantidad de llamados) y le contestas inmediatamente al usuario con el token. El token sirve para que vuelvan a llamar a preguntar "en qué va la cosa" enviándote el token como referencia, y, cuando el asunto ya esté listo, puedas efectivamente contestarle al usuario.

En el método de Token, la idea es que el hilo escriba sus resultados en una tabla en memoria (por ejemplo, un HashMap que relacione el Token con el resultado) cuando el resultado esté listo, y así el usuario remoto podrá seguir preguntando hasta obtener la respuesta.

Ejemplo:
Código:
    private static int token;

    private static HashMap<Integer,String> tablaResultados;

    private class TareaMuyLarga extends Thread{

        int tokenLocal;

        public TareaMuyLarga(int tokenLocal){
            this.tokenLocal = tokenLocal;
        }

        public void Run(){

        ... (Se genera "resultadoLocal" con el resultado)
        tablaResultados.put(new Integer(tokenLocal),resultadoLocal);
        }

    }
Faltaría que el servlet retornara la información como se considere que deba hacerse...

Si la idea es presentar información en una página web, es perfectamente válido efectuar llamados periódicos (por ejemplo, cada 5 segundos) para preguntar el estado: esto es justamente lo que hace Ajax, aunque "por debajo".

Espero que esta información te sea útil.
  #3 (permalink)  
Antiguo 03/04/2011, 08:02
 
Fecha de Ingreso: junio-2009
Mensajes: 65
Antigüedad: 15 años, 6 meses
Puntos: 0
Respuesta: Servlet de larga duración

Hola, muchas gracias por contestar y de una forma tan clara, me han servido de mucha ayuda.

Ahora me surge otra duda a la hora de programar, si lo hago por el método del token que me has comentado, me has comentado que lo mejor es preguntar por medio de Ajax, como va el proceso, pero es lógico preguntar cada 5 segundos al servidor... y por otro lado, las peticiones de Ajax como se realiza poniendo un sleep con Javascript en un bucle infinito, o bien colocando un temporizador que se active cada 5 segundos.

La idea que tengo es que se muestre algo como:

Realizando operación...

Operación realizada con éxito.

Una vez que esto haya sucedido o bien, haya dado error por alguna razón.

Muchas gracias! Espero tu respuesta.
  #4 (permalink)  
Antiguo 03/04/2011, 21:54
Avatar de dackiller  
Fecha de Ingreso: septiembre-2003
Ubicación: The Matrix
Mensajes: 341
Antigüedad: 21 años, 3 meses
Puntos: 4
Respuesta: Servlet de larga duración

Hola:

Es una solución válida, aunque no se cual es el tipo de tarea, pero estoy viendo a simple vista un problema de concurrencia.

Yo pienso que la solución vendría luego hacer un buen analisis, ya que estamos hablando de una aplicación web la cual pudiera tener varios usuarios realizando la misma tarea concurrentemente.

El punto es:

- Que sucede si la persona que corrió la tarea se canso de esperar y otros usuario hacen la petición de la misma tarea mientra esta corriendo ?

Si la tarea es unica para todos los usuarios vas a tener problemas al usar el metodo antes mencionado ya que el HashMap moriria en lo que termine la tarea y adíos al token y si sumamos que la session termino antes que la tarea, sería mucho peor.

En este caso te aconsejaria usar un ThreadLocal + Metodos sincronizados y así poder monitorear la tarea. Lo más sano seria crear un log desde que comienza hasta que la tarea termine bien sea en base de datos ó como lo prefieras. El ThreadLocal te va ayudar porque pudieses asignar como variable del Thread la session del usuario y asi identificar la tarea.

De todas formas, si usas Tomcat, Dale una leida a ServletsListeners, que aunque no se cuales sean tus requerimientos esto te pudiece ayudar.

Saludos.
__________________
--
NOTA: Si haz conseguido la solución a tu problema, por favor edita el titulo del tema colocando el prefijo [SOLUCIONADO], para que otros usuarios puedan encontrar soluciones más rápido.
  #5 (permalink)  
Antiguo 03/04/2011, 22:48
 
Fecha de Ingreso: abril-2011
Mensajes: 14
Antigüedad: 13 años, 8 meses
Puntos: 0
Respuesta: Servlet de larga duración

Hola a todos,

Para evitar problemas de concurrencia, ten cuidado en la forma de acceder a las variables que son accedidas de forma concurrente: esto es, el generador de Tokens (que puede ser una simple variable estática tipo AtomicInteger, por ejemplo) y utilizar una variable tipo HashMap (que es Thread-Safe), que relacione los tokens con el estado actual...

Este ejemplo lo acabo de publicar en Tomcat y funcionó sin problemas... espero sea claro.

Servlet:
Código:
package test;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.concurrent.atomic.AtomicInteger;

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

/**
 * Servlet implementation class SrvTarea
 */
public class SrvTarea extends HttpServlet {
	private static final long serialVersionUID = 1L;

	/**
	 * Variable de control de tokens
	 */
	private static AtomicInteger numToken = new AtomicInteger(0);
	
	/**
	 * Hashmap que relaciona el estado de la tarea con el token
	 */
	private static HashMap<Integer,String> estadoTarea = 
		new HashMap<Integer,String>(); 

	/**
	 * Función para poner el estado de tarea de un token
	 * @param token Token a utilizar
	 * @param estado Estado a actualizar
	 */
	public static void ponerEstadoTarea(int token, String estado){
		estadoTarea.put(new Integer(token), estado);
	}
	
	/**
	 * Borra una tarea terminada de la tabla de estados
	 * @param token Token de la tarea a borrar
	 */
	public static void borrarTareaTerminada(int token){
		estadoTarea.remove(new Integer(token));
	}
	
    /**
     * @see HttpServlet#HttpServlet()
     */
    public SrvTarea() {
    }

    private void procesar(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    	String token = request.getParameter("token");

    	// Si el token es distinto de nulo, asumo que se piden los datos de una tarea...
    	if (token!=null){
        	PrintWriter out = response.getWriter();
        	out.println("<html><head>");
    		//Solicita información de tarea...
    		String estado = estadoTarea.get(new Integer(token));
    		if (estado==null){
    			estado = "TAREA TERMINADA!";
    		} else {
    			out.print("<meta http-equiv=\"refresh\" content=\"3\">");
    		}
    		out.print("</head><body><strong>Estado actual token '" + token + "':" + estado);
    		out.print("</body></html>");
    		out.close();
    	} else {
    		// Supongo que inicia una nueva tarea
    		int tokenNuevo = numToken.addAndGet(1);

    		TareaLarga nuevaTarea = new TareaLarga(tokenNuevo);
    		nuevaTarea.start();
    		response.sendRedirect("SrvTarea?token=" + String.valueOf(tokenNuevo));
    	}
    }
    
	/**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		procesar(request, response);
	}

	/**
	 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		procesar(request, response);
	}

}
Página HTML para mostrar la tarea:
Código HTML:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>PRUEBA DE TAREA LARGA</title>
</head>
<script type="text/javascript" language="javascript">
function enviarTarea(){
	var iframe = document.getElementById("iframeTarea");	
	var boton = document.getElementById("botonIniciar")
	iframe.src = "SrvTarea";
	boton.disabled = "disabled";
}
</script>

<body>
<h1>PRUEBA DE TAREA</h1>
Presione el botón para iniciar la tarea:
<button id="botonIniciar" name="botonIniciar" value="Iniciar Tarea" onclick="javascript:enviarTarea()" >Iniciar Tarea</button><br />
<iframe id="iframeTarea" width="500px" height="300px"></iframe>
</body>
</html> 
Tarea "Larga" (Thread):
Código:
package test;

/**
 * Thread que simula una tarea "Larga"
 * @author Felipe Reyes Palacio
 */
public class TareaLarga extends Thread{
	
	/**
	 * Token utilizado por la tarea actual
	 */
	private int tokenLocal;

	/**
	 * Constructor
	 * @param tokenLocal Asignación del token local
	 */
	public TareaLarga(int tokenLocal) {
		super();
		this.tokenLocal = tokenLocal;
	}
	
	/**
	 * Tarea "Larga". Simulada por un Thread de 40 segundos, que actualiza estado cada 4 segundos.
	 */
	@Override
	public void run() {
		for(int i=1;i<=10;i++){
			// Actualiza el estado de la tarea actual
			SrvTarea.ponerEstadoTarea(tokenLocal, String.valueOf(i*10) + "%");
			
			//Simulación de tarea larga... 5 segundos x 10 = 50 segundos.
			try {
				Thread.sleep(4000);
			} catch (InterruptedException e) {
			}
		}
		
		// Borra la tarea (indicando que ya fue concluida)
		SrvTarea.borrarTareaTerminada(tokenLocal);
	}
	

}
No debería haber problemas de concurrencia. Lo único que vale la pena tener en cuenta es que, como la tabla de estados está en memoria, es vital asegurarse que si la tarea a ejecutar puede lanzar excepciones, la tabla se borre aún si falla la ejecución del thread (para que la tabla no se llene infinitamente de datos, cuando haya excepciones). Eso se maneja fácil con un bloque try-finally.

Exitos.
  #6 (permalink)  
Antiguo 04/04/2011, 03:53
 
Fecha de Ingreso: junio-2009
Mensajes: 65
Antigüedad: 15 años, 6 meses
Puntos: 0
Respuesta: Servlet de larga duración

Hola de nuevas, muchas gracias por las respuestas de los dos.

dackiller: No veo tanto un problema de concurrencia como tu lo ves, creo que ese problema ya lo tengo solucionado, y quizás la solución que propone epilefreyes sea válida, aún así pienso que en la HashMap si que podría existir ese tipo de problemas.

Por otro lado comentarte que el ServletListener según he estado viendo solamente puedo capturar lo siguiente eventos, y por lo tanto ponerme a escuchar estos eventos:

servlet context events (ServletContextListener):

servlet context attribute events (ServletContextAttributeListener):

session events (HttpSessionListener):

session attribute events (HttpSessionAttributeListener):

Quizás me equivoque pero de esa manera creo que no me servirá de mucho.

epilefreyes: Muchas gracias por tu código y por tu tiempo, creo que en la HashMap deberías de colocar un synchronized para evitar la concurrencia de otro procesos, quería preguntar acerca de AtomicInteger que has utilizado, he estado buscando y parece que a parte de la API hay poca documentación, que hace la clase exactamente, te da un ¿número aleatorio? o evita problemas de concurrencia sumándole 1 al contador.

Por otro lado quería comentarte que lo único que quiero mostrar al usuario es lo siguiente, cuando el usuario haga la petición mostrarle.

Se esta realizando su operación, espere unos segundos...

En la misma página más abajo mostrarle.

Se esta realizando su operación, espere unos segundos...

Operación realizada con éxito.

Lo de operación realizada con éxito he pensado que sea una capa que sea rellenada por medio de Ajax, y que yo sepa Ajax no realiza peticiones repetitivamente, si no que tienes que establecerselo tú por medio de Timer cada 5 segundos, por ejemplo he pensado en establecerlo con JQuery un Timer a Ajax para que me rellene cuando la operación halla terminado, mal o bien.

La petición de Ajax sería con un HTTPRequest a la dirección concatenada con el token, el problema que veo es cada cuánto es recomendable realizar la petición de Ajax... cada 5 segundos... 3...

Muchas gracias a ver si me podéis aclarar las dudas.
Saludos!

Etiquetas: larga, servlet
Atención: Estás leyendo un tema que no tiene actividad desde hace más de 6 MESES, te recomendamos abrir un Nuevo tema en lugar de responder al actual.
Respuesta




La zona horaria es GMT -6. Ahora son las 05:03.