Implementando servicios web con PHP

En la actualidad el término servicios web (web services) forma parte esencial dentro del mundo del desarrollo de software, ya se ha escrito mucho sobre que son y cuales tecnologías usan, por lo tanto la razón de ser de este artículo es realizar una implementación real de servicios web con una tecnología tan importante como lo es el PHP, hablando un poco también de los fundamentos teóricos pero sin ahondar demasiado en el tema.

Para lograr la implementación de servicios web en PHP se usó las tecnologías XML-RPC y SOAP sobre las cuales se implementaron servidores y clientes consumidores de servicios web.

Servicios Web
Los servicios Web han venido a revolucionar el mundo de la programación, nos ofrecen una infinidad de ventajas y nos ayudan a mejorar la forma de procesar información. Pero, ¿qué es un servicio Web?, pues bien, es computación distribuida utilizando estándares abiertos como XML y HTTP para llamar o invocar funciones de otras aplicaciones independientes sea cual sea el sistema operativo o plataforma en que se ejecutan. Ahora bien, podemos realizar un servicio Web sencillo en nuestra computadora, pero posiblemente éste no cumplirá con estándares de comunicación, es por eso que debemos de entender que para realizar una correcta función de nuestros servicios Web es necesario estandarizarlos por medio de protocolos. Existen dos tendencias en particular que es XML-RPC y SOAP. Estos dos protocolos son lenguajes de mensajería basada en XML, estandarizados por el consorcio W3C. XML-RPC XML-RPC es el protocolo de llamada de procedimientos remotos (RPC: Remote Procedure Calling), el cual trabaja sobre internet. Un mensaje de XML-RPC es una petición del HTTPPOST [1]. El cuerpo del mismo está en XML, un procedimiento es ejecutado en el servidor y el valor que devuelve está en formato XML. Un ejemplo de una petición [6] sería el siguiente:

POST /RPC2 HTTP/1.0 User-Agent: Frontier/5.1.2 (WinNT) Host: betty.userland.com Content-Type: text/xml Content-length: 181 <?xml version="1.0"?> <methodCall> <methodName>ejemplo.buscaIsbn</methodName> <params> <param> <value><i4>1</i4></value> </param>

</params> </methodCall>

Un ejemplo de respuesta [6] sería entonces:
HTTP/1.1 200 OK Connection: close Content-Length: 158 Content-Type: text/xml Date: Fri, 17 Jul 1998 19:55:08 GMT Server: UserLand Frontier/5.1.2-WinNT <?xml version="1.0"?> <methodResponse> <params> <param> <value><string>PHP</string></value> </param> </params> </methodResponse>

SOAP SOAP (Simple Object Access Protocol, Protocolo de acceso a objetos simple)[3] es un protocolo basado en XML que consiste de tres partes: la primera define cuál es el mensaje y cómo procesarlo, la segunda es un sistema de reglas de codificación para expresar tipos de datos definidos y una tercera parte para representar respuestas de llamadas por parte de procedimientos remotos. La diferencia básica entre los dos protocolos anteriores es su complejidad. XML-RPC está diseñado para ser sencillo, mientras que SOAP está hecho con la idea de ofrecer un soporte completo de todo tipo de servicio web. Por otro lado, también es conveniente describir qué es WSDL. Pues bien, WSDL es un formato XML que describe los servicios de red como un conjunto de puntos finales que procesan mensajes contenedores de información orientada tanto a documentos como a procedimientos. Las operaciones y los mensajes se describen de manera abstracta y después se enlazan a un protocolo de red y a un formato de mensaje concreto para definir un punto final de red.

Desarrollo de un servidor XML-RPC
Como usaremos el framework XML-RPC [2] desarrollado por Edd Dumbill para desarrollar nuestros servicios web basados en XML-RPC. Dicho framework cuenta con la clase xmlrpc_server para construir nuestros servidores, la cual se ha desarrollado lo más simple posible. El constructor básicamente hace todo el trabajo, veamos un pequeño ejemplo:
<?php function foo($parametros) { /* Instrucciones php */ }

$servidor=new xmlrpc_server(array("ejemplo.miFuncion"=>array("function"=>"foo"))); ?>

Es todo lo que necesitamos hacer en un servidor. El único argumento que requiera la clase es un arreglo asociativo de los nombres de los métodos a los nombres de las funciones que se van a exponer, las cuales son responsables de regresar un objeto xmlrpcresp, el cual es serializado de regreso. xmlrpcresp.- Esta clase se usa para proveer las respuestas a las peticiones XML-RPC. Un método en el servidor construirá el xmlrpcresp y regresará su valor. Este valor es el que se regresa al invocar el método send de la clase xmlrpc_client. Existen dos formas de crear esta clase:
<?php $res = new xmlrpcresp($valor_xmlrpc); $res = new xmlrpcresp(0, $NoError, $err); ?>

La primera instancia se usa cuando la ejecución ocurrió sin excepciones, $valor_xmlrpc es un valor xmlrpcval con el resultado de la ejecución del método. El segundo constructor se usa en caso de falla, $NoError y $err son valores para indicarnos lo que estuvo mal. Los métodos más comunes de esta clase son: faultCode:
<?php $codigo=$res->faultCode(); ?>

