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 HTTP-POST [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",
"pecesama.ipowermysql.com",
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();
?>