TigerZF
🌐Español

80.3. Zend_XmlRpc_Server

80.3.1. Introducción

Zend_XmlRpc_Server pretende ser un servidor XML-RPC completo, siguiendo las especificaciones descritas en www.xmlrpc.com. Además, implementa el método system.multicall(), permitiendo el empaquetado de peticiones (boxcarring).

80.3.2. Uso básico

Un ejemplo del caso de uso más básico:

$server = new Zend_XmlRpc_Server();
$server->setClass('My_Service_Class');
echo $server->handle();

80.3.3. Estructura del servidor

Zend_XmlRpc_Server está compuesto por una variedad de componentes, que van desde el propio servidor hasta los objetos de petición, respuesta y fallo.

Para inicializar Zend_XmlRpc_Server, el desarrollador debe adjuntar una o más clases o funciones al servidor, mediante los métodos setClass() y addFunction().

Una vez hecho esto, puede pasar un objeto Zend_XmlRpc_Request a Zend_XmlRpc_Server::handle(), o de lo contrario se instanciará un objeto Zend_XmlRpc_Request_Http si no se proporciona ninguno -- obteniendo así la petición desde php://input.

Zend_XmlRpc_Server::handle() entonces intenta despachar hacia el manejador apropiado según el método solicitado. Luego devuelve ya sea un objeto basado en Zend_XmlRpc_Response o un objeto Zend_XmlRpc_Server_Fault. Estos objetos tienen ambos métodos __toString() que crean respuestas XML-RPC XML válidas, permitiendo que sean impresas directamente.

80.3.4. Anatomía de un servicio web

80.3.4.1. Consideraciones generales

Para un máximo rendimiento se recomienda utilizar un archivo de arranque sencillo para el componente del servidor. Utilizar Zend_XmlRpc_Server dentro de un Zend_Controller se desaconseja firmemente para evitar la sobrecarga.

Los servicios cambian con el tiempo y aunque los servicios web son generalmente menos propensos a cambios que las APIs nativas de código, se recomienda versionar su servicio. Hágalo para sentar las bases para proporcionar compatibilidad a los clientes que utilizan versiones anteriores de su servicio y gestionar el ciclo de vida de su servicio, incluyendo los plazos de obsolescencia. Para hacerlo, simplemente incluya un número de versión en su URI. También se recomienda incluir el nombre del protocolo remoto en la URI para permitir una fácil integración de las próximas tecnologías de acceso remoto. http://myservice.ws/1.0/XMLRPC/.

80.3.4.2. ¿Qué exponer?

La mayoría de las veces no es sensato exponer objetos de negocio directamente. Los objetos de negocio suelen ser pequeños y estar sujetos a cambios frecuentes, porque el cambio es barato en esta capa de su aplicación. Una vez desplegado y adoptado, los servicios web son difíciles de cambiar. Otra preocupación es la E/S y la latencia: las mejores llamadas a servicios web son aquellas que no se producen. Por lo tanto, las llamadas a servicios deben tener un grano más grueso de lo que suele tener la lógica de negocio habitual. A menudo tiene sentido una capa adicional delante de sus objetos de negocio. Esta capa a veces se denomina Remote Facade. Dicha capa de servicio añade una interfaz de grano grueso sobre su lógica de negocio y agrupa operaciones detalladas en otras más pequeñas.

80.3.5. Convenciones

Zend_XmlRpc_Server permite al desarrollador adjuntar funciones y llamadas a métodos de clase como métodos XML-RPC despachables. Mediante Zend_Server_Reflection, realiza introspección sobre todos los métodos adjuntados, utilizando los docblocks de las funciones y métodos para determinar el texto de ayuda del método y las firmas del método.

Los tipos XML-RPC no necesariamente se corresponden uno a uno con los tipos de PHP. Sin embargo, el código hará lo posible por adivinar el tipo apropiado en función de los valores indicados en las líneas @param y @return. Algunos tipos XML-RPC no tienen un equivalente inmediato en PHP, sin embargo, y deben ser indicados mediante el tipo XML-RPC en el PHPDoc. Estos incluyen:

  • dateTime.iso8601, una cadena con el formato 'YYYYMMDDTHH:mm:ss'

  • base64, datos codificados en base64

  • struct, cualquier array asociativo

