053-060_PythonL12

16.11.2005

9:01

Uhr

Página

53

Python • DESARROLLO

La nueva tecnología web.

AJAX

AJAX es la palabra de moda, Google usa AJAX, Yahoo usa AJAX… todo el mundo quiere usar AJAX pero ¿lo usas tú? y más importante aún ¿qué demonios es AJAX? POR JOSÉ MARÍA RUIZ Y PEDRO ORANTES
Brave New World” (“Un Mundo Feliz”) es el nombre de la famosa novela de Aldous Huxley, en ella nos muestra un mundo distinto y aterrador pero que parecía, y parece, cada vez más cercano. Nosotros no tenemos una visión tan pesimista del mundo, pero es probable que ese título (que se podría traducir literalmente por «un nuevo y desafiante mundo») explique todo el revuelo que está levantando AJAX. El término fue acuñado por Jesse James Garrett en el artículo [1] de la tabla Referencias. Durante mucho tiempo las GUIs, las Interfaces Gráficas de Usuario, han dominado la informática. La gente que trabajaba en la Web siempre estaba intentando convencer a todo el mundo de que para la mayoría de los programas, un interfaz web bastaba. Pero los usuarios estaban acostumbrados a ciertas características, como el auto-completado

A

de campos o el arrastrar y soltar, que eran imposibles en la Web. Conforme avanzaba el tiempo numerosas empresas y personas proponían soluciones. La lista es interminable: JavaScript, Java Applets, ActiveX, Tcl, VBScript, Macromedia Flash… Pero todas fallaban de uno u otra manera. En el caso de Java, para ejecutar el Applet necesitabas tener instalado el Java Runtime Environment, y la mayoría de los usuarios no sabían ni qué era aquello que se le pedía. Lo mismo ocurría con Macromedia Flash. Lo peor era que cuando estaba solucionado el tema de la instalación del software adecuado, los desarrolladores creaban, y crean, páginas horribles llenas de cosas moviéndose que distraen e irritan. Se sentían impulsados a usar hasta la última capacidad de las nuevas herramientas y acababan generando monstruosidades.

Esta fase ya casi ha pasado y ahora se busca la sencillez, y en el momento justo surgió AJAX. Para más información ver url [2] de la tabla Referencias.

Los problemas con IE
Internet Explorer, a pesar de ser el primero que introdujo XMLHTTPRequest, es el que más problemas da en su uso. El código aquí mostrado ni siquiera funciona en IE debido a que en él se hace uso de un componente ActiveX para establecer la conexión. Existen numerosas técnicas para permitir la compatibilidad entre navegadores, pero debido a la extensión del artículo y a su complejidad no las hemos mostrado. El lector interesado en la compatibilidad puede estudiar el código de sistemas de código libre que implementan AJAX como puede ser Sarissa. Vea la referencia [3] en la tabla Recursos.

WWW.LINUX- MAGAZINE.ES

Número 12

53

053-060_PythonL12

16.11.2005

9:01

Uhr

Página

54

DESARROLLO • Python

¿Pero qué es AJAX?
Muy buena pregunta. Lo cierto es que AJAX ha estado delante de nuestras narices todo el tiempo, esperando a que alguna mente despierta lo redescubriese. El acrónimo «AJAX» se compone de las palabras «Asynchronous JavaScript and XML», término acuñado por Jesse James Garrett, y curiosamente su existencia se debe a una de esas famosas violaciones de los estándares que suele realizar Microsoft con sus productos. Allá por 1998, Microsoft introdujo dentro de sus productos una librería que le permitía hacer consultas usando el protocolo HTTP de manera autónoma y asíncrona. Cuando tu navegador accede a una página y esta contiene código Javascript, este código a su vez puede traer información de esa u otras páginas de manera independiente. Si además se hace que este código permanezca en ejecución respondiendo a eventos tenemos entre manos la

