Mostrando entradas con la etiqueta SOAP. Mostrar todas las entradas
Mostrando entradas con la etiqueta SOAP. Mostrar todas las entradas

lunes, 14 de junio de 2010

Como devolver arreglos y estructuras de datos en SOAP transparentemente, utilizando PHP y NuSOAP


¡Saludos nuevamente blogueros!

En este artículo veremos como resolver una duda que apareció hace un tiempo, acerca del envío de datos por servicios web utilizando la biblioteca NuSOAP de PHP. En un artículo pasado, hablamos de como implementar un servicio web desde PHP utilizando esta biblioteca; este servicio contaba con dos métodos, uno de ellos devolvía una cadena y el otro un arreglo asociativo de PHP, se pudo consumir el servicio desde un cliente AJAX sin problema procesando el código XML directamente, sin embargo, al trabajar de esta manera se pierde uno de los puntos importantes para utilizar protocolos de comunicación entre aplicaciones: la transparencia. Es decir, al consumir el servicio el cliente no pudo extraer la estructura de datos recibida, sin necesidad de darle un tratamiento al XML de la respuesta y teniendo que saber de antemano cuál era la estructura XML que recibiría.

Ahora, la idea es corregir este problema de transparencia, al mismo tiempo que mostramos como mandar diferentes tipos de datos complejos, como son: estructuras, arreglos y arreglos de estructuras.

Manos a la obra

Para poder llegar a este nivel de transparencia entre aplicaciones es que el protocolo SOAP utiliza el WSDL, el cual además de contener la definición de cada uno de los métodos que posee un servicio web, puede tener también la definición de tipos de datos complejos. Un tipo de dato complejo consiste una colección de valores agrupados dentro de una estructura, y dentro de este tipo de datos es que encontramos los objetos y arreglos.

Podemos definir estos datos complejos utilizando la clase soap_server de la biblioteca NuSOAP. Como recordarán -y si no es así lo menciono- esta clase nos permite crear servicios web en nuestro servidor para que lo consuman los clientes. Dentro de esta clase se encuentra un objeto llamado wsdl, definido como una propiedad de la misma clase, el cual posee una serie de métodos para definir las características del WSDL del servicio web. El método que emplearemos concretamente para definir los nuevos tipos de datos será el addComplexType. El primer paso consistirá en instanciar la clase soap_server de la siguiente forma:

require_once('nusoap.php');
$servidor = new soap_server;


Una vez hecho esto contamos con un objeto de esta clase, para el cual podemos definir sus características:

$ns = "http://nuestroespaciodenombres";
$servidor->configureWSDL('NombreDelServicio',$ns);
$servidor->wsdl->schematargetnamespace = $ns;


Ahora viene lo bueno, definir tipos de datos complejos para que sean devueltos por los métodos de nuestro servicio. Para este ejemplo definiremos 3 estructuras diferentes para probar las posibilidades que nos ofrece esta clase:

  • Primero un arreglo de cadenas sencillo:

$servidor->wsdl->addComplexType(
        'ArregloDeCadenas',
        'complexType',
        'array',
        'sequence',
        'http://schemas.xmlsoap.org/soap/encoding/:Array',
        array(),
        array(array('ref' => 'http://schemas.xmlsoap.org/soap/encoding/:arrayType',
         'wsdl:arrayType' => 'xsd:string[]')
        ),
        'xsd:string'  
);