Regresa el código del error. Un valor de 0 indica éxito, cualquier otro valor indica falla. faultString:
<?php $error=$res->faultString(); ?>

Regresa la descripción del error. value:
<?php $valor_xmlrpc=$res->value(); ?>

Regresa un objeto xmlrpcval que contiene el valor regresado por el servidor. Si el faultCode no es 0 entonces el valor regresado por este método no debe ser usado (puede que no sea un objeto incluso). Ahora que ya conocemos el funcionamiento de las clases que crean el servidor y forman las respuestas vamos a crear nuestro servidor XML-RPC que contendrá el servicio web de buscar el título de un libro enviando su ISBN con el que hemos trabajado.
<?php //Ejemplo de un servidor XML-RPC en PHP //Recibe un ISBN y regresa el Título //del libro. include("xmlrpc.inc");

include("xmlrpcs.inc"); function BuscaIsbn($NoIsbn) { global $NoError; $err=""; // Obtenemos el parametro $ParIsbn=$NoIsbn->getParam(0); // Vemos si es del tipo correcto if (isset($ParIsbn) && ($ParIsbn->scalartyp()=="int")) { // Obtenemos el valor numerico $isbn=$ParIsbn->scalarval(); // Buscamos el libro switch($isbn) { case 1: $titulo="PHP"; break; case 2: $titulo="XML_RPC"; break; case 3: $titulo="Sitios web"; break; case 4: $titulo="Linux"; break; default:$NoError=1; $err="No hay libro ". "con el ISBN '". $isbn . "'"; } } else // No es entero {$err="Se requiere un número";} // Creamos la respuesta if ($err) // Si hay error { return new xmlrpcresp(0, $NoError, $err); } else // Si no hay error { return new xmlrpcresp(new xmlrpcval($titulo)); } } //Creamos el servidor $s=new xmlrpc_server(array("libros.buscaIsbn" =>array("function" => "BuscaIsbn"))); ?>

Ya contamos con nuestro servidor XML-RPC y nuestro servicio web listo para ser usado.

Desarrollo de un cliente XML-RPC
El siguiente paso es conocer las clases que podemos usar para crear un cliente XML-RPC para que consuma servicios web XML-RPC, demos un vistazo rápido a las clases principales. xmlrpc_client.- Esta es la clase básica para un cliente XML-RPC, la forma de usarla es la siguiente:
<?php $cliente = new xmlrpc_client($ruta_servidor,$nombre_servidor,$puerto_servidor); ?>

El puerto es opcional si se omite usa por defecto el 80 para HTTP y el 443 para HTTPS. Los métodos más comunes de esta clase son: send:
<?php $respuesta = $cliente->send($mensaje_xmlrpc,$tiempo_limite,$metodo_servidor); ?>

Donde $mensaje_xmlrpc es una instancia de xmlrpcmsg. $tiempo_limite es opcional y será 0 su valor si es omitido lo cual indica que censará siempre. El parámetro $metodo_servidor también es opcional si se omite será por defecto "http", el otro valor permitido es "https" que es una conexión SSL_HTTP. Si el valor de repuesta es 0, significa que ocurrió un error de entrada / salida, se puede conocer los valores del error en $cliente->errno y en $client>errstring. setDebug:
<?php $cliente->setDebug($valor); ?>

Donde $valor es 0 o 1 dependiendo si deseamos que el cliente imprima la información de depuración en el navegador. Por defecto no imprime la información (0). xmlrpcmsg.- Esta clase provee una representación para una petición a un servidor XML-RPC. Un cliente envía un xmlrpcmsg al servidor y recibe un xmlrpcresp.
<?php $msg = new xmlrpcmsg($nombre_metodo,$arreglo_parametros); ?>

Donde $nombre_metodo es una cadena que indica el nombre del método que se desea invocar y $arreglo_parámetros es un arreglo simple de objetos xmlrpcval. El método más común de esta clase es: xmlrpcval.- Esta clase es la que permite la creación y encapsulamiento de los valores para XML-RPC (hace el trabajo sucio). Cuenta con diferentes constructores:
<?php $valor=new xmlrpcval(); ?>

Crea un valor vacío, que debe ser alterado usando los métodos addScalar, addArray o addStruct antes de ser usado.
<?php $valor=new xmlrpcval($cadena_texto); ?>

Crea una cadena de texto sencilla.
<?php $valor=new xmlrpcval( $valor_escalar,"int"|"boolean"|"string"|"double"|"dateTime.iso860"|"base64"); ?>

Es usado para crear un valor escalar. El segundo parámetro debe ser el nombre de un tipo XML-RPC. Ejemplos:
<?php $entero=new xmlrpcval(123,"int"); ?> <?php $cadena=new xmlrpcval("Hola","string"); ?> <?php $boleano=new xmlrpcval(1,"boolean"); ?>

<?php $valor=new xmlrpcval($arreglo,"array"|"struct"); ?>

Se usa para crear valores complejos XML-RPC. EL primer argumento es un arreglo simple en el caso de usar "array" o un arreglo asocativo en el caso de "struct". Los elementos del arreglo deben ser objetos xmlrpcval. Ejemplos:
<?php $arreglo=new xmlrpcval( array(new xmlrpcval("Abi"), new xmlrpcval("Pedro")), "array"); ?> <?php $estructura=new xmlrpcval( array("nombre"=>new xmlrpcval("Abi"), "edad"=>new xmlrpcval( 23,"int")),"struct"); ?>

Con estas clases son suficientes para poder desarrollar nuestro cliente XML-RPC, que esta de la siguiente manera:
<?php include("xmlrpc.inc"); if ($HTTP_POST_VARS["txtIsbn"]!="") { $f=new xmlrpcmsg('libros.buscaIsbn',array(new xmlrpcval($HTTP_POST_VARS["txtIsbn"], "int"))); print "<pre>".htmlentities($f->serialize())."</pre>\n"; $c=new xmlrpc_client("/servidor.php", "localhost", 80); $c->setDebug(0); $r=$c->send($f); if (!$r) { die("Falló SEND"); } $v=$r->value(); if (!$r->faultCode()) { print "Título del libro ".$HTTP_POST_VARS["txtIsbn"]." es ".$v} >scalarval()."<BR>"; else { print "Falla: "; print "Número de error: " .$r->faultCode()." Descripción del error '".$r>faultString()."'<BR>";} } ?>

Listo ya hemos implementado nuestro servidor y nuestro cliente XML-RPC, pero no es la única forma de consumir o crear servicios, veamos otra forma de crear y consumir servicios web.

Desarrollo de clientes SOAP con PHP
La mayoría de los servicios web que encontramos en la actualidad se basan en los estándares SOAP y WSDL [5] analizados anteriormente. La implementación de SOAP sobre PHP que vamos a utilizar se llama NuSOAP [4] que es desarrollado por la empresa NuSphere y que fue liberado bajo licencia LPGL. Veamos el siguiente script en PHP que usa SOAP para consumir un servicio web que regresa el típico mensaje de "Hola Mundo":
<?php // Manejo de la forma para ver si ya se envió if (!(string)$_POST["boton"] == "") { // Incluimos las clases de SOAP

require("nusoap.php"); // crea el cliente $cliente = new soapclient("http://www.pecesama.net/ws/server.php?wsdl", "wsdl"); $proxy = $cliente->getProxy(); // llamada al metodo (BuscaIsbn) $resultado = $proxy->BuscaIsbn((string)$_POST["isbn"]); // Revisa errores if (!$cliente->getError()) { // muestra resultados print "El titulo del libro con ISBN ".(string)$_POST["isbn"]." es: ".$resultado; } // Error else { echo "<h1>Error: ".$cliente->getError()."</h1>"; } } ?> <!-- Forma de busqueda --> <form name="datos" action="#" method="POST"> ISBN: <input type="text" name="isbn"> <input name="boton" type="submit" value="Buscar"> </form>

De esta sencilla manera podemos consumir servicios web basados en SOAP, solo enviándole la dirección del WSDL que como vimos es la descripción del servicio web creando una clase proxy por medio de la cual llamamos el método. De esta manera logramos consumir con nuestro lenguaje favorito (PHP) tantos y tantos servicios web que existen en la actualidad basados en SOAP y WSDL de una manera sencilla y rápida.

Desarrollo de servidores SOAP con PHP
Solo nos falta desarrollar un servidor de servicios web con SOAP y PHP, para esto seguiremos usando NuSOAP y nuevamente usaremos el ejemplo de buscar el título del libro mediante su ISBN:
<?php // Incluimos las clases de SOAP require("nusoap.php"); // Creamos el objeto del servidor $servidor=new soap_server(); // Registramos la función que queremos exponer como servicio web $servidor->register("buscaIsbn"); // Generación del WSDL $servidor->debug_flag=false; $servidor->configureWSDL("ISBN", "http://www.pecesama.net/ws"); $servidor->wsdl->schemaTargetNamespace = "http://www.pecesama.net/ws"; /*// Agregamos un tipo de dato complejo $servidor->wsdl->addComplexType( "datosLibro", "complexType", "struct", "all", "", array(

"titulo" => array("name"=>"titulo", "type"=>"xsd:string")) );*/ // Registramos el método $servidor->register("BuscaIsbn", array("titulo" => "xsd:string"), array("return"=>"xsd:string"), http://www.pecesama.net/ws");

function BuscaIsbn($isbn) { if (isset($isbn)) { switch($isbn) /*No usaremos base de datos*/ { case 111: $titulo="Taller de PHP"; break; case 222: $titulo="PHP y XML_RPC"; break; case 333: $titulo="Creando sitios web con PHP"; break; case 444: $titulo="PHP para principiantes"; break; default: return new soap_fault("Client", "", "El libro no existe.", ""); } } else { // No hay isbn return new soap_fault("Client", "", "No envio ISBN.", ""); } return $titulo; } // Enviar el resultado como una respuesta SOAP por HTTP $servidor->service($HTTP_RAW_POST_DATA); exit(); ?>