Tenía curiosidad al respecto de esa nueva tecnología que llaman COMET y que básicamente consiste en mantener una comunicación constante entre el cliente y el servidor web. Para explorar un poco, me planteé un proyecto sencillo: un chat asíncrono web.
La estructura es la siguiente:
- Una página html con el código mínimo imprescindible para presentar los mensajes en un área de texto y poder enviar mensajes al chat.
- Un código javascript que se encargue de mantener un hilo de escucha abierto con el servidor (para recibir los mensajes de la gente) y que mande un mensaje al servidor cada vez que escribamos.
- Un código PHP que reciba y envíe los mensajes de los usuarios.
Los principales problemas de esta aproximación son los siguientes:
- ¿Cómo mantener un hilo de escucha con el servidor?. Hasta ahora, lo que sabía hacer es hacer peticiones XMLHttpRequest desde el cliente hasta el servidor, pero no veía la forma en la que el servidor podría distribuir los mensajes a los clientes. Si, podemos recargar la página cada cierto tiempo, pero eso no mola nada, es tecnología del siglo pasado.
La luz me la dió esta página, que discutía varias maneras de implementar streaming sobre HTTP. Una de ellas (la mejor de todas) consiste en hacer desde el cliente una petición XMLHttpRequest y mantenerla abierta durante varios segundos. ¿Cómo?¿Mande? Si, haces una petición a un script php que mediante un bucle while y un uso adecuado de la función time() se ejecute, digamos, unos 30 segundos y en cada iteración del bucle compruebe si hay mensajes nuevos. Evidentemente en cliente tienes que tener una función que se ejecute cada cierto tiempo para leer los mensajes que vayan llegando (típico polling). - En el servidor, ¿Cómo compartir mensajes entre distintas sesiones de usuarios?. No podemos hacerlo a traves del típico $_SESSION, porque cada usuario sólo accede a los datos de su propia sesión. No deberiamos hacerlo a través de un fichero porque no es nada escalable y muy lento, y lo más seguro es que el fichero acabara corrompiendose si no controlamos bien los accesos. ¿Quizás con una base de datos?, puede valer, pero me seguía pareciendo lento y un poco aburrido. Más adelante os mostrará como lo he hecho yo (the cool way).
Veamos un poco de código. Lo siguiente sería el html del chat, muy sencillo y que no necesita explicación (creo).
1 <htmlgt; 2 <head> 3 <scriptlanguage="javascript"src="chat.js"></script> 4 <title>Chat con eventos compartidos en servidor COMET</title> 5 </head> 6 <bodyonload="javascript: main()"> 7 <textareaid="chatPanel"rows="10"cols="90"disabled="true"></textarea> 8 <inputtype="text"id="nick"disabled="true"/> 9 <formonsubmit="javascript:return send();"> 10 <inputtype="text"id="chatBox"value=""style="width:90%"/> 11 </form> 12 </body> 13 </html>
Como veis, lo único que hacemos es traernos un archivo js (chat.js) y definirnos un par de campos para presentar datos. El campo 'chatBox' llama a una función llamada 'send()' para enviar al servidor lo que se escriba en él.
Una cosa más, en el onload llamamos a una función 'main()' que veremos a continuación.
1 var nick = ""; 2 var x; 3 var x_Send; 4 var intervalo; 5 var last_id=0; 6 7 function main(){ 8 while(nick == "") nick = prompt("Nick:"); 9 document.getElementById("nick").value=nick; 10 openStream(); 11 } 12 function openStream(){ 13 x = new XMLHttpRequest() 14 x.open("GET", "chatServer.php?accion=listen", true); 15 x.send(null); 16 intervalo = setInterval(poll, 1000); 17 } 18 function closeStream(){ 19 clearInterval( intervalo ); 20 x.abort(); 21 openStream(); 22 } 23 function poll(){ 24 var r = new Array(); 25 r= x.responseText.split("\n"); 26 for(var f=0; f< r.length; f++){ 27 if(r[f]=="") continue; 28 var evento = eval("("+ r[f] + ")" ); 29 if(evento.id> last_id){ 30 last_id = evento.id; 31 echo(evento.nick+": "+evento.msg+"\n"); 32 } 33 } 34 if(x.readyState == 4) closeStream(); 35 } 36 function echo( str ){ 37 document.getElementById("chatPanel").value += str; 38 document.getElementById("chatPanel").scrollTop = document.getElementById("chatPanel").scrollHeight; 39 } 40 function send(){ 41 x_Send = new XMLHttpRequest(); 42 var msg = document.getElementById("chatBox").value; 43 x_Send.open("GET", "chatServer.php?accion=send&msg="+msg+"&who="+nick, true); 44 x_Send.send(null); 45 document.getElementById("chatBox").value = ""; 46 return false; 47 }
Aquí si que hay jugo. Vamos función por función.
main(): Nos pide un 'nick' y llama a openStream().
openStream(): Abre una petición XMLHttpRequest al archivo chatServer.php con el código de acción 'listen' que como veremos posteriormente, indica al php que se quede en espera y nos vaya enviando mensajes. ¿Cuando leemos esos mensajes? pues para ello nos definimos un intervalo cada segundo (podría ser menos si necesitamos mñas interactividad) para que llame a la función 'poll()'.
closeStream(): Cierra la petición XMLHttpRequest y vuelve a crear otra llamando a openStream.
poll(): Esta función recoge el contenido actual del responseText de la petición de escucha, la divide en lineas y si corresponde, imprime la línea por pantalla con 'echo()'.
echo(): Escribe lo que le mandes como parametro en el textarea y mueve el scroll.
send(): Envía el texto al servidor mediante un XMLHttpRequest normalito.
Hasta aquí la parte del cliente. Hemos visto como implementar mediante javascript hilos de escucha con el servidor. ¿Utilidad? Para todas aquellas aplicaciones en la que la colaboración entre usuarios es fundamental o crítica. Se me ocurre, a bote pronto, edición multiusuario de documentos online, control de concurrencia, etc...
Mientras escribo la segunda parte de este mini tutorial, podeis entreteneros probando el chat en la siguiente url: chat web. Eso si, si solo estás tú conectado, no vas a ver nada espectacular. Lo suyo es probarlo con muchos usuarios.
Actualización: Ya tengo lista la segunda parte.
Artículo escrito por Francisco Javier Nieto Borrallo.