• No se han encontrado resultados

Qué ofrece Autentia Real Business Solutions S.L?

N/A
N/A
Protected

Academic year: 2021

Share "Qué ofrece Autentia Real Business Solutions S.L?"

Copied!
25
0
0

Texto completo

(1)

tel./fax: +34 91 675 33 06

[email protected] - www.autentia.com

Somos su empresa de Soporte a Desarrollo Informático.

Ese apoyo que siempre quiso tener...

1. Desarrollo de componentes y

proyectos a medida

Tecnología Desarrollo Sistemas

Gran Empresa

Producción

autentia Certificación o Pruebas Verificación previa

RFP

Concurso

Consultora 1

Consultora 2

Consultora 3

Equipo propio desarrollo

Piloto

3a

3b

1. Definición de frameworks corporativos.

2. Transferencia de conocimiento de nuevas arquitecturas.

3. Soporte al arranque de proyectos.

4. Auditoría preventiva periódica de calidad.

5. Revisión previa a la certificación de proyectos.

6. Extensión de capacidad de equipos de calidad.

7. Identificación de problemas en producción.

3. Arranque de proyectos basados en nuevas

tecnologías

¿Qué ofrece Autentia Real

Business Solutions S.L?

Para más información visítenos en:

www.autentia.com

Compartimos nuestro conociemiento en:

Gestor portales (Liferay)

Gestor de contenidos (Alfresco)

Aplicaciones híbridas

Tareas programadas (Quartz)

Gestor documental (Alfresco)

Inversión de control (Spring)

BPM (jBPM o Bonita)

Generación de informes (JasperReport)

ESB (Open ESB)

Control de autenticación y

acceso (Spring Security)

UDDI

Web Services

Rest Services

Social SSO

SSO (Cas)

Spring MVC, JSF-PrimeFaces /RichFaces,

HTML5, CSS3, JavaScript-jQuery

JPA-Hibernate, MyBatis

Motor de búsqueda empresarial (Solr)

ETL (Talend)

Dirección de Proyectos Informáticos.

Metodologías ágiles

Patrones de diseño

TDD

2. Auditoría de código y recomendaciones de mejora

(2)

Home | Quienes Somos | Empleo | Tutoriales | Contacte

Autor: Javier Cámara [email protected]

Descargar este documento en formato PDF AJAXaMano.pdf Firma en nuestro libro de Visitas

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 ([email protected])

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

Java & SOAP Editing Tool Edit SOAP, JSP, WSDL, DTD, Schema Easy-to-Use. Download Free Trial!

www.Altova.com/XMLSpy

J2EE Survey Software Used by Cisco, Kaiser, Viacom, Govt Visual editor, scripts, extensible

www.cogix.com

Master Java J2ee Oracle 100% alumnos ya trabajan. Nuevo temario de Struts + J2ME. www.grupoatrium.com

The SCORM Experts The quickest and easiest way to become SCORM conformant. www.scorm.com

(3)

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:

Realiza todo el tutorial donde se crea el servicio, instalando Eclipse, Tomcat etc; o 1.

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.

2.

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

(4)
(5)

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:

(6)

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>

(7)

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;">

1.

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> 2.

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,

Creamos un nuevo fichero: 1.

(8)

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) { 2.

(9)

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

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> 3.

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:

(10)

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:

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í. 1.

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:

(11)

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:

(12)

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:

(13)
(14)

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:

(15)

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:

(16)

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:

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() {

1.

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>'+ 2.

(17)

'</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); }

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.

3.

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:

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.

1.

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. 2.

Si sigue sin haber problemas, entonces ws_processResponseBody obtiene el "Body" del mensaje y saca su primer 3.

(18)

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

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. 5.

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;

(19)

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; }

(20)

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:

Editamos sucursales.html y añadimos una nueva función: function setProgreso(msg)

{

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

1.

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..."); } 2. 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); } } } 3.

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:

(21)

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:

Lo primero que hay que hacer es copiarse esa plantilla en una variable para luego poder reproducirla: var PlantillaLinea;

function init() 1.

(22)

{ 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í. 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); } } } 2.

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; } 3.

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); 4.

(23)

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.

(24)

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? [email protected]

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

XMLEncryption en Java En este magnífico tutorial, Alberto Carrasco nos enseña los fundamentos y un

ejemplo práctico de XMLEncryption.

WebServices con Axis y JBoss En este tutorial os mostramos como realizar servicios web utilizando Axis y el

contenedor de aplicaciones web JBoss

Novedades en Java 1.5

Ya está disponible la versión Beta del J2SDK 1.5. Os mostramos algunas de las nuevas características introducidas en el lenguaje Java: Clases genéricas, enumeraciones, bucles simplificados, etc.

Creación de Webs con Power WebSite Builder

Creación de páginas web sin necesidad de conocimientos HTML, usando la herramienta Power Website Builder

Creación de Un Web básico Como como construir nuestras primeras páginas HTML

Seguridad en Tomcat Os mostramos como proteger de un modo básico el acceso a recursos dentro de

vuestro servidor de componentes Tomcat

XML básico

Si quieres ver de un modo visual como crear un documento XML, este es tu tutorial. Este es el primero de un conjunto de tutoriales que iremos publicando sobre esta fascinante y amplia tecnología

(25)

XML y XSL en Cliente

En este tutorial os enseñamos como formaterar documentos XML directamente en 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 JAXB y WSDP 1.6

Os mostramos como instalar la versión 1.6 de WSDP y como procesar los ficheros XML con uno de sus componentes, JAXB

Introducción a la tecnología AJAX

En este tutorial os mostramos los fundamentos de la tecnología AJAX, junto a un 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 [email protected] para su resolución.

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

Referencias

Documento similar

37 El TPI, en los fundamentos jurídicos del 149 al 154 de la sentencia «Virgia- micina», examinó las dos actividades complementarias que integran la evaluación de riesgos:

Volviendo a la jurisprudencia del Tribunal de Justicia, conviene recor- dar que, con el tiempo, este órgano se vio en la necesidad de determinar si los actos de los Estados

Y en el caso específico del CEDH, valor orientativo mediado por la jurisprudencia del TEDH (6). El derecho a la inviolabilidad del domicilio que proclama el artículo 18.2 CE

Guardar el archivo: Picando sobre el icono o Archivo/Guardar, aunque es bueno habituarse a utilizar Guardar como nuevo programa (el guardar como de siempre), es decir,

The LOMCE specifies that the education system must promote “Values that are pillars of democracy and human rights, including, in any case, the prevention of gender violence

Tras establecer un programa de trabajo (en el que se fijaban pre- visiones para las reuniones que se pretendían celebrar los posteriores 10 de julio —actual papel de los

Por PEDRO A. EUROPEIZACIÓN DEL DERECHO PRIVADO. Re- laciones entre el Derecho privado y el ordenamiento comunitario. Ca- racterización del Derecho privado comunitario. A) Mecanismos

En el capítulo de desventajas o posibles inconvenientes que ofrece la forma del Organismo autónomo figura la rigidez de su régimen jurídico, absorbentemente de Derecho público por