A continuación se muestra un ejemplo de cómo indicar estos tipos:

/**
* This is a sample function
*
* @param base64 $val1 Base64-encoded data
* @param dateTime.iso8601 $val2 An ISO date
* @param struct $val3 An associative array
* @return struct
*/
function myFunc($val1, $val2, $val3)
{
}

PhpDocumentor no realiza ninguna validación de los tipos especificados para los parámetros o los valores de retorno, por lo que esto no tendrá ningún impacto en su documentación de la API. Sin embargo, proporcionar dichas indicaciones es necesario cuando el servidor valida los parámetros proporcionados a la llamada al método.

Es perfectamente válido especificar múltiples tipos tanto para los parámetros como para los valores de retorno; la especificación XML-RPC incluso sugiere que system.methodSignature debería devolver un array con todas las firmas de método posibles (es decir, todas las combinaciones posibles de valores de parámetros y de retorno). Puede hacerlo tal como lo haría normalmente con PhpDocumentor, utilizando el operador '|':

/**
* This is a sample function
*
* @param string|base64 $val1 String or base64-encoded data
* @param string|dateTime.iso8601 $val2 String or an ISO date
* @param array|struct $val3 Normal indexed array or an associative array
* @return boolean|struct
*/
function myFunc($val1, $val2, $val3)
{
}
[Note] Nota

Permitir múltiples firmas puede generar confusión para los desarrolladores que utilizan los servicios; para simplificar las cosas, un método de servicio XML-RPC debería tener una única firma.

80.3.6. Utilizando espacios de nombres

XML-RPC tiene un concepto de espacios de nombres; básicamente, permite agrupar métodos XML-RPC mediante espacios de nombres delimitados por puntos. Esto ayuda a evitar colisiones de nombres entre métodos servidos por distintas clases. Como ejemplo, se espera que el servidor XML-RPC sirva varios métodos en el espacio de nombres 'system':

  • system.listMethods

  • system.methodHelp

  • system.methodSignature

Internamente, estos se corresponden con los métodos del mismo nombre en Zend_XmlRpc_Server.

Si desea añadir espacios de nombres a los métodos que sirve, simplemente proporcione un espacio de nombres al método correspondiente al adjuntar una función o clase:

// All public methods in My_Service_Class will be accessible as
// myservice.METHODNAME
$server->setClass('My_Service_Class', 'myservice');

// Function 'somefunc' will be accessible as funcs.somefunc
$server->addFunction('somefunc', 'funcs');

80.3.7. Objetos de petición personalizados

La mayoría de las veces, simplemente utilizará el tipo de petición predeterminado incluido con Zend_XmlRpc_Server, Zend_XmlRpc_Request_Http. Sin embargo, puede haber ocasiones en las que necesite que XML-RPC esté disponible a través de la CLI, una GUI, u otro entorno, o quiera registrar las peticiones entrantes. Para hacerlo, puede crear un objeto de petición personalizado que extienda Zend_XmlRpc_Request. Lo más importante que debe recordar es asegurarse de que los métodos getMethod() y getParams() estén implementados de forma que el servidor XML-RPC pueda recuperar esa información para despachar la petición.

80.3.8. Respuestas personalizadas

De forma similar a los objetos de petición, Zend_XmlRpc_Server puede devolver objetos de respuesta personalizados; por defecto, se devuelve un objeto Zend_XmlRpc_Response_Http, que envía una cabecera HTTP Content-Type apropiada para su uso con XML-RPC. Posibles usos de un objeto personalizado serían registrar las respuestas, o enviar respuestas a STDOUT.

Para utilizar una clase de respuesta personalizada, use Zend_XmlRpc_Server::setResponseClass() antes de llamar a handle().

80.3.9. Manejo de excepciones mediante faults