posibilidad de traer información al navegador sin recargar la página. Esto es útil para algunas tareas pero no demasiado, ya que a nuestro puzzle le faltan piezas. La primera pieza es la adopción de esta librería por casi todos los navegadores, por lo tanto el código pasa a ser de aplicación universal. Además resulta que podemos modificar el contenido de la página en tiempo real usando el denominado árbol DOM. Y por si fuese poco, cuando AJAX fue definido, los programadores comenzaron a usar protocolos XML para comunicarse con los servidores. ¿Qué quiere decir esto? Pues que ahora, con AJAX, podemos cargar una página y, sin tener que recargarla, traernos información, modificar la página en tiempo real, e interactuar con servidores remotos usando protocolos XML. Básicamente, una vez cargada la página web tenemos entre manos todas las posibilidades de programación de un GUI tradicional. Y todo esto sin necesidad de plugins ni instalaciones, toda esta tecnología está en nuestros navegadores esperando ser usada.

ser consultado usando AJAX. Crearemos una web con algo de código Javascript que a intervalos accederá a nuestro servidor Python y modificará el aspecto de la página web.

Los 5 Ingredientes
Los cinco ingredientes necesarios para elaborar nuestro producto son CSS, Javascript, HTML, XML y Python, como aparecen en la figura 1, y cada uno tiene su función en esta obra. HTML es la base sobre la que vamos a trabajar, definimos una página web en la que todo ocurrirá. De hecho, con el paso del tiempo el propio HTML ha acabado convirtiéndose en una especie de plantilla donde campan a sus anchas CSS y Javascript. CSS nos permite otorgar propiedades visuales a los elementos de HTML. Javascript es el encargado de actuar en la máquina cliente, en el navegador, y puede modificar tanto el HTML como las propiedades visuales que CSS define. Con la llamada XMLHttpResponse sus atribuciones se han disparado. Ahora se ve como un lenguaje de programación de pleno derecho. En los próximos años puede que adquiera mucha más importancia de la que ha tenido hasta ahora.

¿Cómo encaja Python?
Pues vamos a realizar un pequeño servidor de contenidos en Python que pueda

Listado 1: fichero server.py
01 02 03 04 05 06 07 #!/usr/local/bin/python import BaseHTTPServer import os import cgi class AJAXHTTPRequestHandler (BaseHTTPServer.BaseHTTPReques tHandler): """ Responde a peticiones HTTP """ def do_GET(self): "Gestiona los GET" 19 20 21 "/uname.xml": ["envia_comando","uname -a"]} envia_fichero(self,ruta,fichero): 33 # No usamos ruta, pero así simplificamos el código 34 p = Pagina(fichero) 35 self.enviar_respuesta(p.tipo(), p.contenido()) 36 37 def envia_comando(self,ruta,comando): 38 c = Comando(comando) 39 self.enviar_respuesta(c.tipo(), c.contenido()) 40 41 def enviar_respuesta(self, tipo, contenido): 42 self.enviar_cabecera(tipo) 43 self.wfile.write(contenido) 44 45 def enviar_cabecera(self, tipo): 46 self.send_respon-

08 09 10 11 12 13 14 15