Como vemos, se incluye una referencia(http://schemas.xmlsoap.org/soap/encoding/:Array) a la definición del formato que debe tener un dato de tipo complejo Array.

  • Después una estructura de datos, similar a los arreglos asociativos, que contendrá una serie de valores distinguidos por un nombre:


  • $servidor->wsdl->addComplexType(
            'Estructura',
            'complexType',
            'struct',
            'all',
            '',
            array(
            'Nombre' => array('name' => 'Nombre', 'type' => 'xsd:string'),
            'Apellidos'=>array('name' => 'Apellidos', 'type' => 'xsd:string'),
            'Edad'=>array('name' => 'Edad', 'type' => 'xsd:integer')
            )
    );


  • Finalmente -y lo más interesante-, un arreglo de estructuras de datos:

$servidor->wsdl->addComplexType(
        'ArregloDeEstructuras',
        'complexType',
        'array',
        'sequence',
        'http://schemas.xmlsoap.org/soap/encoding/:Array',
        array(),
        array(array('ref' => 'http://schemas.xmlsoap.org/soap/encoding/:arrayType',
         'wsdl:arrayType' => 'tns:Estructura[]')
        ),
        'tns:Estructura'  
);


Digo que el último ejemplo me parece el más interesante porque con este podemos definir datos de entrada y de salida para los métodos que consistirán en listas de estructuras de datos, similares a las que encontramos en muchos lenguajes de programación de alto nivel y sobre todo muy útiles para representar registros de bases de datos y archivos estructurados.

Para incluir estos datos de entrada y de salida de los métodos, basta con declararlo en el método registrer donde se declaran los métodos, y asegurarnos por supuesto de que la estructura de los parámetros de estos métodos que hagan referencia los tipos de datos complejos, corresponda con la estructura declarada, es decir:

function consultaPersonas($param) {
    $arreglo = array();
    $arreglo[] = array('Nombre'=>"Juan", 'Apellidos'=>"Torres", 'Edad'=>18);
    $arreglo[] = array('Nombre'=>"Teresa", 'Apellidos'=>"Jiménez Sánchez", 'Edad'=>19);
    $arreglo[] = array('Nombre'=>"Efraín", 'Apellidos'=>"Ovalles López", 'Edad'=>22);
    return $arreglo;
}

$servidor->register('consultaPersonas',
    array('param'=>'xsd:string'),
    array('return'=>'tns:ArregloDeEstructuras'),
    $ns
);



Si por ejemplo creamos un método que devuelva el tipo de dato complejo ArregloDeEstructuras, en el código de nuestro método deberemos incluir una estructura de datos equivalente, en este caso un arreglo asociativo, donde cada llave del arreglo corresponde al identificador de los datos de nuestra estructura previamente definida -llamada Estructura en este ejemplo-; así en el método, cada entrada del arreglo contendrá a la vez un arreglo asociativo, que corresponderá a esta estructura de datos.

De esta forma es posible crear servicios web que devuelvan a nuestras aplicaciones arreglos de estructuras de datos, e incluso arreglos de arreglos, lo cual ayudará a que nuestros servicios puedan generar mayor variedad de datos de salida, y de esta forma aumentaremos el poder las capacidades nuestras aplicaciones. Además de esto, se logra mucha mayor transparencia, ya que cualquier aplicación de terceros o que nosotros mismos desarrollemos, solo tendrá que seguir el formato definido en el WSDL de nuestros servicios web, para poder desentrañar estos tipos de datos complejos. Algunos entornos de desarrollo -como el de .Net- incluso permiten implementar el consumo de estos servicios web interpretando los tipos de datos complejos como objetos nativos del lenguaje, lo que nos permite manipularlos con mucha mayor facilidad, así que las posibilidades son muchas.

Hasta la próxima.

miércoles, 18 de marzo de 2009

Consumiendo un Servicio Web en PHP desde AJAX, Parte 2: Crear el cliente

Después de una espera un poco larga ya estoy de vuelta para terminar con el tema del servicio Web consumido por AJAX, ahora trabajando del lado del cliente. Antes de comenzar es importante que revisen el tema anterior sobre la creación del servicio Web ya que hice un par de cambios en el código, para agregar un mejor manejo de errores en los métodos (del lado del cliente llamaremos métodos a las funciones PHP del servicio Web). Entonces, ahora nos enfocaremos en el cliente, ¿qué es lo que tendrá que hacer? pues acceder al servicio Web a través de una petición en formato XML, que seguirá el estándar del protocolo SOAP para peticiones, es decir el cliente envía una petición como la siguiente:
<?xml version='1.0' encoding='utf-8'?>
<soap:Envelope xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xmlns:xsd='http://www.w3.org/2001/XMLSchema' xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/'>
<soap:Body>
<buscaAutor xmlns='http://localhost'>
<id xsi:type='xsd:string'>1</id>
</buscaAutor>
</soap:Body>
</soap:Envelope>


En donde la primer etiqueta soap:Envolepe representa el encabezado de una petición SOAP, en el que se establecen los formatos que se utilizarán en la petición, mientras que la etiqueta soap:Body representa el cuerpo de la petición, dentro de la cual se incluyen el nombre del método que se está accediendo (buscaAutor), el namespace del servicio Web (xmlns='http://localhost') y los parámetros que recibirá este método, representando cada parámetro con una etiqueta identificada con el nombre correspondiente. En el ejemplo anterior se envía el parámetro id que es de tipo string y posee el valor de 1.

La respuesta devuelta por el servicio Web tendrá un formato XML que también seguirá el estándar del protocolo SOAP, conteniendo en ella el resultado devuelto por el método al que se accedió, por ejemplo, al acceder al método buscaAutor con un id igual a 1 la respuesta generada es la siguiente:

<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Body>
<ns1:buscaAutorResponse xmlns:ns1="http://locahost">
<nombre xsi:type="xsd:string">Pablo Neruda</nombre>
<nacionalidad xsi:type="xsd:string">Chileno</nacionalidad>
<profesion xsi:type="xsd:string">Poeta</profesion>
<error xsi:type="xsd:string"></error>
</ns1:buscaAutorResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>


En donde el servicio Web responde con un archivo XML que inicia con el encabezdo SOAP-ENV:Envelope para indicar los formatos que esta utilizando, seguido por la etiqueta SOAP-ENV:Body dentro de la cual encontramos el cuerpo de la respuesta, que contiene el resultado de la ejecución del método accedido indicada por la etiqueta ns1:buscaAutorResponse, en la que encontramos los valores devueltos por el resultado, cada uno siendo identificado por las etiquetas nombre, nacionalidad, profesión y error, en esta última podemos ver que no se generó algún mensaje de error al encontrarla vacía.

Entonces nuestro cliente AJAX consistirá en un programa que realizará una petición en formato XML, siguiendo las características que indica protocolo SOAP y que además deberá procesar la respuesta también en formato XML para extraer los datos devueltos y desplegarlos en la pantalla.

Programación del cliente

El cliente accederá al servicio Web ServicioDeAutores y podrá consumir cualquiera de sus dos métodos: buscaAutor y actualizaAutor, para lo cual el archivo HTML de la implementación contendrá un formulario que servirá para la doble tarea de capturar y desplegar los datos que serán enviados y recibidos respectivamente.

En esta ocasión volveré a utilizar la biblioteca Prototype que vimos en un post anterior, para facilitar el acceso a los contenidos de la página desde Javascript, sin embargo, no utilizare la funcionalidad para AJAX que ofrece, debido a que esta me arrojo algunos problemas para enviar los parámetros en formato XML, además tampoco me apoyare en otras bibliotecas para Javascript especializadas en el uso de servicios Web, porque no encontré alguna que fuera lo bastante estándar. En cambio, programaré toda la petición y procesamiento de la respuesta utilizando únicamente la clase XMLHttpRequest, dicho de otra forma lo haré todo a mano.

El código completo del archivo HTML para la implementación del cliente es la siguiente:


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Petición a un servicio Web con Ajax, hecha a mano (como los verdaderos hombres)</title>
<script type="text/javascript" src="prototype-1.6.0.3.js"><!-- Referencia a Prototype--></script>
<script type="text/javascript">
var READY_STATE_COMPLETE=4;
var peticionHttp = null;
function inicializaXhr() { //Función que devuelve el objeto de la clase XMLHttpRequest
    if(window.XMLHttpRequest) {
        return new XMLHttpRequest();
    }
    else if(window.ActiveXObject) {
        return new ActiveXObject("Microsoft.XMLHTTP");
    }
}

function peticionDeBusqueda() { //Función en la que se realiza la petición del método buscaAutor en el servicio Web
    var url = "http://localhost/servicio.php"; //URL del servicio Web
    var ns = "http://localhost"; //Namespace del servicio Web
    var arrParam = creaParamBusqueda(); //Obtenemos los parámetros de la busqueda
    var cadenaXML = creaCadenaXMLDePeticion("buscaAutor",ns,arrParam); //Creamos una cadena con formato XML que contendrá la información de la petición    
    peticionHttp = inicializaXhr();
    if(peticionHttp) {
        modificaBoton("btnBuscar","Buscando...",true); //Deahabilitamos el botón de busqueda
        peticionHttp.onreadystatechange = recibeArreglo; //Especificamos la función que obtendrá el arreglo de datos devuelto por el servicio Web
        peticionHttp.open("POST", url, true); //Específicamos el metódo, url y tipo de la petición
        peticionHttp.setRequestHeader ("SOAPAction", "http://localhost/servicio.php/buscaAutor");  //Agregamos al encabezado de la petición el indicador del protocolo SOAP
        peticionHttp.setRequestHeader ("Content-Type", "text/xml");  //Agregamos al encabezado de la petición que el contenido se trata de un documento XML
        peticionHttp.send(cadenaXML); //Enviamos los parámetros
    }
    $('divRespuesta').innerHTML = ""; //*Nota: El símbolo $ es equivalente a document.getElementById
}

function peticionDeActualizacion() { //Función en la que se realiza la petición del método actualizaAutor en el servicio Web
    var url = "http://localhost/servicio.php"; //URL del servicio Web
    var ns = "http://localhost"; //Namespace del servicio Web
    var arrParam = creaParamEnvio(); //Obtenemos los parámetros del envio de datos para la actualización
    var cadenaXML = creaCadenaXMLDePeticion("actualizaAutor",ns,arrParam); //Creamos el objeto XML que contendrá la información de la petición    
    peticionHttp = inicializaXhr();
    if(peticionHttp) {
        modificaBoton("btnGuardar","Guardando...",true);
        peticionHttp.onreadystatechange = recibeMensaje;
        peticionHttp.open("POST", url, true);
        peticionHttp.setRequestHeader ("SOAPAction", "http://localhost/servicio.php/actualizaAutor");
        peticionHttp.setRequestHeader ("Content-Type", "text/xml");
        peticionHttp.send(cadenaXML);
    }
}

function recibeArreglo() { //Función que manejara la respuesta recibida por el servidor al solicitar el método buscaAutor
    if(peticionHttp.readyState == READY_STATE_COMPLETE) {
        if(peticionHttp.status == 200) {
            var documentoXML = peticionHttp.responseXML;            
            var objResp = {nombre: "", nacionalidad: "", profesion:""}; //Las propiedades identifican el nombre del campo que deseamos extraer del documento
            procesaXML(documentoXML, "buscaAutor", objResp);
            if(!objResp.error) //Verificamos si se obtuvo algún error de la respuesta
            {
                $('txtNombre').value = objResp.nombre;
                $('txtNacionalidad').value = objResp.nacionalidad;
                $('txtProfesion').value = objResp.profesion;
            }
            else
                alert(objResp.error);
            modificaBoton("btnBuscar","Buscar",false);
        }
    }
}

function recibeMensaje() {//Función que manejara la respuesta recibida por el servidor al solicitar el método buscaAutor
    if(peticionHttp.readyState == READY_STATE_COMPLETE) {
        if(peticionHttp.status == 200) {
            var documentoXML = peticionHttp.responseXML;
            var objResp = {mensaje: ""}; //Las propiedades identifican el nombre del campo que deseamos extraer del documento
            procesaXML(documentoXML, "actualizaAutor", objResp);
            if(objResp.mensaje.indexOf("Error") < 0) //Verificamos si la cadena contiene algún error
                $('divRespuesta').innerHTML = objResp.mensaje;
            else
                alert(objResp.mensaje);
            modificaBoton("btnGuardar","Guardar",false);
        }
    }
}

function procesaXML(documXML, nomMetodo, objSalida) { //Función que extre del objeto ducumXML los valores devueltos por el Servicio Web dentro del objeto objSalida
    var nomRespuesta = "ns1:" + nomMetodo + "Response";
    var raiz = documXML.getElementsByTagName('SOAP-ENV:Envelope')[0];
    var cuerpo = raiz.getElementsByTagName('SOAP-ENV:Body')[0];
    if(!cuerpo.getElementsByTagName('SOAP-ENV:Fault')[0])
    {   
        var respuesta = cuerpo.getElementsByTagName(nomRespuesta)[0];
        for(var campo in objSalida)
        {
            if(respuesta.getElementsByTagName(campo)[0].firstChild)
                objSalida[campo] = respuesta.getElementsByTagName(campo)[0].firstChild.nodeValue;
        }
    if(respuesta.getElementsByTagName('error')[0] && respuesta.getElementsByTagName('error')[0].firstChild)
        objSalida.error = respuesta.getElementsByTagName('error')[0].firstChild.nodeValue;
    }
    else {
        var respuesta = cuerpo.getElementsByTagName('SOAP-ENV:Fault')[0];
        objSalida.error = respuesta.getElementsByTagName('faultstring')[0].firstChild.nodeValue;
    }
}

function creaCadenaXMLDePeticion(nomMetodo, ns, arrParametros) { //Devuelve una cadena que representa al documento XML que será enviado como petición al Servicio Web
    var cadenaXML = "<?xml version='1.0' encoding='utf-8'?>";
    cadenaXML += "<soap:Envelope xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' ";
    cadenaXML += "xmlns:xsd='http://www.w3.org/2001/XMLSchema' ";
    cadenaXML += "xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/'>";
    cadenaXML += "<soap:Body>"
    cadenaXML += "<" + nomMetodo + " xmlns='"+ ns +"'>"; //Elemento XML en el que aparece el nombre del método solicitado y el namespace especificado
    tope = arrParametros.length;
    for(var i=0; i < tope; i++) { //Para cada parámetro que requiere el método se crea un elemento con la información del objeto del arreglo correspondiente
        cadenaXML += "<"+arrParametros[i].nombre+" xsi:type='xsd:"+arrParametros[i].tipo+"'>"+arrParametros[i].valor+"</"+arrParametros[i].nombre+">";
    }
    cadenaXML += "</"+nomMetodo+">";
    cadenaXML += "</soap:Body></soap:Envelope>";
    return cadenaXML;
}

function creaParamBusqueda() { //Función que crea un arreglo que contiene el objeto del parámetro para la busqueda
    var arreglo = new Array(
    {nombre:'id', valor: $("txtId").value, tipo: "string"}
    ); //El objeto representa el parametro id de la función buscaAutor
    return arreglo; //*Nota: El símbolo $ es equivalente a document.getElementById, esta es una funcionalidad que nos ofrece la biblioteca Prototype
}

function creaParamEnvio() {//Función que crea un arreglo que contiene los objetos de los parámetros para la actualización
    var arreglo = new Array(
    {nombre:'id', valor: $("txtId").value, tipo: "string"},
    {nombre:'nombre', valor: $("txtNombre").value, tipo: "string"},
    {nombre:'nacionalidad', valor: $("txtNacionalidad").value, tipo: "string"},
    {nombre:'profesion', valor: $("txtProfesion").value, tipo: "string"}
    ); //Cada uno de los objetos que se encuentran en el arreglo representan los parámetros de la función del servicio Web (id, nombre, nacionalidad, profesion)
    return arreglo; //*Nota: El símbolo $ es equivalente a document.getElementById, esta es una funcionalidad que nos ofrece la biblioteca Prototype
}

function modificaBoton(id, valor, deshabilitar) { //Función que puede habilitar o deshabilitar de un elemento especificado, así como cambiar su valor
    if($(id)) {
        if(valor !="")
            $(id).value = valor;
        $(id).disabled = deshabilitar;        
    }
}
</script>
</head>
<body>
<form>
<p><label>Id:</label> <input type="text" name="txtId" id="txtId" size="5" /> <input type="button" value="Buscar" id="btnBuscar" onclick="peticionDeBusqueda()" /></p>
<p>
  <label>Nombre:</label>
  <input type="text" id="txtNombre" name="txtNombre" size="40" /><br/>
  <label>Nacionalidad:</label>
  <input type="text" id="txtNacionalidad" name="txtNacionalidad" size="30" /><br/>
  <label>Profesion:</label>
  <input type="text" id="txtProfesion" name="txtProfesion" size="50" /><br/>
  <input type="button" value="Guardar" id="btnGuardar" onclick="peticionDeActualizacion()" /></p>
  </form>
<div id="divRespuesta"></div>
</body>
</html>


Como pueden ver, es un código algo largo y es que incluí algunas funcionalidades adicionales a lo que habíamos venido manejando hasta ahora. Una vez más recurrimos a la función inicializaXhr() para acceder a la instancia de la clase XMLHttpRequest, y este objeto es utilizado para iniciar dos peticiones dentro de las funciones peticionDeBusqueda y peticionDeActualizacion.

function peticionDeBusqueda() {
  var url = "http://localhost/servicio.php";
  var ns = "http://localhost";
  var arrParam = creaParamBusqueda();
  var cadenaXML = creaCadenaXMLDePeticion("buscaAutor",ns,arrParam);
  peticionHttp = inicializaXhr();
  if(peticionHttp) {
...


La función peticionDeBusqueda() será llamada al hacer clic sobre el botón btnBuscar, y al hacerlo se iniciará la petición a nuestro servicio Web del método buscaAutor. Como vemos en el código se define la URL del serivico Web, se declara el namespace de este servicio (ns) y después por medio de la función creaParamBusqueda, accedemos al valor del campo id capturado por el usuario en el formulario, el cual es alamacenado en un arreglo de objetos llamado arrParam, este arreglo es pasado como parámetro a la función creaCadenaXMLPeticion(), la cual utiliza éste y otros dos parámetros para crear una cadena con formato XML que representará el mensaje XML de la petición del método buscaAutor (siguiendo el formato que definimos al inicio de este post). En seguida se crea la instancia de la clase XMLHttpRequest y después de validarla se procede a realizar la petición:

modificaBoton("btnBuscar","Buscando...",true);
peticionHttp.onreadystatechange = recibeArreglo;
peticionHttp.open("POST", url, true);
peticionHttp.setRequestHeader ("SOAPAction", "http://localhost/servicio.php/buscaAutor");
peticionHttp.setRequestHeader ("Content-Type", "text/xml")
peticionHttp.send(cadenaXML);


Como vemos, se realiza una llamada a la función modificaBoton, cuya única función es deshabilitar el botón del formulario btnBuscar, para evitar que se pulse otra vez mientras se atiende la petición del servicio Web. En seguida se hace referencia a la función recibeArreglo() para indicar que se ejecute en cuanto se complete la solicitud al servidor Web, después con el método open se establecen las características de la petición al servidor Web. El método setRequestHeader se utiliza para agregar dos encabezados adicionales dentro de la petición, el primero para indicar que se trata de una petición que implementa el protocolo SOAP por medio del identificador "SOAPAction", y el segundo para indicar que el contenido de la petición tendrá un formato XML por medio del identificador "Content-Type" de tipo "text/xml". De esta forma, el texto que contiene cadenaXML será interpretado como el contenido XML de la petición enviada al servidor Web al momento de pasárselo a la al métod send, con lo cual se inicia la petición del método buscaAutor al servicio Web ServicioDeAutores.

La función peticionDeActualizacion() se iniciará haciendo clic sobre el botón identificado como btnGuardar, y su funcionamiento es muy parecido al de peticionDeBusqueda, con la diferencia de que aquí el método que se solicita es actualizaAutor y al cual se le proporcionan los datos del autor capturados en el formulario, a los cuales se accede por medio de la función creaParamEnvio():
var arreglo = new Array(
    {nombre:'id', valor: $("txtId").value, tipo: "string"},
    {nombre:'nombre', valor: $("txtNombre").value, tipo: "string"},
    {nombre:'nacionalidad', valor: $("txtNacionalidad").value, tipo: "string"},
    {nombre:'profesion', valor: $("txtProfesion").value, tipo: "string"}
    );
    return arreglo;


Que como vemos simplemente crea un arreglo que contiene los valores capturados en este formulario, dentro de una serie de objetos (delimitados con la apertura y cierre de llaves "{}"), en los que además del valor se establece el nombre y tipo de dato para los valores recogidos, debido a que este arreglo se utilizará en la función creaCadenaXMLDePeticion para elaborar el texto del contenido XML para la petición, en donde los valores de este arreglo se usarán para establecer los parámetros del método que se solicita:

var cadenaXML = "<?xml version='1.0' encoding='utf-8'?>";
cadenaXML += "<soap:Envelope xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' ";
cadenaXML += "xmlns:xsd='http://www.w3.org/2001/XMLSchema' ";
cadenaXML += "xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/'>";
cadenaXML += "<soap:Body>"
cadenaXML += "<" + nomMetodo + " xmlns='"+ ns +"'>";
tope = arrParametros.length;
for(var i=0; i < tope; i++) {
    cadenaXML += "<"+arrParametros[i].nombre+" xsi:type='xsd:"+arrParametros[i].tipo+"'>"+arrParametros[i].valor+"</"+arrParametros[i].nombre+">";
}
cadenaXML += "</"+nomMetodo+">";
cadenaXML += "</soap:Body></soap:Envelope>";
return cadenaXML;


En la función creaCadenaXMLDePeticion la variable cadenaXML es utilizada para guardar el cuerpo de la petición XML que se va generando dentro, utilizando el nombre del método, nombre de namespace y el arreglo de valores que recibe como parámetros.

Una vez que se recibe la respuesta del servidor Web, en el caso del método actualizaAutor se ejecuta la función recibeMensaje, la cual después de validar el estado de la respuesta se encarga de procesar su contenido:
var documentoXML = peticionHttp.responseXML;
var objResp = {mensaje: ""};
procesaXML(documentoXML, "actualizaAutor", objResp);
if(objResp.mensaje.indexOf("Error") < 0)
    $('divRespuesta').innerHTML = objResp.mensaje;
else
    alert(objResp.mensaje);
modificaBoton("btnGuardar","Guardar",false);


Creando un objeto XML llamado documentoXML que representará el documento XML devuelto por el servicio Web, y podremos utilizarlo para extraer de él la información que nos interesa. En este caso queremos conocer el contenido del campo mensaje que el servicio devuelve como resultado de la ejecución del método actualizaAutor. Para lograrlo utilizamos la función procesaXML que recorre todo el documento XML y almacena dentro de una propiedad del objeto objResp el valor del campo que necesitamos. Una vez que tenemos a la mano este valor, podemos desplegar el mensaje devuelto por el servicio con la instrucción:
$('divRespuesta').innerHTML = objResp.mensaje;


Donde el simbolo '$' equivale a document.getElementById, la cual es una funcionalidad que nos ofrece Prototype para hacer nuestra captura más rápida.

Almacenando este archivo HTML podemos pasar a probar su funcionamiento, teniendo presenta cual es la URL del servicio Web que accederemos, por si se necesitan hacer cambios las url contenidas en el código Javascript.

Nota: Una vez más les recuerdo que antes de probar el servicio Web modifiquen el código PHP agregando la contraseña de acceso a su SMBD MySQL en la parte indicada por [TuContraseña].

Al acceder al cliente por medio de nuestro navegador Web, este debe tener una apariencia como la que sigue:


Podemos probar el servicio de búsqueda de autores capturando un identificador como sigue:


Si capturamos un identificador que no exista en la tabla de la base de datos el sistema nos responde los siguiente:


Si capturamos cambios en la información de un autor consultado, podemos probar el método para la actualización de la información:


O, si capturamos un nuevo identificador y hacemos clic en guardar el sistema interpretará que se trata de un nuevo registro realizando una inserción en la tabla:


Y de esta forma es que este pequeño sistema funciona, es obvio que le hacen falta diferentes validaciones, pero cumple con el propósito que nos fijamos al inicio al consumir exitosamente los métodos del servicio Web desarrollado.

Conclusiones

Este fue el último post dedicado al aprendizaje de AJAX dentro de este blog, con todas las anotaciones hechas hasta aquí considero que cubrí todas las expectativas que tenía al iniciar comenzar a aprender sobre este componente de Javascript, lo cual no significa que ya no dedicaré su tiempo a los temas relacionados con AJAX, sino que ya no me centraré en asuntos de nivel básico al momento de referirme estos, y que ahora exploraré otras herramientas de software para seguir haciendo pequeñas guías como esta. Si algún cibernauta se topa con este blog buscando un guía sobre el lenguaje AJAX espero que todos estos artículos le sean de utilidad, para cualquier duda o critica que quieran hacer esta la opción para dejar sus comentarios, los cuales siempre se agradecen.

Hasta la próxima.

martes, 10 de marzo de 2009

Consumiendo un Servicio Web en PHP desde AJAX, Parte 1: Crear el servicio


Otra solución para múltiples plataformas


Otra forma de que tenemos para acceder a la funcionalidad de nuestro servidor Web es a través de los Servicios Web, cuyo funcionamiento es similar al modo de trabajo que hemos utilizado hasta ahora en el que se solicita una página Web por medio de una petición HTTP, mandándole una serie de parámetros y recibiendo e interpretando la respuesta del servidor. Ahora bien, como la información nos indica los Servicios Web consisten una serie de estándares y protocolos para llevar a cabo el intercambio de datos entre nuestros clientes y el servidor Web, y es gracias a ésto que existe una diferencia entre ambos modos de trabajo, ya que gracias a estos protocolos trabajaremos de manera más transparente al realizar la comunicación entre nuestras aplicaciones, al dejar varios de los detalles de esta labor a la implementación del servicio Web que estemos utilizando. El gran potencial de los servicios Web es que permiten la comunicación de aplicaciones situadas en diferentes plataformas de manera estándar, por lo que podríamos tener un servicio Web hecho en .Net y consumirlo desde un cliente Java, o un servicio Web en PHP y consumirlo desde un cliente .Net, con total independencia del sistema operativo o el manejador de base de datos que estemos utilizando.

En este artículo, me propongo mostrar el ejemplo del uso de un Servicio Web programado en PHP desde nuestro cliente AJAX, realizando las operaciones de consulta, inserción y actualización de los registros de una tabla en nuestra base de datos obras_literarias de MySQL, las cuales serán solicitadas desde nuestro cliente AJAX.

Antes de comenzar

Para desarrollar nuestro servicio Web desde PHP utilizaremos las implementaciones de los protocolos XML-RPC/SOAP que nos ofrece la clase soap_server, la cual esta contenido en la biblioteca del archivo nusoap.php que podemos descargar de manera totalmente gratuita desde el enlace que acabamos de indicar -la documentación completa la encuentran aquí-, en mi caso la última versión disponible que encontré es la 0.7.3. Una vez a nuestra disposición incluiremos el archivo nusoap.php en el directorio de nuestro servidor Web para hacerle referencia en el programa PHP que realizaremos. Además nos apoyaremos en la clase AccesoADatos que definimos en un post anterior.

Nomenclatura utilizada:

-El protocolo de comunicación que se utilizará esSOAP, que es el que define el mecanismo de comunicación entre un cliente (en esta caso en AJAX) y un servicio Web (en este caso en PHP).
-En la definición de este servicio Web también aparecerá el término WSDL siglas de Web Services Description Language, que es un formato XML que se utiliza de manera estándar para describir las características de los servicios Web. Dentro del código definiremos algunas de las características del WSDL del servicio Web que implementaremos.

El trabajo con la base de datos

El sistema manejador de base de datos seguirá siendo MySQL, dentro del cual tendremos la base de datos obras_literarias, en la cual encontramos la tabla autores, que ya habíamos utilizado en los posts anteriores y se puede crear a través del siguiente script:


CREATE TABLE `autores` (
`Id` mediumint(9) NOT NULL,
`Nombre` varchar(60) NOT NULL,
`Nacionalidad` varchar(50) NOT NULL,
`Profesion` varchar(60) NOT NULL,
PRIMARY KEY (`Id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;


Si lo deseamos podemos insertar algunos registros, aunque no es un requisito ya que el sistema que haremos incluirá la inserción de registros:
INSERT INTO `autores` ( `Id` , `Nombre` , `Nacionalidad` , `Profesion` )
VALUES (
'1', 'Juan Rulfo', 'Mexicano', 'Novelista'
),(
'2', 'Pablo Neruda', 'Chileno', 'Poeta'
),(
'3', 'Miguel de Cervantes Saavedra', 'Español', 'Novelista, poeta y dramaturgo'
),(
'4', 'Jorge Luis Borges', 'Argentino', 'Ensayista, escritor y poeta'
);


Trabajo del lado del servidor

Para el servidor crearemos el programa PHP servicio.php, dentro del cual declararemos el servicio Web SOAP que que contendrá los métodos que realizarán las labores de consulta y actualización de la tabla autores.

El código para el trabajo es el siguiente (en seguida explicaré cada uno de los pasos que se fueron realizando):

servicio.php

<?php
require("nusoap.php"); //Referencia al archivo que contiene la clase soap_server
require("AccesoADatos.php");//Referencia al archivo que contiene la clase AccesoADatos
//Implementación del servicio Web
$servicio = new soap_server; //Instancia de la clase para la creación del servicio Web
$servicio->soap_defencoding = "UTF-8"; //Señalamos que el juego de caracteres que utilizaremos es UTF-8
/*
* Definimos un espacio de nombres (namespace) para el servicio Web, funciona como un identificador del servicio Web
* Web -en este caso usamos una URL-
*/
$ns = "http://localhost";
$servicio->configureWSDL('ServicioDeAutores',$ns); //Configuramos el WSDL del servicio Web (Param 1: Nombre del servicio Web, Param 2: Namespace del servicio Web)

/*
*Definimos cuales serán las funciones que poseerá nuestro servicio Web con el metodo register, como primer
* parámetro recibe el nombre de la función, en el segundo y tercer parámetro se incluyen dos arreglos asociativos que especifican los parámetros
* que reciben y los valores devueltos por estas funciones respectivamente
*/
$servicio->register('buscaAutor',
                    array('id'=>'xsd:string'),
                    array('nombre'=>'xsd:string','nacionalidad'=>'xsd:string','profesion'=>'xsd:string','error'=>'xsd:string'),
                    $ns); //Recibirá el id del autor que es de tipo string y devolverá un arreglo con los datos del registro
$servicio->register('actualizaAutor',
                    array('id'=>'xsd:string','nombre'=>'xsd:string','nacionalidad'=>'xsd:string','profesion'=>'xsd:string'),
                    array('mensaje'=>'xsd:string'),
                    $ns); //Recibirá los datos del autor y devolverá un mensaje tipo string
//Obtenemos los parámetros del encabezado HTTP
if(isset($HTTP_RAW_POST_DATA))
{
    $input = $HTTP_RAW_POST_DATA;
}
else
{
    $input = implode("\r\n",file("php://input")); //Secuencia de entrada php://input
}
$servicio->service($input); //Con este método que recibe el contenido del encabezado HTTP se procesa la petición para el servicio Web y se genera la respuesta
exit;

//Funciones para el servicio web
function buscaAutor($id) { //Función para la busqueda de autores
    $acceso = new AccesoADatos('localhost','root','[TuContraseña]','obras_literarias');
    $autor = array('nombre'=>"",'nacionalidad'=>"",'profesion'=>"");
    if($acceso->abreConexion()) { //Accedemos a la base de datos
        $consulta = "SELECT * FROM autores WHERE Id = $id LIMIT 1";
        if($acceso->consulta($consulta)) {//Realizamos la consulta y comprobamos el resultado
            if($acceso->devuelveRegsLeidos() > 0){//Comprobamos si se encontró el registro                                
                $renglon = $acceso->devuelveArreglo();//Extrae el renglon del resultado como un arreglo asosciativo
                $autor['nombre'] = $renglon['Nombre'];
                $autor['nacionalidad'] = $renglon['Nacionalidad'];
                $autor['profesion'] = $renglon['Profesion'];
            }
            else
                $autor['error'] = "Error: No se localizó un autor con el identificador específicado";
        }
        else
            $autor['error'] = "Error de acceso a datos";
    }
    else
        $autor['error'] = "Error de acceso a datos";
    return $autor;
}

function actualizaAutor($id, $nombre, $nacionalidad, $profesion) { //Función para la actualización de la tabla autores
    $acceso = new AccesoADatos('localhost','root','[TuContraseña]','obras_literarias');
    if($acceso->abreConexion()) { //Accedemos a la base de datos     
        $datos = array('Nombre'=>$nombre,'Nacionalidad'=>$nacionalidad,'Profesion'=>$profesion);
        $consulta = "SELECT * FROM autores WHERE Id = $id LIMIT 1";
        if($acceso->consulta($consulta)) { //Realizamos la consulta y comprobamos el resultado
            if($acceso->devuelveRegsLeidos() > 0) { //Comprobamos si se encotró el registro
                $campoLlave = array('Id'=>$id); //Si se encontró, significa que se está actualizando un registro existente                              
                if($acceso->actualizaRegistro("autores",$datos,$campoLlave) > 0)//Realizamos la actualización del registro                            
                    $mensaje = "El registro fué actualizado exitosamente";            
                else
                    $mensaje = "Error: No se registraron cambios en la información del autor";                                        
            }
            else { //En caso contrario se está realizando una insersión
                $datos['Id'] = $id;
                if($acceso->insertaRegistro("autores",$datos) > 0)//Realizamos la inserción del registro
                    $mensaje = "El registro fué insertado exitosamente";            
                else
                    $mensaje = "Error: No se completo la inserción del registro";
            }
        }
        else
            $mensaje = "Error de acceso a datos";            
    }
    else
        $mensaje = "Error de acceso a datos";     
    return $mensaje;
}
?>


Como vemos, al inicio se incluye la referencia a los archivos de las clases que utilizaremos:

require("nusoap.php");
require("AccesoADatos.php");

para implementar el servicio Web y para acceder a la base de datos respectivamente.

En seguida se crea la instancia de la clase soap_server con la cual implementaremos el servicio Web:

$servicio = new soap_server;


Específicamos que el juego de caracteres utilizado es UTF-8:

$servicio->soap_defencoding = "UTF-8";


Por medio de la variable $ns definimos el namespace del servicio Web, que representa el identificador único de nuestro servicio, este caso representado por medio de la URL de nuestro servidor Web local, que indica el directorio en el que podemos localizar el archivo del servicio Web por medio de HTTP:

$ns = "http://localhost";


También podríamos utilizar una URL con dirección IP, o una dirección con un nombre dominio propio servidor si disponemos de una.

En seguida definimos el WSDL del servicio Web, estableciendo el nombre del servicio Web como "ServicioDeAutores" y su namespace como "http://locahost/PruebasPHP":

$servicio->configureWSDL('ServicioDeAutores',$ns);


Una vez hecho esto a través del método registrer() especificamos cuales serán las funciones que ofrecerá ServicioDeAutores:

$servicio->register('buscaAutor',
                    array('id'=>'xsd:string'),
                    array('nombre'=>'xsd:string','nacionalidad'=>'xsd:string','profesion'=>'xsd:string','error'=>'xsd:string'),
                    $ns);
$servicio->register('actualizaAutor',
                    array('id'=>'xsd:string','nombre'=>'xsd:string','nacionalidad'=>'xsd:string','profesion'=>'xsd:string'),
                    array('mensaje'=>'xsd:string'),
                    $ns);

En el primer parámetro de este método definimos el nombre de la función mencionada, en el segundo se definen los parámetros de esta función, el tercer parámetro define los valores que serán devueltos por la función, pudiendo tratarse de un único valor o de un arreglo asociativo, dependiendo del número de valores que se definan. Cómo último parámetro el método register() recibe el namespace del servicio Web. La declaración de estas funciones aparecerá en el bloque final del código del archivo.

En seguida se obtiene el contenido completo del encabezado HTTP que recibe el programa PHP:

if(isset($HTTP_RAW_POST_DATA))
{
    $input = $HTTP_RAW_POST_DATA;
}
else
{
    $input = implode("\r\n",file("php://input"));
}

Este encabezado se puede ser accedido por medio de la variable predifinida $HTTP_RAW_POST_DATA o utilizando la secuencia de entrada php://input.

A continuación y para finalizar con la implementación del servicio Web utilizamos el método service(), el recibe el contenido del encabezado HTTP y procesa la petición para el servicio Web y se genera la respuesta XML:

$servicio->service($input);
exit;


Seguido de la instrucción exit para finalizar el programa PHP.

En el siguiente bloque de código aparece la declaración de las funciones buscaAutor y actualizaAutor, en la primera se lleva a cabo la búsqueda de una autor en la base de datos por medio del $id recibido. La segunda realiza la actualización o inserción de un registro, dependiendo de si el valor campo "Id" proporcionado existe o no en la base de datos.

Nota: Antes de probar el servicio Web recuerden modificar el código anterior agregando la contraseña de acceso a su SMBD MySQL en la parte indicada por [TuContraseña].

Y ahora, gracias a este este código podremos implementar nuestro servicio Web que será consumido por el cliente AJAX que desarrollaremos, con la ventaja de que este mismo servicio puede ser utilizado por cualquier otra aplicación que soporte la utilización de servicios Web.

Para probar que todo anda bien con el ServicioDeAutores podemos acceder a él por medio de nuestro navegador, utilizando la ruta del servidor Web en la que está almacenado, para este ejemplo ésta sería: http://localhost/servicio.php, obteniendo un resultado como el que sigue:



Debido a que nuestro navegador es capas de interpretar el contenido XML de la respuesta del servicio Web, nos ofrece la posibilidad de visualizar su WSDL, así como las características de la funciones que éste posee. El WSDL arrojado en este ejemplo es el siguiente:

img


Mientras que al seleccionar alguna de las funciones nos ofrece una descripción como la que sigue:



Si todo salió bien nuestro servicio Web ya funciona, ahora todo lo que queda es probarlo desde un cliente, cuya programación veremos en el siguiente artículo.

Hasta la próxima.