Zend_XmlRpc_Server captura las excepciones generadas por un método despachado, y genera una respuesta de fallo (fault) XML-RPC cuando se captura dicha excepción. Sin embargo, por defecto, los mensajes y códigos de excepción no se utilizan en una respuesta de fallo. Esta es una decisión intencionada para proteger su código; muchas excepciones exponen más información sobre el código o el entorno de la que un desarrollador necesariamente pretendería (un ejemplo típico son las excepciones de la capa de abstracción o de acceso a base de datos).

Sin embargo, las clases de excepción pueden incluirse en una lista blanca para usarse como respuestas de fallo. Para ello, simplemente utilice Zend_XmlRpc_Server_Fault::attachFaultException() para pasar una clase de excepción a incluir en la lista blanca:

Zend_XmlRpc_Server_Fault::attachFaultException('My_Project_Exception');

Si utiliza una clase de excepción de la que heredan las demás excepciones de su proyecto, puede entonces incluir en la lista blanca toda una familia de excepciones a la vez. Las excepciones de tipo Zend_XmlRpc_Server_Exception siempre están en la lista blanca, para permitir informar de errores internos específicos (métodos indefinidos, etc.).

Cualquier excepción que no esté específicamente en la lista blanca generará una respuesta de fallo con un código '404' y un mensaje 'Unknown error'.

80.3.10. Almacenamiento en caché de las definiciones del servidor entre peticiones

Adjuntar muchas clases a una instancia del servidor XML-RPC puede utilizar muchos recursos; cada clase debe hacer introspección utilizando la API de Reflection (mediante Zend_Server_Reflection), lo que a su vez genera una lista de todas las posibles firmas de método para proporcionar a la clase del servidor.

Para reducir algo este impacto en el rendimiento, se puede utilizar Zend_XmlRpc_Server_Cache para almacenar en caché la definición del servidor entre peticiones. Cuando se combina con __autoload(), esto puede incrementar considerablemente el rendimiento.

A continuación se muestra un ejemplo de uso:

function __autoload($class)
{
    Zend_Loader::loadClass($class);
}

$cacheFile = dirname(__FILE__) . '/xmlrpc.cache';
$server = new Zend_XmlRpc_Server();

if (!Zend_XmlRpc_Server_Cache::get($cacheFile, $server)) {
    require_once 'My/Services/Glue.php';
    require_once 'My/Services/Paste.php';
    require_once 'My/Services/Tape.php';

    $server->setClass('My_Services_Glue', 'glue');   // glue. namespace
    $server->setClass('My_Services_Paste', 'paste'); // paste. namespace
    $server->setClass('My_Services_Tape', 'tape');   // tape. namespace

    Zend_XmlRpc_Server_Cache::save($cacheFile, $server);
}

echo $server->handle();

El ejemplo anterior intenta recuperar una definición del servidor desde xmlrpc.cache en el mismo directorio que el script. Si no lo consigue, carga las clases de servicio que necesita, las adjunta a la instancia del servidor, y luego intenta crear un nuevo archivo de caché con la definición del servidor.

80.3.11. Ejemplos de uso

A continuación se muestran varios ejemplos de uso, que muestran todo el espectro de opciones disponibles para los desarrolladores. Los ejemplos de uso se irán construyendo cada uno sobre el ejemplo anterior proporcionado.

Example 80.8. Basic Usage

El ejemplo a continuación adjunta una función como un método XML-RPC despachable y gestiona las llamadas entrantes.

/**
 * Return the MD5 sum of a value
 *
 * @param string $value Value to md5sum
 * @return string MD5 sum of value
 */
function md5Value($value)
{
    return md5($value);
}

$server = new Zend_XmlRpc_Server();
$server->addFunction('md5Value');
echo $server->handle();

Example 80.9. Attaching a class

El ejemplo a continuación ilustra cómo adjuntar los métodos públicos de una clase como métodos XML-RPC despachables.

require_once 'Services/Comb.php';

$server = new Zend_XmlRpc_Server();
$server->setClass('Services_Comb');
echo $server->handle();

Example 80.10. Attaching a class with arguments

El siguiente ejemplo ilustra cómo adjuntar los métodos públicos de una clase y pasar argumentos a sus métodos. Esto puede utilizarse para especificar ciertos valores predeterminados al registrar clases de servicio.

