Vous êtes sur la page 1sur 25

¿Qué ofrece Autentia?

Somos su empresa de

Soporte a Desarrollo Informático


Ese apoyo que siempre quiso tener ….

• Desarrollo de componentes y proyectos a medida.


• Auditoría de código y recomendaciones de mejora.
• Arranque de proyectos basados en nuevas tecnologías.
• Curso de Formación
Dirección de Proyectos Informáticos.
Gestión eficaz del Tiempo.
Arquitecturas de desarrollo.
Java/ J2EE a todos los niveles.
Análisis y diseño orientado a objeto.
UML y patrones de diseño.
Buenas prácticas en el desarrollo de aplicaciones
Técnicas avanzadas: Lucene, Hibernate, JSF, Struts, etc.

Nuestra mejor referencia son los conocimientos que

compartimos en nuestro web

www.adictosaltrabajo.com

Decenas de entidades cuentan ya con nosotros

Para más información visítenos en www.autentia.com


Tel. 91 675 33 06 - info@autentia.com
Tutoriales en AdictosAlTrabajo: Java, J2EE, Visual C++, Linux, UML... http://www.adictosaltrabajo.com/tutoriales/tutoriales.php?pagina=AJ...

Home | Quienes Somos | Empleo | Tutoriales | Contacte

Autor: Javier Cámara

jcamara@softwareag.es

Descargar este documento en formato PDF AJAXaMano.pdf

Firma en nuestro libro de Visitas

Java & SOAP Editing Tool J2EE Survey Software Master Java J2ee Oracle The SCORM Experts
Edit SOAP, JSP, WSDL, DTD, Used by Cisco, Kaiser, Viacom, 100% alumnos ya trabajan. The quickest and easiest way to
Schema Easy-to-Use. Download Govt Visual editor, scripts, Nuevo temario de Struts + J2ME. become SCORM conformant.
Free Trial! extensible www.grupoatrium.com www.scorm.com
www.Altova.com/XMLSpy www.cogix.com

Una aplicación AJAX hecha a mano


En este tutorial se muestra cómo hacer una página web que, usando AJAX, accede a un servicio web SOAP. Para ello sólo se
usa JavaScript, sin nada de código en el servidor.

Por Javier Cámara (jcamara@softwareag.es)


Arquitecto de software en el BCS Competence Center de Software AG España.

Introducción
Si trabajas en algo relacionado con las aplicaciones web es muy complicado que no hayas oído hablar de AJAX recientemente.
AJAX es una forma de construir aplicaciones web que permite que éstas sean más usables y dinámicas. El efecto se puede
resumir en la frase "páginas web con actualización parcial": o sea, en vez de que las páginas se reciban completamente desde
el servidor cada vez que hacemos algo, hay una única página fija que recibe datos del servidor "por debajo" con los que
actualiza partes de la misma. El resultado neto es esa mayor interactividad.

En puridad AJAX significa Asynchronous JAvascript with XML, indicando que la página se comunica con el servidor usando XML.
La "asincronía" sólo sirve para que el navegador no se quede congelado durante esa comunicación, que es lo que pasa si la
comunicación es síncrona en vez de asíncrona. Pero en realidad, como AJAX se clasifica cualquier cosa que tenga
actualizaciones parciales. Hay otras variantes que no son XML, como JSON, que obtienen el mismo efecto.

Curiosamente esto ha sido posible desde hace varios años (yo conozco aplicaciones que podrían clasificarse como algo parecido
desde 1998 o así), y en realidad es gracias al por muchos odiado Microsoft que esto es posible. Pero no ha sido hasta que ha
existido un navegador alternativo al de Microsoft que soportase esto por completo (Firefox), y que Google (no sólo Microsoft)
haya mostrado a todo el mundo cómo se le puede sacar partido (ej. con Google maps), que se ha popularizado esta técnica.

En un futuro cercano, el AJAX será una forma muy común, probablemente la más extendida, de construir muchas aplicaciones.
Aunque ésto sólo será cierto si existen herramientas que oculten la complejidad del AJAX haciéndolo lo más transparente
posible, como Microsoft Atlas, Google web toolkit, quizás JSF, u otra miríada de herramientas de desarrollo que insisten en que
si las usas podrás crear aplicaciones web AJAX en un plis-plas. Aún así, en este tutorial vamos a mostrar cómo se puede hacer
un poquito de AJAX a mano, o sea sin usar ninguna herramienta especial.

Por ello, nuestra aplicación va a consistir de únicamente una página HTML con JavaScript dentro, que se conectará a un
servicio web SOAP que sacaremos de otro tutorial que ya hicimos previamente. Y además, esa aplicación funcionará tanto en
Internet Explorer 6 como en Firefox.

El resto de este tutorial contiene lo siguiente:

Qué debe hacer nuestra aplicación


El servidor
Creando la interfaz HTML
Creando el mensaje de petición
Llamando a nuestro servicio
Mostrando los resultados en la pantalla
Conclusiones

1 de 24 02/11/2006 11:23
Tutoriales en AdictosAlTrabajo: Java, J2EE, Visual C++, Linux, UML... http://www.adictosaltrabajo.com/tutoriales/tutoriales.php?pagina=AJ...

Qué debe hacer nuestra aplicación


Vamos a reutilizar los resultados de un tutorial previo sobre construcción de servicios web a partir de WSDL, y vamos a ponerle
una cara al servicio que creamos en ese tutorial. O sea, vamos a crear una aplicación AJAX que pida un código postal o parte
de él, y luego muestre la lista de sucursales bancarias en ese código.

El servidor
Nuestra aplicación AJAX se va a conectar a un servidor, y me temo que vas a tener que tener instalado en tu PC ese servidor.
Esto es debido a una importante limitación de una página AJAX: no puede acceder a cualquier recurso. El mecanismo que
usa el AJAX para comunicarse con el exterior es abrir una conexión HTTP desde código JavaScript, y por buenas razones de
seguridad los navegadores limitan el que esa conexión se pueda abrir a una máquina diferente de la que se descargó el
JavaScript (en el caso de Firefox, hasta el puerto tiene que ser el mismo). Por ello, la página que vamos a crear en este tutorial
debe estar grabada dentro de la misma aplicación web que implementa nuestro servicio.

