Académique Documents
Professionnel Documents
Culture Documents
Somos su empresa de
www.adictosaltrabajo.com
jcamara@softwareag.es
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
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.
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...
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.
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:
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.
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...
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:
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>
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,
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...
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+=">";
else if (c=='<')
s1+="<";
else if (c=='&')
s1+="&";
else
s1+=c;
}
return s1;
}
<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:
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;
}
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.
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.
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:
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á.
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...
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.
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;
}
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;
}
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:
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>';
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).
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>
function setProgreso(msg)
{
document.getElementById('progreso').innerHTML=msg?msg:"";
}
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);
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.
<p>Sucursales encontradas:</p>
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()">
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);
}
}
}
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;
}
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:
¡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...
Recuerda
que el personal de Autentia te regala la mayoría del conocimiento aquí compartido (Ver todos los tutoriales)
¿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
Subscribirse a Novedades
e-mail
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...
Nota: Los tutoriales mostrados en este Web tienen como objetivo la difusión del
conocimiento.
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.
24 de 24 02/11/2006 11:23