class Services_PricingService
{
    /**
     * Calculate current price of product with $productId
     *
     * @param ProductRepository $productRepository
     * @param PurchaseRepository $purchaseRepository
     * @param integer $productId
     */
    public function calculate(ProductRepository $productRepository,
                              PurchaseRepository $purchaseRepository,
                              $productId)
    {
        ...
    }
}

$server = new Zend_XmlRpc_Server();
$server->setClass('Services_PricingService',
                  'pricing',
                  new ProductRepository(),
                  new PurchaseRepository());

Los argumentos pasados en setClass() en el momento de construir el servidor se inyectan en la llamada al método pricing.calculate() en la invocación remota. En el ejemplo anterior, sólo se espera del cliente el argumento $purchaseId.


Example 80.11. Passing arguments only to constructor

Zend_XmlRpc_Server permite restringir el paso de argumentos únicamente a los constructores. Esto puede utilizarse para la inyección de dependencias en el constructor. Para limitar la inyección a los constructores, llame a sendArgumentsToAllMethods y pase FALSE como argumento. Esto deshabilita el comportamiento predeterminado de inyectar todos los argumentos en el método remoto. En el ejemplo siguiente, la instancia de ProductRepository y PurchaseRepository sólo se inyecta en el constructor de Services_PricingService2.

class Services_PricingService2
{
    /**
     * @param ProductRepository $productRepository
     * @param PurchaseRepository $purchaseRepository
     */
    public function __construct(ProductRepository $productRepository,
                                PurchaseRepository $purchaseRepository)
    {
        ...
    }

    /**
     * Calculate current price of product with $productId
     *
     * @param integer $productId
     * @return double
     */
    public function calculate($productId)
    {
        ...
    }
}

$server = new Zend_XmlRpc_Server();
$server->sendArgumentsToAllMethods(false);
$server->setClass('Services_PricingService2',
                  'pricing',
                  new ProductRepository(),
                  new PurchaseRepository());

Example 80.12. Attaching a class instance

setClass() permite registrar en el servidor un objeto previamente instanciado. Simplemente pase una instancia en lugar del nombre de la clase. Obviamente, pasar argumentos al constructor no es posible con objetos ya instanciados.


Example 80.13. Attaching several classes using namespaces

El ejemplo a continuación ilustra cómo adjuntar varias clases, cada una con su propio espacio de nombres.

require_once 'Services/Comb.php';
require_once 'Services/Brush.php';
require_once 'Services/Pick.php';

$server = new Zend_XmlRpc_Server();
$server->setClass('Services_Comb', 'comb');   // methods called as comb.*
$server->setClass('Services_Brush', 'brush'); // methods called as brush.*
$server->setClass('Services_Pick', 'pick');   // methods called as pick.*
echo $server->handle();

Example 80.14. Specifying exceptions to use as valid fault responses

El ejemplo a continuación permite que cualquier clase derivada de Services_Exception informe de su código y mensaje en la respuesta de fallo.

require_once 'Services/Exception.php';
require_once 'Services/Comb.php';
require_once 'Services/Brush.php';
require_once 'Services/Pick.php';

// Allow Services_Exceptions to report as fault responses
Zend_XmlRpc_Server_Fault::attachFaultException('Services_Exception');

$server = new Zend_XmlRpc_Server();
$server->setClass('Services_Comb', 'comb');   // methods called as comb.*
$server->setClass('Services_Brush', 'brush'); // methods called as brush.*
$server->setClass('Services_Pick', 'pick');   // methods called as pick.*
echo $server->handle();

Example 80.15. Utilizing custom request and response objects

Algunos casos de uso requieren utilizar un objeto de petición personalizado. Por ejemplo, XML/RPC no está vinculado a HTTP como protocolo de transferencia. Es posible utilizar otros protocolos de transferencia como SSH o telnet para enviar los datos de petición y respuesta a través de la red. Otro caso de uso es la autenticación y autorización. En el caso de un protocolo de transferencia diferente, es necesario cambiar la implementación para leer los datos de la petición.

El ejemplo a continuación instancia un objeto de petición personalizado y lo pasa al servidor para que lo gestione.