La forma normal de evitar esta limitación es usar un proxy en el servidor: si una página AJAX se quiere conectar a un recurso
fuera de su máquina origen, lo que hace es conectarse a un proceso (ej. un pequeño servlet) en su máquina origen que le hace
de intermediario. Pero nosotros no vamos a hacer esto en este tutorial, sino que vamos a poner directamente la página AJAX
en el mismo sitio donde está el servicio.

La variante JSON del AJAX no tiene esta limitación, pues al usar frames ocultos éstos pueden apuntar a cualquier recurso. Pero
claro, el servidor debe devolver JavaScript, lo cual no es ni de lejos tan común como SOAP, y tengo mis dudas de que esté lo
suficientemente estandarizado.

En nuestro tutorial, para conseguir tener el servicio web ejecutándose en tu máquina puedes hacer, al menos, una de estas dos
cosas:

1. Realiza todo el tutorial donde se crea el servicio, instalando Eclipse, Tomcat etc; o
2. Instálate Tomcat y luego despliega en él este archivo WAR que contiene la aplicación web del servicio. Ten en cuenta que
luego modificaremos una página que está dentro de ese WAR, así que si usas un servidor distinto de Tomcat eso puede
obligarte a hacer operaciones adicionales.

Durante el resto del tutorial se asumirá que has elegido la opción 1, y se usará Eclipse y la aplicación sucursales del anterior
tutorial para crear la página AJAX. Si has elegido la opción 2, puedes usar cualquier editor para modificar la página, en vez de
Eclipse.

Creando la interfaz HTML


La interfaz de usuario de nuestra aplicación tendrá el siguiente aspecto:

Como el objetivo de este tutorial no es aprender HTML sino AJAX, aquí tienes esa página ya preconstruida para que te la
descargues. Además, también puedes descargarte la aplicación final completa tal y como quedaría si se siguen todos los pasos
de este tutorial.

Como ya hemos dicho antes, esa página debe estar dentro de la aplicación web del servicio. Si has descargado sucursales.war
en vez de partir del tutorial de creación del servicio, esto no lo tienes que hacer porque ese war ya contiene sucursales.html .
Pero si no, pues meteremos la página en Eclipse. Para ello:

Salva la página en alguna parte de tu disco duro


En Eclipse, importa la página en el directorio WebContent del proyecto Sucursales:

2 de 24 02/11/2006 11:23
Tutoriales en AdictosAlTrabajo: Java, J2EE, Visual C++, Linux, UML... http://www.adictosaltrabajo.com/tutoriales/tutoriales.php?pagina=AJ...

3 de 24 02/11/2006 11:23
Tutoriales en AdictosAlTrabajo: Java, J2EE, Visual C++, Linux, UML... http://www.adictosaltrabajo.com/tutoriales/tutoriales.php?pagina=AJ...

Tras salvar esa página, el Eclipse se ocupará de redesplegarla en el Tomcat, y si éste está arrancado y ejecutándose en el
puerto 8081, al conectarnos a http://localhost:8081/sucursales/sucursales.html veremos nuestra interfaz de usuario.

En el resto del tutorial nos dedicaremos a rellenar esa página con el JavaScript que le dará el toque AJAX.

Creando el mensaje de petición


Lo primero que haremos será crear el código que invoque a nuestro servicio. Para eso primero tenemos que averiguar cuál es
exactamente el mensaje que éste espera. Si entiendes muy bien WSDL y XML Schema, puedes intentar averiguarlo mirando
sucursales.wsdl ; pero si como es normal no es así, mejor que utilices alguna herramienta. El XML Spy es una herramienta
buenísima para esto (y de pago), pero con Eclipse lo más fácil es ver qué documentos XML se envían y reciben cuando
ejecutamos el servicio con el Web Services Explorer:

4 de 24 02/11/2006 11:23
Tutoriales en AdictosAlTrabajo: Java, J2EE, Visual C++, Linux, UML... http://www.adictosaltrabajo.com/tutoriales/tutoriales.php?pagina=AJ...

Así podemos comprobar que el mensaje de petición es así:

<?xml version="1.0" encoding="UTF-8"?>


<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:q0="http://banquito.com/Sucursales/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Body>
<q0:buscaSucursalesRequest>
<q0:parteCodPostal>28</q0:parteCodPostal>
</q0:buscaSucursalesRequest>
</soapenv:Body>
</soapenv:Envelope>

Y ahí aún hay algunas cosas que sobran. Vamos a modificar sucursales.html para incluir la creación de ese mensaje:

5 de 24 02/11/2006 11:23
Tutoriales en AdictosAlTrabajo: Java, J2EE, Visual C++, Linux, UML... http://www.adictosaltrabajo.com/tutoriales/tutoriales.php?pagina=AJ...

1. Primero, en el onclick de nuestro botón vamos a invocar a una nueva función Javascript, dejándolo como sigue:

<input type=submit value="Buscar sucursales"


onclick="buscaSucursales();return false;">

2. Luego vamos a crear esa función. Por ahora sólo crearemos el mensaje XML, insertando dentro el código postal
introducido en el formulario. Luego lo mostraremos ese mensaje, para ir comprobando que todo va bien:

<head>
<title>Banquito - búsqueda de sucursales</title>

<script>

function buscaSucursales()
{
var smsg=
'<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">'+
'<SOAP-ENV:Body>'+
'<bs:buscaSucursalesRequest xmlns:bs="http://banquito.com/Sucursales/">'+
'<parteCodPostal>'+document.formSucu.cp.value+'</parteCodPostal>'+
'</bs:buscaSucursalesRequest>'+
'</SOAP-ENV:Body>'+
'</SOAP-ENV:Envelope>';

alert(smsg);
}
</script>

</head>

Si accedemos a nuestra página con un navegador, por ejemplo en http://localhost:8081/sucursales/sucursales.html (recuerda


refrescar la página para que recargue los cambios) y le damos al botón de Buscar sucursales, nos debería salir algo como esto:

Hemos creado el mensaje XML de la forma más obvia: como una cadena de caracteres. También podríamos haber intentado
usar DOM, pero eso a) resulta en un código mucho más complicado y b) funciona diferente en Internet Explorer y en Firefox,
con lo cual en la práctica no vale la pena. Además, usar cadenas es la forma en que el código queda más legible, así que en
entornos "ligeros" como Javascript yo es la opción que recomiendo.