acciones = { "/" : ["envia_fichero","index.html"] , 16 "/ps.xml" : ["envia_comando", "ps afx"], 17 "/df.xml": ["envia_comando", "df"], 18 "/who.xml": ["envia_comando","who"],

if self.path in acciones.keys(): 22 accion = acciones[self.path] 23 (getattr(self,accion[0]))(self .path,accion[1]) 24 else: 25 if (self.path[-3:] == ".js" or 26 self.path[-4:] == ".css"): 27 self.envia_fichero("",self.pat h[1:]) 28 29 else: 30 self.envia_fichero("","404.htm l") 31 32 def

54

Número 12

WWW.LINUX- MAGAZINE.ES

053-060_PythonL12

16.11.2005

9:01

Uhr

Página

55

053-060_PythonL12

16.11.2005

9:01

Uhr

Página

56

DESARROLLO • Python

XML es el nuevo lenguaje estándar de intercambio de información. Prácticamente cualquier lenguaje dispone ya de librerías para generar y analizar documentos XML. Dentro del mundillo AJAX se ha convertido en el estándar para el intercambio de información con el servidor y para la serialización de objetos con protocolos como JSON. Y, como no, Python. En nuestro caso se va a encargar tanto de realizar las tareas de servidor HTTP como de recolectar información importante y confeccionar con ella ficheros XML. Tenemos que compenetrar todos estos elementos para realizar nuestro proyecto. El objetivo es que los componentes HTML, Javascript, CSS y XML sean tan estáticos como sea posible, debido a que todos ellos interactúan con el usuario. Es en el servidor Python donde debemos aportar la flexibilidad necesaria como para añadir nuevas características sin tener que cambiar ninguno de los otros elementos.

Figura 1: Esquema de nuestra aplicación AJAX con todos sus compenentes.

La parte de Python: BaseHTTPRequest
Python dispone en sus librerías estándar de muchos «esqueletos» para distintos tipos de servidores. Entre ellos encontra-

mos BaseHttpRequest. Esta clase nos permite construir servidores HTTP sin excesivo esfuerzo, así que la emplearemos. Pero no debemos usarla directamente, sino a través de la clase BaseHttpRequestHandler, que es la encargada de gestionar los eventos que se suceden en el servidor. Por tanto heredaremos de ella y crearemos una clase llamada AJAXHttpRequestHandler, ver Listado 1 (todos los listados de este artículo pueden descargarse de [4]). Un servidor HTTP recibe distintos tipos de comandos, pero el que nos inte-

resa es el comando GET. Es el usado para solicitar información al servidor. Cada vez que se realice una petición GET se invocará el método do_GET de BaseHTTPRequestHandler así que lo vamos a redefinir en nuestra clase. Cuando se invoque do_GET, en la variable de instancia self.path se encuentra la ruta solicitada por el cliente. Nosotros contrastaremos esta ruta contra las que aceptamos. Si no se encuentra entre ellas, devolveremos la célebre página 404, indicando que la página solicitada no existe. La información se devuelve

Listado 1: fichero server.py (cont.)
se(200) 47 self.send_header("Content-type ","text/" + tipo) 48 self.end_headers() 49 50 class Pagina: 51 def __init__(self,nombre): 52 self.nombre = nombre 53 self.texto = "" 54 fichero = file(self.nombre) 55 self.texto = fichero.read() 56 fichero.close() 57 58 def contenido(self): 59 return self.texto 60 61 def tipo(self): 62 tipo = "html" 63 ext = self.nombre[-4:] 64 if (ext == "html"): 65 tipo = "html" 66 elif (ext == ".xml"): 67 tipo = "xml" 68 elif (ext == ".css"): 69 tipo = "css" 70 return tipo 71 72 class Comando: 73 def __init__(self,comando): 74 self.tuberia = os.popen(comando) 75 self.xml = "" 76 77 def contenido(self): 78 # fichero XML 79 if not self.xml: 80 self.xml = "<?xml version=\"1.0\" ?>" 81 self.xml += "<salida>" 82 linea = self.tuberia.readline()[:-1] # para quitar el \n 83 while linea: 84 self.xml += "<linea>" + cgi.escape(linea)

+ "</linea>" 85 linea = self.tuberia.readline()[:-1] 86 self.xml += "</salida>" 87 return self.xml 88 89 def tipo(self): 90 return "xml" 91 92 def test(HandlerClass = AJAXHTTPRequestHandler, 93 ServerClass = BaseHTTPServer.HTTPServer): 94 BaseHTTPServer.test(HandlerCla ss, ServerClass) 95 96 if __name__ == '__main__': 97 test()

56

Número 12

WWW.LINUX- MAGAZINE.ES

053-060_PythonL12

16.11.2005

9:01

Uhr

Página

57

Python • DESARROLLO

usando el método self.wfile.write(), lo que en él escribamos, será devuelto al cliente que realizó la petición. Además del fichero index.html, ofreceremos una serie de servicios en forma de ficheros XML. Estos servicios consistirán en la ejecución de un comando de sistema, y la conversión de su salida en un fichero XML. El formato del fichero será muy sencillo:

Figura 2: La clase BaseHTTPRequest genera una entrada por comando.

desde envia_respuesta antes de enviar el fichero en sí. Es de esta manera como el navegador determina el tipo de fichero que recibe y sus características. Cuando arranquemos el servidor, veremos que van apareciendo mensajes correspondientes a los distintos comandos que se le mandan. La clase BaseHTTPRequest genera una entrada por cada una. Ver figura 2.

Listado 2: fichero index.html
01 <html> 02 <head> 03 <title>Pruebas con AJAX</title> 04 <link rel="stylesheet" href="estilo.css" type="text/css" /> 05 <script language="Javascript" 06 src="ajax.js"> 07 </script> 08 </head> 09 <body> 10 <div id="documento"> 11 <h3 id="titulo">Información del sistema</h3> 12 <input type="button" name="button" value="Procesos" 13 onclick="javascript:hazPeticio n('ps.xml');" /> 14 <input type="button" name="button" value="Disco" 15 onclick="javascript:hazPeticio n('df.xml');" /> 16 <input type="button" name="button" value="Usuarios" 17 onclick="javascript:hazPeticio n('who.xml');" /> 18 <input type="button" name="button" value="Máquina" 19 onclick="javascript:hazPeticio n('uname.xml');" /> 20 <div id="contenedor"> 21 <div id="datos"></div> 22 </div> 23 </div> 24 </body> 25 </html>

<?xml version="1.0"?> <salida> <linea>linea de salida</linea> ... <linea>linea de salida</linea> ... <linea>linea de salida</linea> </salida>

Gestores de servicios
Para gestionar los servicios se han creado las clases Pagina y Comando, que responden a los mismos métodos. La primera, Pagina, se encarga de las peticiones de ficheros de texto, que en nuestro programa se reducen al fichero index.html y sus ficheros supletorios, fichero Javascript y CSS. Básicamente los carga en una variable y, mediante el método contenido, las demás clases tienen accesos a los mismos. En este caso, el parámetro ruta no afecta a esta clase, pero se ha definido para que guarde compatibilidad con la clase Comando. La clase Comando ejecuta el comando especificado y permite el acceso al texto devuelto por él mismo mediante el mismo método que la clase Pagina. De esta manera son intercambiables. Ambas clases poseen un método tipo() que devuelve el tipo de fichero que almacenan. En el caso de Comando, siempre será un fichero XML, pero Pagina debe «adivinar» el tipo de los ficheros que

Por lo que generaremos el fichero XML «a mano», sin hacer uso de librerías. De esta manera, cuando el cliente solicite el fichero ps.xml, nuestro servidor ejecutará el comando ps afx, creará el fichero ps.xml con la salida del comando y se lo enviará al cliente.

Definición de servicios
Para permitir que la definición de servicios sea lo más simple posible, basta con introducir una nueva entrada en el diccionario acciones de la clase AJAXHTTPRequestHandler con la siguiente estructura:
<ruta> : [<método_a_invocar>,U <comando_a_ejecutar>],

Cuando se gestiona el comando HTTP GET, se busca en este diccionario la ruta. En caso de que esté presente, se ejecutará el método almacenado usando como parámetros la ruta y el comando. Esto nos da gran flexibilidad, añadir un nuevo servicio consiste en introducir una nueva linea de código. Existe un detalle importante, todo fichero devuelto usando HTTP debe tener una cabecera con una serie de líneas con formato llave: valor que dan información al cliente, el navegador, sobre el fichero devuelto. Debido a problemas de espacio hemos decidido devolver sólo el formato del fichero. Para ello se invoca el método envia_cabecera

WWW.LINUX- MAGAZINE.ES

Número 12

57

053-060_PythonL12

16.11.2005

9:01

Uhr

Página

58

DESARROLLO • Python

devuelve. Para ello se comprueba la extensión de los mismos. En aras de la simplicidad, sólo consideraremos tres extensiones. Cuando un comando es ejecutado por Comando, se tiene especial cuidado en utilizar la función cgi.escape sobre cada linea. Esta función realiza algunas conversiones dentro del texto que se le pasa para que pueda ser correctamente visualizado por un navegador web. El problema radica en que ciertos caracteres, como pueden ser «<» or «”» son especiales y si no se «escapan», si no se preceden de un «\», causarán problemas. Y con esto, hemos definido un servidor web básico. Python nos permite realizar complejas tareas con poco código y

este es uno de esos casos. Vayamos ahora a por AJAX para comprenderlo.

Javascript, desde otra perspectiva
Mucha gente ha tenido extraños encuentros con este lenguaje de programación. Es raro y, hasta hace no demasiado, no muy útil. Te permitía modificar colores en páginas web o poner insidiosos banners. Por no hablar de las famosas ventanas emergentes. Esto ha hecho que se haya ganado una fama muy mala, tal es así que casi todos tenemos restricciones en nuestro navegador en torno a qué acciones puede o no realizar Javascript. Lo más normal es que tengamos uno de esos famosos bloqueadores de popups. Pero Javascript se ha reinsertado en la sociedad de los programadores por la puerta grande gracias a un solo objeto.

HTML
Quizá éste sea uno de los artículos donde Python tenga menor protagonismo, pero con cinco actores suele ser complicado. Veamos el HTML. La página index.html se puede ver en el Listado 2. Básicamente carga un fichero Javascript, un fichero CSS y muestra un título junto a unos cuantos botones. Estos botones invocan acciones en Javascript. Debemos fijarnos especialmente en el uso del atributo id en numerosas etiquetas HTML. Gracias a estos ids podremos manipularlas mediante Javascript.

Listado 3: fichero ajax.js.
01 // GLOBALES 02 var http_request = false; 03 04 function hazPeticion(url) { 05 http_request = false; 06 http_request= new XMLHttpRequest(); 07 if (http_request.overrideMimeType ) { 08 http_request.overrideMimeType( 'text/xml'); 09 } 10 11 if (!http_request) { 12 alert('Error al crear la instancia de XMLHttpRequest.'); 13 return false; 14 } 15 16 // Esto es un callback, que se dispara al terminar de 17 // descargar el fichero xml. 18 http_request.onreadystatechange = modificaContenido; 19 http_request.open('GET', url, true); 20 http_request.send(null); 21 } 22 // Elimina todo elemento con id "linea" 23 function vaciaContenido(){ 24 var d = document.getElementById("contenedor"); 25 while(document.getElementById( "linea0") || 26 document.getElementById("linea 1")){ nodo = document.getElementById("linea 0"); if (! nodo){ nodo = document.getElementById("linea 1"); } var nodo_basura = d.removeChild(nodo); } } 47 48 49 var nodo = root.childNodes.item(i);

27

28 29

30 31

32 33 34 35 // Carga el resultado del XML 36 function modificaContenido() { 37 if (http_request.readyState == 4) { 38 if (http_request.status == 200) { 39 vaciaContenido(); 40 41 var xmldoc = http_request.responseXML; 42 var root = xmldoc.getElementsByTagName('s alida').item(0); 43 44 var fondo = 0; 45 46 for(var i = 0; i < root.childNodes.length; i++){

var contenedor = document.getElementById("contenedor"); 50 var p = document.createElement("p"); 51 52 // Truco para los colores ;) 53 if (fondo == 0) { 54 p.setAttribute("id","linea0"); } 55 else { 56 p.setAttribute("id","linea1"); 57 } 58 fondo = 1 - fondo; 59 60 var titulo = document.getElementById("datos"); 61 p.textContent = nodo.firstChild.data; 62 63 contenedor.insertBefore(p,titu lo); 64 } 65 66 } else { 67 alert('Hubo un problema con la petición.'); 68 } 69 } 70 }

58

Número 12

WWW.LINUX- MAGAZINE.ES

053-060_PythonL12

16.11.2005

9:02

Uhr

Página

59

053-060_PythonL12

16.11.2005

9:02

Uhr

Página

60

DESARROLLO • Python

mente nada que ver, aparte del nombre. En nuestro ejemplo vemos tres funciones: • hazPeticion() • modificaContenido() • vaciaContenido() Javascript, además de muchas de las características presentes Figura 3: Nuestra página devolverá los resultados de en otros lenguajes (y algunas la consulta sin recargar. ausentes) dispone de una colección de objetos que le permiten Nos referimos al ahora famoso realizar operaciones. A día de hoy los XMLHttpRequest. más importantes: Si vemos el código del Listado 3, vere• Manipulación DOM mos un lenguaje que quizá nos recuerda • Manipulación XML a Java. En realidad no tienen absoluta• XMLHTTP DOM permite a Javascript manipular, en Listado 4: fichero estilo.css tiempo real, el contenido de la página web. Puede añadir, modificar o quitar etiquetas y atributos, por lo que pode01 #documento{ mos operar sobre el documento de cual02 margin-left: quier forma posible. Javascript puede 100px; manipular un fichero XML de igual 03 } forma que hace DOM. 04 Y la gran novedad, Javascript puede 05 realizar conexiones ASÍNCRONAS con el 06 #titulo{ servidor. Y resalto en mayúscula la pala07 text-decoration: bra ASÍNCRONAS porque ahí está la underline; clave. 08 } Esto significa que podemos hace cone09 xiones, traernos documentos XML del 10 servidor y realizar operaciones DOM o 11 de cualquier otro tipo, ¡cuando quera12 #linea0 { mos! 13 margin: 0px; El usuario carga su página web, y una 14 padding-left: 20px; vez cargada, sin necesidad de recargarla, 15 background: #e0e0e0; podemos modificarla a nuestro antojo en 16 font-family: monosbase a información que podemos pedir pace; al servidor en cualquier momento. 17 } Volviendo a nuestras funciones, la fun18 ción hazPeticion() recibe una url, crea el 19 #linea1 { objeto XMLHttpRequest y después de 20 margin: 0px; algunas comprobaciones, asigna una 21 padding-left: 20px; función para que sea invocada cuando el 22 font-family: monosfichero que esa url especifica sea compace; pletamente descargado. 23 } Esto significa que mientras leemos 24 nuestra web, Javascript estará bajando 25 #contenedor{ un fichero y, cuando finalice, llamará a 26 border-style: la función modificaContenido. dashed; ¿Y qué hace esta función? Comprueba 27 border-width: el estado de la petición, (el estado 200 el 1px; de «todo correcto» y el de 404 el de «lo 28 width: 600px; sentimos mucho, pero el fichero solicita29 border-color: do no está disponible») y entonces obtieblack; ne el documento XML del objeto que 30 } gestionaba la conexión.

Pero antes invoca a vaciaContenido(), que localiza toda etiqueta con las ids «linea0» o «linea1» y las elimina de la página. Hacemos esto porque a continuación las volvemos a introducir en la página, pero esta vez con los datos frescos del servidor. No queremos entrar en los detalles, ya que, en teoría, esto es un artículo sobre Python no Javascript, pero básicamente esto es lo que hace el fichero ajax.js. El fichero estilo.c, que aparece en el listado 4, simplemente configura los colores y características de algunas de las etiquetas, para que el aspecto mejore. Y se acabó, aquí tenemos nuestra aplicación AJAX. El resultado final será el que vemos en la figura 3. Cuando pulsemos cualquiera de los botones, se cargará la salida de texto de la ejecución asociada a cada uno de ellos en pantalla, pero sin recargar la página. Si queremos añadir un nuevo comando sólo tenemos que introducir la línea correspondiente en acciones en server.py y añadir un nuevo botón como los que ya existen en index.html.

Conclusión
¿Es tan complicado eso de AJAX? ¡Por supuesto que no! Lo que ocurre es que es una palabra que se está convirtiendo en un mito, pero no deja de ser una astuta combinación de programación en el servidor y cliente además del uso intensivo de XMLHttpRequest. Poco a poco AJAX está poblando todas las páginas webs y la mayoría de los currículos vitae. Quien sabe, lo mismo dentro de dos años esa palabra tenga tanto poder como otra palabra de cuatro I letras: J2EE.

60

Número 12

WWW.LINUX- MAGAZINE.ES

Sign up to vote on this title
UsefulNot useful