require_once 'Services/Request.php';
require_once 'Services/Exception.php';
require_once 'Services/Comb.php';
require_once 'Services/Brush.php';
require_once 'Services/Pick.php';

// Allow Services_Exceptions to report as fault responses
Zend_XmlRpc_Server_Fault::attachFaultException('Services_Exception');

$server = new Zend_XmlRpc_Server();
$server->setClass('Services_Comb', 'comb');   // methods called as comb.*
$server->setClass('Services_Brush', 'brush'); // methods called as brush.*
$server->setClass('Services_Pick', 'pick');   // methods called as pick.*

// Create a request object
$request = new Services_Request();

echo $server->handle($request);

Example 80.16. Specifying a custom response class

El ejemplo a continuación ilustra cómo especificar una clase de respuesta personalizada para la respuesta devuelta.

require_once 'Services/Request.php';
require_once 'Services/Response.php';
require_once 'Services/Exception.php';
require_once 'Services/Comb.php';
require_once 'Services/Brush.php';
require_once 'Services/Pick.php';

// Allow Services_Exceptions to report as fault responses
Zend_XmlRpc_Server_Fault::attachFaultException('Services_Exception');

$server = new Zend_XmlRpc_Server();
$server->setClass('Services_Comb', 'comb');   // methods called as comb.*
$server->setClass('Services_Brush', 'brush'); // methods called as brush.*
$server->setClass('Services_Pick', 'pick');   // methods called as pick.*

// Create a request object
$request = new Services_Request();

// Utilize a custom response
$server->setResponseClass('Services_Response');

echo $server->handle($request);

80.3.12. Optimización del rendimiento

Example 80.17. Cache server definitions between requests

El ejemplo a continuación ilustra cómo almacenar en caché las definiciones del servidor entre peticiones.

// Specify a cache file
$cacheFile = dirname(__FILE__) . '/xmlrpc.cache';

// Allow Services_Exceptions to report as fault responses
Zend_XmlRpc_Server_Fault::attachFaultException('Services_Exception');

$server = new Zend_XmlRpc_Server();

// Attempt to retrieve server definition from cache
if (!Zend_XmlRpc_Server_Cache::get($cacheFile, $server)) {
    $server->setClass('Services_Comb', 'comb');   // methods called as comb.*
    $server->setClass('Services_Brush', 'brush'); // methods called as brush.*
    $server->setClass('Services_Pick', 'pick');   // methods called as pick.*

    // Save cache
    Zend_XmlRpc_Server_Cache::save($cacheFile, $server);
}

// Create a request object
$request = new Services_Request();

// Utilize a custom response
$server->setResponseClass('Services_Response');

echo $server->handle($request);

[Note] Nota

El archivo de caché del servidor debe ubicarse fuera de la raíz del documento.

Example 80.18. Optimizing XML generation

Zend_XmlRpc_Server utiliza DOMDocument de la extensión de PHP ext/dom para generar su salida XML. Aunque ext/dom está disponible en muchos hosts, no es precisamente la más rápida. Los benchmarks han demostrado que XmlWriter de ext/xmlwriter rinde mejor.

Si ext/xmlwriter está disponible en su host, puede seleccionar el generador basado en XmlWriter para aprovechar las diferencias de rendimiento.

require_once 'Zend/XmlRpc/Server.php';
require_once 'Zend/XmlRpc/Generator/XmlWriter.php';

Zend_XmlRpc_Value::setGenerator(new Zend_XmlRpc_Generator_XmlWriter());

$server = new Zend_XmlRpc_Server();
...

[Note] Realice benchmarks de su aplicación

El rendimiento está determinado por muchos parámetros y los benchmarks sólo aplican al caso de prueba concreto. Las diferencias provienen de la versión de PHP, las extensiones instaladas, el servidor web y el sistema operativo, por nombrar sólo algunos. Asegúrese de realizar sus propios benchmarks de su aplicación y decidir qué generador utilizar en función de sus propios números.

[Note] Realice benchmarks de su cliente

Esta optimización también tiene sentido en el lado del cliente. Simplemente seleccione el generador XML alternativo antes de realizar cualquier trabajo con Zend_XmlRpc_Client.