Precisamente por usar cadenas, ese código que hemos puesto tiene un fallo muy obvio, y es que puede generar XML inválido.
Por ejemplo, si como código postal metemos "casque>" el XML que nos generará será éste:

Que ni siquiera podremos mandar al servidor porque no es XML. Este tipo de cosas causan errores inesperados más tarde y
hasta problemas de seguridad, así que lo mejor es cortarlos cuanto antes. Para eso, vamos a "escapear" los caracteres
especiales de XML con una nueva función Javascript. Como esta función no es específica de nuestra aplicación sino que sería
válida para otras aplicaciones y otros servicios, la vamos a meter en una librería de funciones común llamada ws.js (por "web
services", claro). Así pues,

1. Creamos un nuevo fichero:

6 de 24 02/11/2006 11:23
Tutoriales en AdictosAlTrabajo: Java, J2EE, Visual C++, Linux, UML... http://www.adictosaltrabajo.com/tutoriales/tutoriales.php?pagina=AJ...

2. Y ahí vamos a definir nuestra nueva función, ws_escapeXML:

function ws_escapeXML(s)
{
var s1="";
var i,I;
for (i=0,I=s.length;i<I;++i)
{

7 de 24 02/11/2006 11:23
Tutoriales en AdictosAlTrabajo: Java, J2EE, Visual C++, Linux, UML... http://www.adictosaltrabajo.com/tutoriales/tutoriales.php?pagina=AJ...

var c=s.charAt(i);
if (c=='>')
s1+="&gt;";
else if (c=='<')
s1+="&lt;";
else if (c=='&')
s1+="&amp;";
else
s1+=c;
}
return s1;
}

3. Luego incluimos esa librería en nuestro sucursales.html e invocamos a la función:

<head>
<title>Banquito - búsqueda de sucursales</title>

<script src="ws.js"></script>

<script>

function buscaSucursales()
{
var smsg=
'<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">'+
'<SOAP-ENV:Body>'+
'<bs:buscaSucursalesRequest xmlns:bs="http://banquito.com/Sucursales/">'+
'<parteCodPostal>'+ws_escapeXML(document.formSucu.cp.value)+'</parteCodPostal>'+
'</bs:buscaSucursalesRequest>'+
'</SOAP-ENV:Body>'+
'</SOAP-ENV:Envelope>';

alert(smsg);
}
</script>

</head>

Después de eso, si volvemos a meter "casque>" como código postal, el XML que nos generará ya será válido:

Llamando a nuestro servicio


Ahora que ya tenemos el mensaje creado, vamos realmente a invocar al servicio, o sea la parte realmente AJAX del asunto.
Para ello haremos lo siguiente:

Averiguar cuál es el URL del servicio


Crear una XMLHttpRequest e invocar al servicio de forma asíncrona
Hacer que la llamada al servicio tarde un poco
Recoger el resultado del servicio de forma asíncrona

Averiguar cuál es el URL del servicio

Como estamos copiando sucursales.html dentro de la aplicación web del servicio, podemos saber con facilidad cuál es el URL
del servicio independientemente del nombre de la máquina etc. Vamos a crear una nueva función en sucursales.html que nos
componga ese URL:

function getURLServicio()
{
var url=
document.location.protocol+"//"+document.location.host+
primParte(document.location.pathname)+
"/services/SucursalesSOAP";
return url;
}

Y la función de utilidad primParte, que devuelve el primer componente de un path (/comp1/comp2/...) la meteremos en ws.js
aunque no tenga mucho que ver con servicios web:

8 de 24 02/11/2006 11:23
Tutoriales en AdictosAlTrabajo: Java, J2EE, Visual C++, Linux, UML... http://www.adictosaltrabajo.com/tutoriales/tutoriales.php?pagina=AJ...

function primParte(s)
{
var d=0;
if (s.charAt(0)=='/')
d=1;
var p=s.indexOf('/',d);
if (p>0)
return s.substring(0,p);
else
return s;
}

Crear una XMLHttpRequest e invocar al servicio de forma asíncrona

El objeto XMLHttpRequest es la piedra angular del AJAX. Es un buen invento del por muchos odiado Microsoft como una forma
de cumplir la vieja promesa del XML de ser intercambiado entre los navegadores y los servidores. Es muy viejo, yo recuerdo
haberlo visto ya allá por el año 2000. Y, afortunadamente, la gente de Mozilla creyó que era una buena idea, así como el HTML
dinámico (otro buen invento también del odiado Microsoft). Luego, los de Google se preguntaron que, ya que tanto el
XMLHttpRequest como el HTML dinámico eran multinavegador, que por qué no usarlos, y así nació el AJAX. Otros navegadores
también soportan todo esto, y el XMLHttpRequest está siendo objeto de estandarización por parte del W3C.

Este objeto permite enviar una petición XML a un servidor por HTTP, y recibir la respuesta XML también. Sencillo. Esta
recepción de respuesta se puede hacer de forma síncrona, de modo que el navegador se queda congelado hasta que el servidor
responde (no podemos movernos por la página, ir a otra página, usar los menús, etc); o asíncrona, de modo que el navegador
sigue procesando acciones del usuario y cuando llega la respuesta invoca a una función que le digamos.

Vamos a empezar por lo fácil y haremos una invocación síncrona:

1. Primero editamos la función buscaSucursales de nuestro sucursales.html y añadimos el uso de XMLHttpRequest:

function buscaSucursales()
{
var smsg=
'<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">'+
'<SOAP-ENV:Body>'+
'<bs:buscaSucursalesRequest xmlns:bs="http://banquito.com/Sucursales/">'+
'<parteCodPostal>'+ws_escapeXML(document.formSucu.cp.value)+'</parteCodPostal>'+
'</bs:buscaSucursalesRequest>'+
'</SOAP-ENV:Body>'+
'</SOAP-ENV:Envelope>';

var req=ws_newXMLHttpRequest();

var endpoint=getURLServicio();
req.open("POST",endpoint,false);
req.setRequestHeader("SOAPAction",'"http://banquito.com/Sucursales/buscaSucursales"');
var resp=req.send(smsg);

if (!req.responseXML || !req.responseXML.documentElement)
{
var error;
if (req.status !=200)
error="Error HTTP '"+req.status+" "+req.statusText+"' accediendo al servicio en "+endpoint;
else
error="El servicio en "+endpoint+" no ha devuelto un XML correcto";
alert(error);
}
else
alert("Resultado:"+req.responseXML.documentElement.xml);
}

La SOAPAction es una cabecera HTTP que es uno de los mecanismos de comunicación de SOAP sobre HTTP. Supongo que
sirve para permitir redirigir peticiones sin tener que analizar el XML. No estoy seguro de que Axis le haga caso, pero lo
apropiado es ponerlo en cualquier caso. Ese valor que hemos puesto,
"http://banquito.com/Sucursales/buscaSucursales", lo hemos averiguado consultando sucursales.wsdl:

Y según las reglas del WS-I, es obligatorio que vaya rodeado de "" aunque sea vacío, y por eso lo mandamos así.

2. En ese código se utiliza la función ws_newXMLHttpRequest, y por tanto hemos de crearla. Esta función se ocupa de crear
un objeto XMLHttpRequest, pues aunque su uso es el mismo en Internet Explorer y Firefox, lamentablemente su creación
no lo es, e incluso varía entre versiones de Internet Explorer. La meteremos en ws.js, claro:

9 de 24 02/11/2006 11:23
Tutoriales en AdictosAlTrabajo: Java, J2EE, Visual C++, Linux, UML... http://www.adictosaltrabajo.com/tutoriales/tutoriales.php?pagina=AJ...

function ws_newXMLHttpRequest()
{
var req;
try
{
// Esto funciona en Mozilla pero en IE lanza una excepción
req=new XMLHttpRequest();
}
catch (e)
{
try
{
req=new ActiveXObject("MSXML2.XmlHttp");
}
catch (e)
{
try
{
req=new ActiveXObject("Microsoft.XmlHttp");
}
catch (e)
{
try
{
req=new ActiveXObject("MSXML.XmlHttp");
}
catch (e)
{
req=new ActiveXObject("MSXML3.XmlHttp");
}
}
}
}
return req;
}

Si desplegamos eso dentro de la aplicación web de nuestro servicio, accedemos a ella mediante el navegador (recuerda
refrescar la página para que recargue los cambios) y pulsamos en Buscar sucursales, deberíamos obtener algo como esto:

Que es el XML que ha devuelto nuestro servicio, y que tiene buena pinta. Aunque aún no hemos hecho AJAX, sino como mucho,
"JAX". Pero antes de añadir la "A" de Asíncrono, vamos a demostrar para qué sirve esto de hacer las cosas asíncronas.

Hacer que la llamada al servicio tarde un poco

Cuando el servicio responde casi inmediatamente, no se nota la diferencia entre síncrono y asíncrono. Así que para que se note
vamos a introducir un retraso en la respuesta del servidor. Además, para hacer esto no vamos a modificar el servicio, sino que
vamos a usar una útil utilidad de Axis llamada TCPMon. Su función principal es la de permitirnos ver el tráfico entre nuestros
clientes y nuestros servidores, lo cual es impagable cuando hay algún problema. Pero además nos permite simular una
conexión lenta, que es justo lo que queremos.

El TCPMon hace de intermediario: se pone a escuchar en un puerto, y todo lo que recibe por él lo redirecciona a otro puerto,
posiblemente de otra máquina. La idea es que el cliente hable con el TCPMon en vez de con el servicio, y que el TCPMon sea el
que hable con el servicio, pudiendo así hacer cosas interesantes en el tráfico. TCPMon viene con Axis, y como nuestro servicio
se ejecuta con Axis, pues ya lo tenemos. Para arrancarlo se puede usar la siguiente línea de comandos:

java -cp "(directorio lib de nuestro servicio)\axis.jar" org.apache.axis.utils.tcpmon

Donde, claro, (directorio lib de nuestro servicio) es el directorio WEB-INF/lib de la aplicación web de nuestro servicio. Si estás
usando Eclipse, estará en tu workspace de Eclipse y será algo como
.metadata\.plugins\org.eclipse.wst.server.core\tmp0\webapps\sucursales\WEB-INF\lib. Si estás usando tu propio
Tomcat, pues ya deberías saber dónde está.

Una vez arrancado, TCPMon nos muestra esto:

10 de 24 02/11/2006 11:23
Tutoriales en AdictosAlTrabajo: Java, J2EE, Visual C++, Linux, UML... http://www.adictosaltrabajo.com/tutoriales/tutoriales.php?pagina=AJ...

Tenemos que poner un Listen port # que no esté usado, por ejemplo el 8666, que es donde va a escuchar TCPMon y donde
nuestro cliente se conectará. Y en el Listener Target Port # ponemos el puerto donde está nuestro servicio, por ejemplo 8081.
Y luego le damos al botón Add:

11 de 24 02/11/2006 11:23
Tutoriales en AdictosAlTrabajo: Java, J2EE, Visual C++, Linux, UML... http://www.adictosaltrabajo.com/tutoriales/tutoriales.php?pagina=AJ...

Pinchando en la nueva pestaña Port 8666 veremos la traza de mensajes:

12 de 24 02/11/2006 11:23
Tutoriales en AdictosAlTrabajo: Java, J2EE, Visual C++, Linux, UML... http://www.adictosaltrabajo.com/tutoriales/tutoriales.php?pagina=AJ...

Aquí conviene marcar la opción XML format, que nos muestra los mensajes XML de forma más legible.

Para probar que TCPMon funciona, nos conectamos con nuestro navegador a sucursales.html con el mismo URL que antes, pero
ahora con el puerto 8666 para que sea TCPMon quien la cargue, y por tanto el tráfico subsequente de XMLHttpRequest pase
por él:

Si vemos la pantalla del TCPMon veremos el tráfico entre el navegador y el servidor, que en este caso no es XML sino HTML.
Como no nos interesa, le podemos dar al botón Remove All para no liarnos. Luego, le damos a nuestro Buscar sucursales. La
aplicación funciona igual que antes, pero en TCPMon vemos que tenemos un registro de los mensajes XML intercambiados:

13 de 24 02/11/2006 11:23
Tutoriales en AdictosAlTrabajo: Java, J2EE, Visual C++, Linux, UML... http://www.adictosaltrabajo.com/tutoriales/tutoriales.php?pagina=AJ...

Esto está bien, pero lo que queríamos ver era qué pasaba cuando teníamos un servidor lento. Para ello, volvemos al TCPMon, le
damos a Close para cerrar el Port 8666 y volvemos a definirlo, pero esta vez marcando la opción Simulate slow connection:

14 de 24 02/11/2006 11:23
Tutoriales en AdictosAlTrabajo: Java, J2EE, Visual C++, Linux, UML... http://www.adictosaltrabajo.com/tutoriales/tutoriales.php?pagina=AJ...

Y le damos a Add, claro. Si ahora volvemos a nuestra aplicación y repetimos el Buscar sucursales, pues va a funcionar igual,
sólo que bastante más lento. Y, como resulta que nuestra aplicación hace peticiones síncronas y no asíncronas, pues el
navegador entero se cuelga hasta que recibimos la respuesta. Ni la aplicación puede mostrar ningún mensaje de estado o
avance, ni el usuario puede pulsar ningún botón de cancelar, ni cambiar de página, ni cerrar el navegador de forma normal.
Después de tantos años acostumbrados a que aunque las páginas tarden en cargarse uno puede hacer lo que quiera, pues esto
resulta extraño. Y como es fácilmente mejorable, pues vamos a hacerlo con la invocación asíncrona.

Recoger el resultado del servicio de forma asíncrona

Para ello tenemos que utilizar XMLHttpRequest de forma diferente, y procesar el resultado no justo después de llamarlo, sino
en una función Javascript separada. Así que vamos a hacer unos cuantos cambios:

1. Primero modificamos sucursales.html para añadir una nueva variable global, AsyncReq, que guarde nuestra petición
entre que la enviamos y la recibimos:

<script>

var AsyncReq;

function buscaSucursales()
{

2. Luego cambiamos nuestra función buscaSucursales, para que inicie la petición pero no procese el resultado:

function buscaSucursales()
{
var smsg=
'<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">'+
'<SOAP-ENV:Body>'+
'<bs:buscaSucursalesRequest xmlns:bs="http://banquito.com/Sucursales/">'+
'<parteCodPostal>'+ws_escapeXML(document.formSucu.cp.value)+'</parteCodPostal>'+
'</bs:buscaSucursalesRequest>'+

15 de 24 02/11/2006 11:23
Tutoriales en AdictosAlTrabajo: Java, J2EE, Visual C++, Linux, UML... http://www.adictosaltrabajo.com/tutoriales/tutoriales.php?pagina=AJ...

'</SOAP-ENV:Body>'+
'</SOAP-ENV:Envelope>';

AsyncReq=ws_newXMLHttpRequest();

var endpoint=getURLServicio();
AsyncReq.onreadystatechange=asyncRecv;
AsyncReq.open("POST",endpoint,true);
AsyncReq.setRequestHeader("SOAPAction",'"http://banquito.com/Sucursales/buscaSucursales"');
AsyncReq.send(smsg);
}

3. Como vemos, el mayor cambio es que no procesamos el resultado, puesto que éste será notificado más adelante cuando
haya alguno. Ese true en vez del false en la llamada a AsyncReq.open precisamente indica proceso asíncrono. Además,
AsyncReq.onreadystatechange especifica cuál será la función Javascript que será invocada cuando haya cambios en el
estado de la petición. En nuestro caso hemos puesto asyncRecv, y por tanto hemos de crear esa función en
sucursales.html:

function asyncRecv()
{
if (AsyncReq.readyState == 4)
{
var req=AsyncReq;
AsyncReq=null;
try
{
var sucursalesResponse=ws_processResponseBody(req);
alert("Resultado:"+sucursalesResponse.xml);
}
catch (e)
{
alert("Error invocando al servicio en "+getURLServicio()+":"+e);
}
}
}

Los distintos valores de AsyncReq.readyState indican diferentes cosas; el 4 significa que se ha completado la recepción
del resultado y por tanto es el que nos interesa.

4. En asyncRecv se utiliza una nueva función, ws_processResponseBody, que procesa el resultado de la petición y, si es
exitoso, devuelve el primer hijo del Body del mensaje SOAP recibido. Es ahí donde debería estar siempre la información
útil, y donde está en nuestro caso. En caso de encontrar cualquier error, ws_processResponseBody lanza una excepción
de Javascript, que es lo que caza el try..catch de asyncRecv. Esta función ws_processResponseBody la añadimos en
ws.js. Podríamos hacerla más sencilla para empezar, pero vamos a meterle ya todo el control de errores apropiado:

function ws_processResponseBody(req)
{
var error,firstBodyChild;
if (req.responseXML && req.responseXML.documentElement)
error=ws_getFault(req.responseXML.documentElement);
if (!error)
{
if (req.status !=200)
error="El servidor devuelve error HTTP "+req.status+" "+req.statusText;
else if (!req.responseXML || !req.responseXML.documentElement)
error="El servidor no ha devuelto una respuesta XML";
}
if (!error)
{
firstBodyChild=ws_getFirstChildElement(ws_getChildElement(req.responseXML.documentElement,"Body"));
if (!firstBodyChild)
error="El servidor no ha devuelto un mensaje SOAP correcto";
}
if (error)
throw new Error(error);
return firstBodyChild;
}

Esta función hace lo siguiente:

1. Primero verifica si la respuesta es XML y contiene una "SOAP Fault". Las SOAP Fault son el mecanismo estándar en
SOAP para informar de errores, así que si la hay, ws_processResponseBody extrae el error de ahí mediante la
función adicional ws_getFault, que luego crearemos.
Por cierto, a la hora de crear servicios web, por favor utiliza para informar de errores este mecanismo de SOAP
Faults, en vez de inventarte nuevos campos donde se informe del resultado. Eso mejorará la interoperabilidad de
tu servicio con herramientas y toolkits.
2. Si no hay SOAP Fault (o la respuesta no es XML), entonces ws_processResponseBody comprueba si el servidor ha
devuelto un estado HTTP distinto de 200 (éxito), o si la respuesta no es XML, en cuyo caso da diferentes errores.
3. Si sigue sin haber problemas, entonces ws_processResponseBody obtiene el "Body" del mensaje y saca su primer

16 de 24 02/11/2006 11:23
Tutoriales en AdictosAlTrabajo: Java, J2EE, Visual C++, Linux, UML... http://www.adictosaltrabajo.com/tutoriales/tutoriales.php?pagina=AJ...

hijo. Si no lo hay, genera otro error, y si lo hay, eso es lo que se devuelve al llamante.

5. Como vemos, hacen falta unas cuantas funciones más a añadir a ws.js. Empecemos por ws_getFault:

function ws_getFault(env)
{
var fault=ws_getChildElement(ws_getChildElement(env,"Body"),"Fault");
if (fault)
{
var eFaultCode=ws_getChildElement(fault,"faultcode");
var eFaultString=ws_getChildElement(fault,"faultstring");
var faultCode=eFaultCode?ws_getElementText(eFaultCode):"";
var faultString=eFaultString?ws_getElementText(eFaultString):"";
return "Error devuelto por el servidor: ("+faultCode+") "+faultString;
}

return null;
}

Esta función busca un hijo "Fault" en el "Body" y si lo encuentra compone un mensaje de error a partir de él.

6. Y por fin ya sólo nos quedan esas funciones que tanto ws_getFault como ws_processResponseBody utilizan, como
ws_getChildElement y ws_getElementText. Ésas son parte de mi típico toolkit de mejora del API del DOM XML, que es
muy estándar y ubicuo pero bastante engorroso de usar. Así que vamos a añadirlas a ws.js:

function ws_getLocalName(e)
{
// Moz: localName, IE: baseName
return e.localName?e.localName:e.baseName;
}

function ws_getChildElement(e,nombre)
{
if (!e)
return null;

var child;
for (child=e.firstChild;child;child=child.nextSibling)
{
if (child.nodeType==ELEMENT && ws_getLocalName(child)==nombre)
return child;
}

return null;
}

function ws_getFirstChildElement(e)
{
if (!e)
return null;

var children=e.childNodes;
var i;
for (i=0;i<children.length;++i)
{
var child=children.item(i);
if (child.nodeType==ELEMENT)
return child;
}
return null;
}

function ws_getElementText(element)
{
if (!element)
return null;

var text="";
var node;
for (node=element.firstChild;node!=null;node=node.nextSibling)
{
if (node.nodeType==TEXT)
text+=node.nodeValue;
}
return text;
}

Y también, al principio de ws.js, estas dos declaraciones de utilidad:

var ELEMENT=1;

17 de 24 02/11/2006 11:23
Tutoriales en AdictosAlTrabajo: Java, J2EE, Visual C++, Linux, UML... http://www.adictosaltrabajo.com/tutoriales/tutoriales.php?pagina=AJ...

var TEXT=3;

Estas funciones permiten un acceso rápido y sencillo a los hijos de elementos de un DOM, o al menos de una forma más
sencilla que con el DOM estándar. Funcionan tanto en Internet Explorer como en Firefox. Es importante notar que estas
funciones ignoran el namespace de los elementos. Esto es bastante práctico pues en realidad los namespaces son
un incordio; pero también son necesarios para no confundir elementos. No obstante, al menos en aplicaciones sencillas
como estas es práctico hacer esta simplificación. En cualquier caso, costaría poco hacer versiones similares de estas
funciones que tuviesen en cuenta el namespace.

Si conseguimos hacer todos esos cambios correctamente (recuerda que también puedes descargarte la aplicación final
completa), nos conectamos a la aplicación. Para que no moleste, te aconsejo que dejes de usar TCPMon (ej. volver a usar el
puerto 8081), o que le quites el retardo; más adelante ya veremos cómo la asíncronía mejora la aplicación para servidores
lentos.

Ahora, tras introducir un nuevo código postal y pulsar Buscar sucursales (recuerda refrescar la página para que recargue los
cambios) nuestra aplicación AJAX nos debería mostrar un mensaje como este:

O sea, parecido a lo anterior, pero ahora se muestra el contenido de Body en vez de todo el mensaje. Vamos a comprobar que
la gestión de errores funciona. Para ello, vamos a introducir deliberadamente errores en sucursales.html:

Si se envía un mensaje XML incorrecto:

function buscaSucursales()
{
var smsg=
'<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">'+
'<SOAP-ENV:Body>'+
'<bs:buscaSucursalesRequestMAL xmlns:bs="http://banquito.com/Sucursales/">'+
'<parteCodPostal>'+ws_escapeXML(document.formSucu.cp.value)+'</parteCodPostal>'+
'</bs:buscaSucursalesRequestMAL>'+
'</SOAP-ENV:Body>'+
'</SOAP-ENV:Envelope>';

Nuestra aplicación nos tendría que mostrar el siguiente error:

Que sale de una SOAP Fault que nos ha devuelto AXIS.

Si invocamos al URL incorrecto:

function getURLServicio()
{
var url=
document.location.protocol+"//"+document.location.host+
primParte(document.location.pathname)+
"/MAL/services/SucursalesSOAP";
return url;
}

18 de 24 02/11/2006 11:23
Tutoriales en AdictosAlTrabajo: Java, J2EE, Visual C++, Linux, UML... http://www.adictosaltrabajo.com/tutoriales/tutoriales.php?pagina=AJ...

Que viene del error HTTP 404 que nos ha devuelto Tomcat.

Si tu aplicación se comporta así, enhorabuena (y si no, recuerda que también puedes descargarte la aplicación final completa).

Mostrar un mensaje de estado

Por que se note más la diferencia entre el uso síncrono y el asíncrono, vamos a mostrar un mensaje de estado mientras
estamos esperando que el servidor responda. Podríamos usar la línea de estado del navegador, pero al menos yo nunca la miro
y así no me enteraría de lo que sale ahí, así que vamos a usar un elemento específico que ya viene en sucursales.html:.

<p id=progreso></p>

Así que vamos a rellenarlo apropiadamente:

1. Editamos sucursales.html y añadimos una nueva función:

function setProgreso(msg)
{
document.getElementById('progreso').innerHTML=msg?msg:"";
}

2. Y la llamamos justo tras iniciar la petición:

function buscaSucursales()
{
var smsg=
'<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">'+
'<SOAP-ENV:Body>'+
'<bs:buscaSucursalesRequest xmlns:bs="http://banquito.com/Sucursales/">'+
'<parteCodPostal>'+ws_escapeXML(document.formSucu.cp.value)+'</parteCodPostal>'+
'</bs:buscaSucursalesRequest>'+
'</SOAP-ENV:Body>'+
'</SOAP-ENV:Envelope>';

AsyncReq=ws_newXMLHttpRequest();

var endpoint=getURLServicio();
AsyncReq.onreadystatechange=asyncRecv;
AsyncReq.open("POST",endpoint,true);
AsyncReq.setRequestHeader("SOAPAction",'"http://banquito.com/Sucursales/buscaSucursales"');
AsyncReq.send(smsg);

setProgreso("Consultando al servidor, espere...");


}

3. Y cuando finaliza:

function asyncRecv()
{
if (AsyncReq.readyState == 4)
{
var req=AsyncReq;
AsyncReq=null;
try
{
var sucursalesResponse=ws_processResponseBody(req);
setProgreso(null);
alert("Resultado:"+sucursalesResponse.xml);
}
catch (e)
{
var msg="Error invocando al servicio en "+getURLServicio()+":"+e;
setProgreso(msg);
alert(msg);
}
}
}

Y ahora vamos a volver a poner TCPMon con el retraso, igual que antes. Así podemos ver cómo se comporta la aplicación
cuando el servidor es lento. Cuando pinchamos en Buscar sucursales vemos que se muestra el mensaje de estado, y seguimos
pudiendo interactuar con la aplicación:

19 de 24 02/11/2006 11:23
Tutoriales en AdictosAlTrabajo: Java, J2EE, Visual C++, Linux, UML... http://www.adictosaltrabajo.com/tutoriales/tutoriales.php?pagina=AJ...

Y cuando por fin llega la respuesta, el mensaje de estado se limpia. De hecho, la aplicación es tan interactiva que nos permite
volver a pulsar Buscar sucursales, lo cual hace que se pierda la petición original y se inicie una nueva (lo podemos ver en la
traza del TCPMon - no tiene por qué crearse una nueva conexión HTTP, sino que puede que se reutilice alguna otra). No
obstante este aparente paralelismo de peticiones, hay que ser consciente de que XMLHttpRequest no permite manejar a la
vez más de una petición. O sea, es el mismo comportamiento que si en una página HTML normal le damos al botón de
Submit una y otra vez. El que esto sea correcto o no depende de la semántica de nuestra aplicación, aunque en la mayoría de
casos no será aconsejable que ocurra. Aunque gestionar esto no es tan sencillo (la forma más adecuada sería en el servicio y
usando identificadores únicos de mensaje con sentido), aquí simplemente lo vamos a evitar en el propio sucursales.html,
ignorando pulsaciones sucesivas de Buscar sucursales hasta que la primera haya finalizado:

function buscaSucursales()
{
if (AsyncReq)
return;

var smsg=

Como ya estamos poniendo AsyncReq=null al procesar la respuesta, con eso debería bastar.

Mostrando los resultados en la pantalla


Para concluir nuestra aplicación AJAX ya sólo nos queda mostrar los resultados devueltos por el servicio en la pantalla; no con
un feo alert sino en el HTML. Esto al final es puro HTML dinámico y está más trillado que el AJAX, pero sin él no hay AJAX que
valga así que vamos a ello. Por cada sucursal devuelta debemos mostrar una fila en la tabla de Sucursales encontradas. A mí
personalmente no me gusta crear HTML desde código Javascrit (ni Java), así que prefiero trabajar con plantillas: escribo el
HTML con un editor de texto, y luego lo reutilizo desde el código. Es por eso que la tabla de sucursales.html viene con una
fila-plantilla:

<p>Sucursales encontradas:</p>

<table id=ListaSucursales border=1>


<tr>
<th>Código postal</th>
<th>Dirección</th>
<th>Cód. sucursal</th>
</tr>
<tr>
<td align=right></td>
<td></td>
<td align=right></td>
</tr>
</table>

La primera fila (de <TH>) es la cabecera, pero la siguiente es una plantilla que indica cómo deben mostrarse en general todas
las filas de la tabla. Con esto ya podemos modificar sucursales.html:

1. Lo primero que hay que hacer es copiarse esa plantilla en una variable para luego poder reproducirla:

var PlantillaLinea;

function init()

20 de 24 02/11/2006 11:23
Tutoriales en AdictosAlTrabajo: Java, J2EE, Visual C++, Linux, UML... http://www.adictosaltrabajo.com/tutoriales/tutoriales.php?pagina=AJ...

{
PlantillaLinea=document.getElementById("ListaSucursales").rows.item(1);
PlantillaLinea.parentNode.removeChild(PlantillaLinea);
}

</script>

</head>

<body onload="init()">

<h1>Banquito - búsqueda de sucursales</h1>

Y además la quitamos de la tabla, pues no tiene sentido que siga ahí.

2. Luego, desde asyncRecv tenemos que causar la carga esa tabla:

function asyncRecv()
{
if (AsyncReq.readyState == 4)
{
var req=AsyncReq;
AsyncReq=null;
try
{
var sucursalesResponse=ws_processResponseBody(req);
var msg=processSucursales(sucursalesResponse);
setProgreso(msg);
}
catch (e)
{
var msg="Error invocando al servicio en "+getURLServicio()+":"+e;
setProgreso(msg);
alert(msg);
}
}
}

3. Por supuesto, lo siguiente es crear esa función processSucursales:

function processSucursales(sucursalesResponse)
{
limpiaSucursales();
var msg=null;
var eSucursales=ws_getChildElement(sucursalesResponse,"sucursalesEncontradas");
if (!eSucursales)
throw new Error("La respuesta del servidor no contiene el elemento 'sucursalesEncontradas'");
else
{
var eInfoSucursal,ns;
for (eInfoSucursal=eSucursales.firstChild,ns=0;
eInfoSucursal;
eInfoSucursal=eInfoSucursal.nextSibling,++ns)
{
if (eInfoSucursal.nodeType==ELEMENT)
{ // Elemento; debería ser una sucursal
var codSucursal=ws_getElementText(ws_getChildElement(eInfoSucursal,"codSucursal"));
var direccion=ws_getElementText(ws_getChildElement(eInfoSucursal,"direccion"));
var codPostal=ws_getElementText(ws_getChildElement(eInfoSucursal,"codPostal"));
muestraSucursal(codSucursal,direccion,codPostal);
}
}
msg=ns+" sucursales encontradas";
}
return msg;
}

4. Y también las otras dos funciones que sucursalesResponse utiliza:

function limpiaSucursales()
{
var ls=document.getElementById("ListaSucursales");
while (ls.rows.length>1)
ls.deleteRow(1);
}

function muestraSucursal(codSucursal,direccion,codPostal)
{
var ls=document.getElementById("ListaSucursales");
var row=PlantillaLinea.cloneNode(true);

21 de 24 02/11/2006 11:23
Tutoriales en AdictosAlTrabajo: Java, J2EE, Visual C++, Linux, UML... http://www.adictosaltrabajo.com/tutoriales/tutoriales.php?pagina=AJ...

ls.tBodies.item(0).appendChild(row);
row.cells.item(0).innerHTML=codPostal;
row.cells.item(1).innerHTML=direccion;
row.cells.item(2).innerHTML=codSucursal;
}

Con todo esto, ya nuestra aplicación AJAX debería tener un bonito comportamiento:

Y por cierto, debería funcionar perfectamente también en Firefox:

¡Ya tenemos nuestra aplicación AJAX terminada! Si hay algo que no te funcione, puedes descargarte el código completo de
sucursales.html y ws.js.

Conclusiones
En este tutorial hemos visto cómo se puede hacer una aplicación AJAX que acceda a un servicio web SOAP, desde cero,
utilizando únicamente funciones Javascript estándar. Posiblemente no te parezca tan complicado como habías oído. En este
caso no lo es, pero puedes imaginar que la cosa es peor cuando en vez de una única llamada a un servicio tienes del orden de
diez, relacionadas con otros tantos pedazos de la pantalla como combos, campos de entrada, etc, cada uno de los cuales
requiere su propio tratamiento. Además, como a menudo cosas como los combos se comportan de forma muy parecida en
todas las pantallas, esto lleva de forma natural a la creación de controles de interfaz de usuario reutilizables, que desde
hace varios años en el mundo .Net, y por fin parece que ahora también en el mundo Java, se considera la forma más apropiada
de crear aplicaciones web interactivas.

Si te ha gustado esta aplicación AJAX y el servicio de búsqueda de sucursales, también puedes leer este otro tutorial en el cual
se utiliza el producto AmberPoint Express para monitorizar su actividad... con algún resultado inesperado, por cierto.

22 de 24 02/11/2006 11:23
Tutoriales en AdictosAlTrabajo: Java, J2EE, Visual C++, Linux, UML... http://www.adictosaltrabajo.com/tutoriales/tutoriales.php?pagina=AJ...

Puedes opinar sobre este tutorial aquí

Recuerda
que el personal de Autentia te regala la mayoría del conocimiento aquí compartido (Ver todos los tutoriales)

¿Nos vas a tener en cuenta cuando necesites consultoría o formación en tu empresa?

¿Vas a ser tan generoso con nosotros como lo tratamos de ser con vosotros?

info@autentia.com

Somos pocos, somos buenos, estamos motivados y nos gusta lo que hacemos ......
Autentia = Soporte a Desarrollo & Formación

Autentia S.L. Somos expertos en:


J2EE, Struts, JSF, C++, OOP, UML, UP, Patrones de diseño ..
y muchas otras cosas

Nuevo servicio de notificaciones


Si deseas que te enviemos un correo electrónico cuando introduzcamos nuevos tutoriales,
inserta tu dirección de correo en el siguiente formulario.

Subscribirse a Novedades
e-mail

Otros Tutoriales Recomendados (También ver todos)


Nombre Corto Descripción
En este magnífico tutorial, Alberto Carrasco nos enseña los fundamentos y un
XMLEncryption en Java
ejemplo práctico de XMLEncryption.
En este tutorial os mostramos como realizar servicios web utilizando Axis y el
WebServices con Axis y JBoss
contenedor de aplicaciones web JBoss
Ya está disponible la versión Beta del J2SDK 1.5. Os mostramos algunas de las
Novedades en Java 1.5 nuevas características introducidas en el lenguaje Java: Clases genéricas,
enumeraciones, bucles simplificados, etc.
Creación de Webs con Power Creación de páginas web sin necesidad de conocimientos HTML, usando la
WebSite Builder herramienta Power Website Builder
Creación de Un Web básico Como como construir nuestras primeras páginas HTML
Os mostramos como proteger de un modo básico el acceso a recursos dentro de
Seguridad en Tomcat
vuestro servidor de componentes Tomcat
Si quieres ver de un modo visual como crear un documento XML, este es tu tutorial.
XML básico Este es el primero de un conjunto de tutoriales que iremos publicando sobre esta
fascinante y amplia tecnología

23 de 24 02/11/2006 11:23
Tutoriales en AdictosAlTrabajo: Java, J2EE, Visual C++, Linux, UML... http://www.adictosaltrabajo.com/tutoriales/tutoriales.php?pagina=AJ...

En este tutorial os enseñamos como formaterar documentos XML directamente en


XML y XSL en Cliente vuestro navegador a través de Plantillas XSL. En cursos sucesivos veremos como
hacerlo en el servidor, para no crear dependencias con el navegador del cliente.
Procesamiento XML en Java con Os mostramos como instalar la versión 1.6 de WSDP y como procesar los ficheros
JAXB y WSDP 1.6 XML con uno de sus componentes, JAXB
En este tutorial os mostramos los fundamentos de la tecnología AJAX, junto a un
Introducción a la tecnología AJAX ejemplo y orientación sobre su utilización en el desarrollo de aplicaciones web en
general

Nota: Los tutoriales mostrados en este Web tienen como objetivo la difusión del
conocimiento.

Los contenidos y comentarios de los tutoriales son responsabilidad de sus respectivos


autores.

En algún caso se puede hacer referencia a marcas o nombres cuya propiedad y derechos es
de sus respectivos dueños. Si algún afectado desea que incorporemos alguna reseña
específica, no tiene más que solicitarlo.

Si alguien encuentra algún problema con la información publicada en este Web, rogamos que
informe al administrador rcanales@adictosaltrabajo.com para su resolución.

Patrocinados por enredados.com .... Hosting en Castellano con soporte Java/J2EE

www.AdictosAlTrabajo.com Opimizado 800X600

24 de 24 02/11/2006 11:23

Vous aimerez peut-être aussi