Comet en Glassfish/Grizzly con Ajax o iframe

04:40AM jun 11, 2007 en categoria Java por Enrique Rodriguez Lasterra

Etiquetas:

La verdad es que el ejemplo de Jean-Francois me parecía demasiado complejo y me quede algo extrañado por el uso de un iframe como cliente del Comet... con todo lo que se habla de AJAX y las posibilidades de Comet, me parecía raro que "volvieramos" a usarlo.

La verdad, es que se nota bastante que esto del Comet es muy nuevo y no hay mucha información en internet, pero he podido encontrar cosas interesantes y razones por las cuales usar iframes o AJAX como cliente de Comet. Tengo que advertir, que si por algo no me destaco es por mis capacidades con javascript, pero conozco un poco y me han hablado bien siempre de Prototype, así que el objetivo era que el cliente AJAX fuera realizado con este framework. Empezemos:

Ya vimos lo sencilla que era la implementación de Comet en Grizzly, pero repito explicación sobre mi ejemplo que tiene menos funcionalidades.

1.- Registramos el comet bajo un nombre. Puede coincidir con una URL para dar sentido a las operaciones pero no es algo obligatorio. Esta operación se debe realizar antes que ninguna otra, por lo que su ubicación natural es el método init de un Servlet. Si además configurames el servlet para que se inicie al arrancar nuestra aplicación con la etiqueta <load-on-startup>0</load-on-startup> nos evitaremos problemas.

public void init (ServletConfig config) throws ServletException
{
super.init(config);
//comet = servlet context path pero no es obligatorio
contextPath = config.getServletContext()
.getContextPath() + "/subscription";
CometEngine cometEngine = CometEngine.getEngine();
context = cometEngine.register(contextPath);
context.setExpirationDelay(40 * 1000); //segundos de delay
}

2.-El Servlet solo va a realizar dos operaciones registrar al cliente en el Comet y enviar notificaciones a todos los clientes conectado. Para enviar la notificación se debe enviar el parametro notificar al Servlet. Para garantizar que un cliente no intenta conectarse dos veces, se crea una variable de sesión, conectado, que se comprueba que no exista antes de conectarse.

protected void doPost(HttpServletRequest request, 
HttpServletResponse response)
throws ServletException, IOException {
if (request.getParameter("notificar")==null
&& request.getSession().getAttribute("conectado")==null)
{//conecto el cliente al comet
response.setContentType("text/html");//muy importante
EjemploCometHandler handler = new EjemploCometHandler();
andler.attach(response.getWriter());
int handlerId = context.addCometHandler(handler);
request.getSession().setAttribute("conectado",
Integer.valueOf(handlerId));
response.getWriter().print("Conectado al Commet
");
response.getWriter().flush();
//no cerramos la conexion.. que de eso se trata
}else if (request.getParameter("notificar")!=null){
context.notify("Envio a todos los usuarios conectados al comet
");
}
return;
}

 

3.- El CometHandler implementado para el ejemplo se limita a enviar los mensajes de notificación y a informar de la desconexión

public class EjemploCometHandler implements CometHandler {

private PrintWriter printWriter=null;

public void attach(PrintWriter printWriter) {
this.printWriter = printWriter;
}

public void onEvent(CometEvent event) throws IOException {
printWriter.println(event.attachment());
printWriter.flush();
}

public void onInitialize(CometEvent event) throws IOException {
printWriter.println("onInitialize");
printWriter.flush();
}

public void onTerminate(CometEvent event) throws IOException {
printWriter.println("onTerminate");
printWriter.flush();
printWriter.close();
}

public void onInterrupt(CometEvent event) throws IOException {
printWriter.println("onInterrup");
printWriter.flush();
printWriter.close();
}
}

 

Con esto ya tenemos desarrollada la parte del servidor, ahora vamos al cliente. Como decía al principio hay dos posibilidades AJAX o iframe. Todo apunta a que la mejor opción es la del iFrame, por varias razones:

  1. Un navegador solo puede abrir dos conexiones HTTP desde una página/frame, por lo que si mantenemos una abierta para el Comet, solo nos queda una libre para realizar llamadas.
  2. El iframe funciona en todos los navegadores mientras que AJAX en InternetExplorer parece tener problemas con Comet, ya que en este navegador AJAX no es consciente de la información que va llegando hasta que se cierra la conexión :-(

Veamos el código del ejemplo del cliente con el iframe, por un lado tenemos el iframe que se conecta al comet. El boton realiza un llamada Ajax que actualiza el iframe de la misma página y la de todos los clientes conectados:

<html>
  <head><title>Ejemplo Comet iFrame</title>
      <script src="prototype.js" type="text/javascript"></script>
  </head>
  <body>
  <iframe src="subscription" name="comet" width="100%"></iframe>
  <script type="text/javascript" language="javascript">
    // <![CDATA[
    function notificarComet(){
        var url = 'subscription';
        new Ajax.Request( url, {
          method: 'get',
          parameters: 'notificar=true'
        });
    }
    // ]]>
  </script> 
  <input type="button" name="a" value="notificar"
onclick="notificarComet();"/>
  </body>
</html>

 

La única forma que he encontrado para hacer los mismo con Prototype y sabiendo que solo funciona en firefox es la siguente, tras hacer la petición y conectar al Comet, es necesario introducir el cliente en un bucle que vaya comprobando si se ha enviado información desde el Comet:

<html>
  <head><title>Ejemplo Comet Ajax</title>
  <script src="prototype.js" type="text/javascript"></script>
  <script type="text/javascript" language="javascript">
    // <![CDATA[
      function comet(){
        var url = 'subscription';
        var listener=null;
        new Ajax.Request(
          url,
          { onInteractive: function(xhr){
                if (!listener){
                  listener=new PeriodicalExecuter(
                    function(){
                       $('update').update(xhr.responseText);
                    },
                    1 /* check for changes every 1 second*/
                  );
                }
            }
          }
        );
      }

       function notificarComet(){
        var url = 'subscription';
        new Ajax.Request( url, {
          method: 'get',
          parameters: 'notificar=true&t='+new Date().getTime()
        });
        }
    // ]]>
  </script>
  </head>               
  <body onload="comet();">
  <div id="update"></div>
  <input type="button" name="a" value="notificar"
onclick="notificarComet();"/>
  </body>
</html>

No parece que Prototype se haya preparado para Comet. Sin embargo otro framework javascript muy conocido, Dojo, esta muy integrado con un servidor Comet, www.cometd.org, y con el no hay que recurrir a trucos como en Prototype, ya que cuenta con la operación dojo.io.bind() que me temo habrá que probar en próximos capítulos. Mientras os dejo el ejemplo para que lo probéis.

Comet in Glassfish/Grizzly with Ajax or iframe

Mi primera aplicacion Comet en Glassfish

01:43AM jun 07, 2007 en categoria Java por Enrique Rodriguez Lasterra

Etiquetas:

Grizzly es un framework de desarrollo de servidores Java. No vamos a desarrollar un servidor, ni mucho menos, pero vamos a intentar ver las posibilidades que nos ofrece Grizzly dentro de Glassfish.

Gracias a Grizzly nuevos proyectos nacidos recientemente como JRuby o Phobos cuentan con un rendimiento envidiable y una base solida y estable por su extenso uso en Glassfish como conector HTTP. Esta fue la idea inicial con la que nació, dar soporte HTTP al servidor de aplicaciones de Sun, por entonces el Sun Aplication Server 8.1, para que este pudiera funcionar de forma independiente sin servidor Web.

Grizzly, según sus autores, consigue excelentes rendimientos gracias a que usa la "nueva" librería de entrada/salida introducida en la JDK1.4: NIO. Esta librería además aportar facilidades para desarrollar servidores no bloqueantes (asincronos) lo cual puede resultar muy interesante como veremos a continuación.

Además de su rendimiento Grizzly es tremendamente "programable". En el Weblog de Jean-Francois Arcand podéis encontrar muchísima información pero de lo que yo he leído, destaco cosas tan sorprendentes como la capacidad de desarrollar un control de cuotas de ancho de banda o acceder a la capa de conexiones HTTP del servidor para poder realizar cosas tan novedosas como las aplicaciones Comet.

Estas aplicaciones consisten en enviar datos desde el servidor al navegador sin necesidad de que se realicen peticiones por parte del cliente. Para que este envió push sea posible, el servidor debe registrar y mantener una conexión abierta con los usuarios que se subscriben a la información que será enviada. El ejemplo más claro es el de un chat web como el de GMail, donde el envío de un usuario se ve reflejado en la pantalla de todos los usuarios conectados al chat.

El primer paso para realizar una aplicación comet es configurar el listener-http. En la instalación por defecto de Glassfish V2 existen dos listener el http y el https, para el ejemplo hay que añadir la propiedad cometSupport al http-listener-1 con valor true y eliminar la propiedad proxiedProtocols, que parece que no se lleva muy bien con el soporte de Comet. (Lo estoy consultando)

Configuración Listener HTTP ara aplicaciones Comet

Una vez nuestro servidor esta preparado vamos a utilizar un ejemplo de chat desarrollado por Jean-Francoise y comentado en su blog. El servlet de una aplicación comet como el del ejemplo, debe instanciarse y ejecutar su método init al arrancar la aplicación, por lo que debemos añadir a su configuración en el web.xml la etiqueta <load-on-startup>0</load-on-startup>. De esta forma obtenemos el contexto o ruta (CometContext) donde se van a esperar las conexiones antes de que la aplicación este funcionando. Podemos decir que el CometContext es el enlace del API de Comet con Grizzly.

super.init(config);
contextPath = config.getServletContext().getContextPath()+"/chat";
CometEngine cometEngine = CometEngine.getEngine();
CometContext context = cometEngine.register(contextPath);
context.setExpirationDelay(20 * 1000); 

Una vez el servlet se ha iniciado y por tanto tenemos el contexto comet, los usuarios pueden acceder al chat. Cuando esto ocurre el ejemplo registra el nombre del usuario en la sessión y redirecciona a la página chat.jsp, compuesta por dos iframes.

El primero de ellos es la pantalla donde se muestran los mensajes de los usuarios del chat. Se puede decir que este iframe es el que muestra el uso de la tecnología comet. Para ello llama al servlet del ejemplo enviando el parametro openchat. En el servlet al llegar esta petición se crea un CometHandler. Este interfaz que debemos implementar es el enlace entre nuestra aplicación y Grizzly. Se encarga de registrar el ciclo de vida y los eventos/notificaciones generados en el contexto. Además tiene la posibilidad de adjuntar recursos de la aplicación para hacer uso de ellos al producirse estos eventos. El caso más habitual es adjuntar los streams de salida (HTTPServletResponse ó PrintWriter) para enviar los datos al cliente.
Una vez hemos creado el handler, debemos añadirlo al contexto vinculando así al usuario. mas concretamente a su conexión HTTP.

CometRequestHandler handler = new CometRequestHandler();
handler.clientIP = request.getRemoteAddr();
handler.attach(response.getWriter());
cometContext.addCometHandler(handler); 

Todos los eventos que se produzcan en el contexto comet a partir de ahora mediante las llamadas a la operación CometContext.notify() serán enviados como eventos al handler del usuario (Típica implementación del patrón Observer). Estos eventos se generan desde el segundo iframe, que se encarga de enviar los mensajes del chat. El servlet en este caso, traduce el mensajes de los usuarios en un evento lanzado a todos los usuarios vinculados al contexto mediante la ya nombrada operación notify y por tanto generando una llamada a la operación onEvent del interfaz CometHandler.

cometContext.notify("[ " + username + " ]  " + message + " "); 

La operación onEvent del Handler recibe como parámetro el último interfaz de esta pequeña API, el CometEvent. Bajo este interfaz se almacena toda la información del CometContext y el objeto adjunto a la notificación, en el ejemplo, el mensaje enviado por algún usuario. Como el handler ya tiene los recursos necesarios para enviar los datos al usuarios, tan solo debe redireccionar la información adjunta al evento hasta el cliente.

printWriter.println(event.attachment());
printWriter.flush();

Y esto es todo, creo que merece la pena descargar el ejemplo y probarlo. Hace algunas cosas más de las que comento en el post, pero la basé es asi de simple: tres interfaces implementando un patron Observer. La verdad es que se me ocurren un par de ejemplos de donde usar esta técnologia... veremos si los pongo en práctica.

My first Comet Application in Glassfish