Zend_XmlRpc_Client

Zend_XmlRpc_Server

Introduction

Zend_XmlRpc_Server fournit un serveur XML-RPC qui suit les spécifications » dictées par www.xmlrpc.com. Il fournit aussi la méthode system.multicall(), permettant le traitement de requêtes multiples.

Usage de base

Voici un exemple d'utilisation basique :

  1. $server = new Zend_XmlRpc_Server();
  2. $server->setClass('My_Service_Class');
  3. echo $server->handle();

Structures du serveur

Zend_XmlRpc_Server se décompose en un objet serveur (lui-même), un objet requête, réponse, et des objets d'erreurs.

Pour démarrer un serveur Zend_XmlRpc_Server, vous devez attacher une ou plusieurs classes ou fonctions au serveur, grâce à setClass() et addFunction().

Lorsque c'est fait, vous pouvez passer un objet Zend_XmlRpc_Request à Zend_XmlRpc_Server::handle(), sinon par défaut il utilisera un objet Zend_XmlRpc_Request_Http qui récupérera la requête depuis php://input.

Zend_XmlRpc_Server::handle() va alors essayer de traiter la requête. Cette méthode retournera un objet Zend_XmlRpc_Response ou Zend_XmlRpc_Server_Fault. Tous deux possèdent une méthode __toString() qui crée une réponse XML valide XML-RPC.

Anatomy of a webservice

General considerations

For maximum performance it is recommended to use a simple bootstrap file for the server component. Using Zend_XmlRpc_Server inside a Zend_Controller is strongly discouraged to avoid the overhead.

Services change over time and while webservices are generally less change intense as code-native APIs, it is recommended to version your service. Do so to lay grounds to provide compatibility for clients using older versions of your service and manage your service lifecycle including deprecation timeframes.To do so just include a version number into your URI. It is also recommended to include the remote protocol name in the URI to allow easy integration of upcoming remoting technologies. http://myservice.ws/1.0/XMLRPC/.

What to expose?

Most of the time it is not sensible to expose business objects directly. Business objects are usually small and under heavy change, because change is cheap in this layer of your application. Once deployed and adopted, web services are hard to change. Another concern is I/O and latency: the best webservice calls are those not happening. Therefore service calls need to be more coarse-grained than usual business logic is. Often an additional layer in front of your business objects makes sense. This layer is sometimes referred to as » Remote Facade.. Such a service layer adds a coarse grained interface on top of your business logic and groups verbose operations into smaller ones.

Conventions

Zend_XmlRpc_Server permet d'attacher des classes et/ou des fonctions au serveur XML-RPC. Grâce à Zend_Server_Reflection, l'introspection va utiliser les blocs de commentaires pour déterminer les types d'arguments et de réponse de la fonction/classe.

Les types XML-RPC n'ont pas forcément de correspondance native vers un type PHP. Le code fera de son mieux pour deviner le type de données approprié, en se basant sur les valeurs listées dans les balises @param et @return. Certains types XML-RPC n'ont par contre pas d'équivalent PHP direct, ils devront alors être spécifiés manuellement sous forme de balises phpdoc :

  • dateTime.iso8601, une chaîne formatée comme YYYYMMDDTHH:mm:ss

  • base64, données encodées en base64

  • struct, tableau associatif

Voici un exemple d'utilisation de type particulier:

  1. /**
  2. * This is a sample function
  3. *
  4. * @param base64 $val1 Base64-encoded data
  5. * @param dateTime.iso8601 $val2 An ISO date
  6. * @param struct $val3 An associative array
  7. * @return struct
  8. */
  9. function myFunc($val1, $val2, $val3)
  10. {}

PhpDocumentor ne vérifie (valide) pas les types des paramètres, mais les types sont obligatoires pour que le serveur puisse lui, valider les paramètres passés aux appels des méthodes.

Il est parfaitement valide de spécifier plusieurs types pour les paramètres et les retours de méthodes. La spécification XML-RPC suggère que system.methodSignature retourne un tableau des possibilités au regard des paramètres d'entrée de la méthode, et de son type de sortie. Ceci ce fait grâce au caractère '|' de PhpDocumentor

  1. /**
  2. * This is a sample function
  3. *
  4. * @param string|base64 $val1 String or base64-encoded data
  5. * @param string|dateTime.iso8601 $val2 String or an ISO date
  6. * @param array|struct $val3 Normal indexed array or an associative array
  7. * @return boolean|struct
  8. */
  9. function myFunc($val1, $val2, $val3)
  10. {}

Note: Attention toutefois, une signature multiple peut prêter à confusion au regard des personnes utilisant votre service. En général une fonction ne devrait posséder qu'une seule signature.

Utiliser des espaces de noms (Namespaces)

XML-RPC accepte le concept d'espace de noms, ce qui permet de grouper les méthodes XML-RPC. Ceci aide à prévenir les collisions de noms (deux fonctions avec le même nom), de différentes classes. Par exemple le serveur XML-RPC sert des méthodes dans l'espace "system" :

  • system.listMethods

  • system.methodHelp

  • system.methodSignature

En interne la correspondance est faite avec les méthodes du même nom, de Zend_XmlRpc_Server.

Si vous voulez ajouter un espace de noms aux méthodes que vous servez, procédez alors comme suit :

  1. // Toutes les méthodes publiques de My_Service_Class seront accessibles
  2. // via myservice.METHODNAME
  3. $server->setClass('My_Service_Class', 'myservice');
  4.  
  5. // la fonction 'somefunc' sera accessible via funcs.somefunc
  6. $server->addFunction('somefunc', 'funcs');

Requêtes personnalisées

La plupart du temps, vous utiliserez l'objet de requête par défaut Zend_XmlRpc_Request_Http, sans vous en occuper. En revanche si vous avez un besoin spécifique, comme par exemple journaliser la requête, traiter une requête CLI, GUI, ou autre environnement, vous devrez alors créer un objet étendant Zend_XmlRpc_Request. Implémentez les méthodes getMethod() et getParams() afin que le serveur puisse analyser ces informations pour traiter la requête.

Réponses personnalisées

Comme avec les objets de requête, Zend_XmlRpc_Server peut retourner des objets de réponse personnalisés. Par défaut il s'agit d'objets Zend_XmlRpc_Response_Http qui envoient un en-tête HTTP Content-Type HTTP pour XML-RPC. Vous pourriez utiliser des objets de réponse personnalisés pour par exemple renvoyer les réponses vers STDOUT, ou les journaliser.

Pour utiliser une classe de réponse personnalisée, utilisez Zend_XmlRpc_Server::setResponseClass() avant d'appeler handle().

Gérer les exceptions grâce aux erreurs (Faults)

Zend_XmlRpc_Server attrape les Exceptions générées par vos classes/fonctions, et génère une réponse XML-RPC "fault" lorsqu'une exception a été rencontrée. Par défaut, les message et code des exceptions ne sont pas attachés dans la réponse XML-RPC. Ceci est du au fait que de telles exceptions peuvent en dire trop, au regard de la sécurité de votre application.

Des classes d'exception peuvent cependant être mises en liste blanche, et donc utilisées pour les réponses d'erreur ("fault"). Utilisez simplement Zend_XmlRpc_Server_Fault::attachFaultException() en lui passant une classe d'exception :

  1. Zend_XmlRpc_Server_Fault::attachFaultException('My_Project_Exception');

Si vous héritez correctement vos exceptions, vous pouvez alors passer en liste blanche l'exception de plus bas niveau, et ainsi accepter plusieurs types d'exceptions qui en hériteront. Évidemment, les Zend_XmlRpc_Server_Exceptions sont elles automatiquement mises en liste blanche, afin de pouvoir traiter les requêtes vers des méthodes inexistantes, ou toute autre erreur "générique".

Toute exception rencontrée, mais non mise en liste blanche, donnera naissance à une réponse d'erreur avec le code "404" et le message "Unknown error".

Cacher la définition du serveur entre les requêtes

Attacher beaucoup de classes au serveur XML-RPC peut consommer beaucoup de ressources, car l'introspection de chaque classe/fonction est mise en place.

Pour améliorer les performances, Zend_XmlRpc_Server_Cache peut être utilisé pour mettre en cache la définition d'un serveur. Combiné à __autoload(), ceci améliore grandement les performances.

Un exemple d'utilisation :

  1. function __autoload($class)
  2. {
  3.     Zend_Loader::loadClass($class);
  4. }
  5.  
  6. $cacheFile = dirname(__FILE__) . '/xmlrpc.cache';
  7. $server = new Zend_XmlRpc_Server();
  8.  
  9. if (!Zend_XmlRpc_Server_Cache::get($cacheFile, $server)) {
  10.     require_once 'My/Services/Glue.php';
  11.     require_once 'My/Services/Paste.php';
  12.     require_once 'My/Services/Tape.php';
  13.  
  14.     $server->setClass('My_Services_Glue', 'glue');
  15.     // espace de noms glue
  16.     $server->setClass('My_Services_Paste', 'paste');
  17.     // espace de noms paste
  18.     $server->setClass('My_Services_Tape', 'tape');
  19.     // espace de noms tape
  20.  
  21.     Zend_XmlRpc_Server_Cache::save($cacheFile, $server);
  22. }
  23.  
  24. echo $server->handle();

L'exemple ci dessus essaye de récupérer la définition du serveur via le fichier xmlrpc.cache. Si ceci échoue, alors les classes nécessaires au service sont chargées, attachées au serveur, et une tentative de création de cache est lancée.

Exemples d'utilisation

Voici quelques exemples qui démontrent les diverses options disponibles pour un serveur XML-RPC.

Example #1 Utilisation basique

L'exemple ci dessous attache une fonction au service XML-RPC.

  1. /**
  2. * Retourne le hash MD5 d'une valeur
  3. *
  4. * @param string $value Valeur à hasher
  5. * @return string Hash MD5 de la valeur
  6. */
  7. function md5Value($value)
  8. {
  9.     return md5($value);
  10. }
  11.  
  12. $server = new Zend_XmlRpc_Server();
  13. $server->addFunction('md5Value');
  14. echo $server->handle();

Example #2 Attacher une classe

L'exemple ci dessous montre comment attacher les méthodes publiques d'une classe en tant que méthodes XML-RPC.

  1. $server = new Zend_XmlRpc_Server();
  2. $server->setClass('Services_Comb');
  3. echo $server->handle();

Example #3 Attaching a class with arguments

The following example illustrates how to attach a class' public methods and passing arguments to its methods. This can be used to specify certain defaults when registering service classes.

  1. class Services_PricingService
  2. {
  3.     /**
  4.      * Calculate current price of product with $productId
  5.      *
  6.      * @param ProductRepository $productRepository
  7.      * @param PurchaseRepository $purchaseRepository
  8.      * @param integer $productId
  9.      */
  10.     public function calculate(ProductRepository $productRepository,
  11.                               PurchaseRepository $purchaseRepository,
  12.                               $productId)
  13.     {
  14.         ...
  15.     }
  16. }
  17.  
  18. $server = new Zend_XmlRpc_Server();
  19. $server->setClass('Services_PricingService',
  20.                   'pricing',
  21.                   new ProductRepository(),
  22.                   new PurchaseRepository());

The arguments passed at setClass() at server construction time are injected into the method call pricing.calculate() on remote invokation. In the example above, only the argument $purchaseId is expected from the client.

Example #4 Passing arguments only to constructor

Zend_XmlRpc_Server allows to restrict argument passing to constructors only. This can be used for constructor dependency injection. To limit injection to constructors, call sendArgumentsToAllMethods and pass false as an argument. This disables the default behavior of all arguments being injected into the remote method. In the example below the instance of ProductRepository and PurchaseRepository is only injected into the constructor of Services_PricingService2.

  1. class Services_PricingService2
  2. {
  3.     /**
  4.      * @param ProductRepository $productRepository
  5.      * @param PurchaseRepository $purchaseRepository
  6.      */
  7.     public function __construct(ProductRepository $productRepository,
  8.                                 PurchaseRepository $purchaseRepository)
  9.     {
  10.         ...
  11.     }
  12.  
  13.     /**
  14.      * Calculate current price of product with $productId
  15.      *
  16.      * @param integer $productId
  17.      * @return double
  18.      */
  19.     public function calculate($productId)
  20.     {
  21.         ...
  22.     }
  23. }
  24.  
  25. $server = new Zend_XmlRpc_Server();
  26. $server->sendArgumentsToAllMethods(false);
  27. $server->setClass('Services_PricingService2',
  28.                   'pricing',
  29.                   new ProductRepository(),
  30.                   new PurchaseRepository());

Example #5 Attaching a class instance

setClass() allows to register a previously instantiated object at the server. Just pass an instance instead of the class name. Obviously passing arguments to the constructor is not possible with pre-instantiated objects.

Example #6 Attacher plusieurs classes grâce aux espaces de noms

L'exemple ci dessous montre comment attacher plusieurs classes grâce aux espaces de noms.

  1. require_once 'Services/Comb.php';
  2. require_once 'Services/Brush.php';
  3. require_once 'Services/Pick.php';
  4.  
  5. $server = new Zend_XmlRpc_Server();
  6. $server->setClass('Services_Comb', 'comb');
  7. // méthodes appelées sous la forme comb.*
  8. $server->setClass('Services_Brush', 'brush');
  9. // méthodes appelées sous la forme brush.*
  10. $server->setClass('Services_Pick', 'pick');
  11. // méthodes appelées sous la forme pick.*
  12. echo $server->handle();

Example #7 Spécifier les exceptions à utiliser en cas d'erreurs dans les réponses XML-RPC

L'exemple ci dessous montre comment spécifier les exceptions à utiliser en cas d'erreurs dans les réponses XML-RPC.

  1. require_once 'Services/Exception.php';
  2. require_once 'Services/Comb.php';
  3. require_once 'Services/Brush.php';
  4. require_once 'Services/Pick.php';
  5.  
  6. // Utilise les Services_Exception pour les erreurs
  7. Zend_XmlRpc_Server_Fault::attachFaultException('Services_Exception');
  8.  
  9. $server = new Zend_XmlRpc_Server();
  10. $server->setClass('Services_Comb', 'comb');
  11. // méthodes appelées sous la forme comb.*
  12. $server->setClass('Services_Brush', 'brush');
  13. // méthodes appelées sous la forme brush.*
  14. $server->setClass('Services_Pick', 'pick');
  15. // méthodes appelées sous la forme pick.*
  16. echo $server->handle();

Example #8 Utiliser un objet de requête personnalisé

Some use cases require to utilize a custom request object. For example, XML/RPC is not bound to HTTP as a transfer protocol. It is possible to use other transfer protocols like SSH or telnet to send the request and response data over the wire. Another use case is authentication and authorization. In case of a different transfer protocol, one need to change the implementation to read request data.

L'exemple suivant montre comment utiliser un objet de requête personnalisé.

  1. require_once 'Services/Request.php';
  2. require_once 'Services/Exception.php';
  3. require_once 'Services/Comb.php';
  4. require_once 'Services/Brush.php';
  5. require_once 'Services/Pick.php';
  6.  
  7. // Utilise les Services_Exception pour les erreurs
  8. Zend_XmlRpc_Server_Fault::attachFaultException('Services_Exception');
  9.  
  10. $server = new Zend_XmlRpc_Server();
  11. $server->setClass('Services_Comb', 'comb');
  12. // méthodes appelées sous la forme comb.*
  13. $server->setClass('Services_Brush', 'brush');
  14. // méthodes appelées sous la forme brush.*
  15. $server->setClass('Services_Pick', 'pick');
  16. // méthodes appelées sous la forme pick.*
  17.  
  18. // Crée un objet de requête
  19. $request = new Services_Request();
  20.  
  21. echo $server->handle($request);

Example #9 Utiliser un objet de réponse personnalisé

L'exemple suivant montre comment utiliser un objet de réponse personnalisé.

  1. require_once 'Services/Request.php';
  2. require_once 'Services/Response.php';
  3. require_once 'Services/Exception.php';
  4. require_once 'Services/Comb.php';
  5. require_once 'Services/Brush.php';
  6. require_once 'Services/Pick.php';
  7.  
  8. // Utilise les Services_Exception pour les erreurs
  9. Zend_XmlRpc_Server_Fault::attachFaultException('Services_Exception');
  10.  
  11. $server = new Zend_XmlRpc_Server();
  12. $server->setClass('Services_Comb', 'comb');
  13. // méthodes appelées sous la forme comb.*
  14. $server->setClass('Services_Brush', 'brush');
  15. // méthodes appelées sous la forme brush.*
  16. $server->setClass('Services_Pick', 'pick');
  17. // méthodes appelées sous la forme pick.*
  18.  
  19. // Crée un objet de requête
  20. $request = new Services_Request();
  21.  
  22. // Utilise une réponse personnalisée
  23. $server->setResponseClass('Services_Response');
  24.  
  25. echo $server->handle($request);

Optimisation des performances

Example #10 Cache entre les requêtes

Les exemples suivants montrent comment gérer une politique de cache inter-requêtes.

  1. require_once 'Services/Request.php';
  2. require_once 'Services/Response.php';
  3. require_once 'Services/Exception.php';
  4. require_once 'Services/Comb.php';
  5. require_once 'Services/Brush.php';
  6. require_once 'Services/Pick.php';
  7.  
  8. // Specifier un fichier de cache
  9. $cacheFile = dirname(__FILE__) . '/xmlrpc.cache';
  10.  
  11. // Utilise les Services_Exception pour les erreurs
  12. Zend_XmlRpc_Server_Fault::attachFaultException('Services_Exception');
  13.  
  14. $server = new Zend_XmlRpc_Server();
  15.  
  16. // Essaye de récupérer la définition du serveur via le cache
  17. if (!Zend_XmlRpc_Server_Cache::get($cacheFile, $server)) {
  18.     $server->setClass('Services_Comb', 'comb');
  19.     // méthodes appelées sous la forme comb.*
  20.     $server->setClass('Services_Brush', 'brush');
  21.     // méthodes appelées sous la forme brush.*
  22.     $server->setClass('Services_Pick', 'pick');
  23.     // méthodes appelées sous la forme pick.*
  24.  
  25.     // Sauve le cache
  26.     Zend_XmlRpc_Server_Cache::save($cacheFile, $server));
  27. }
  28.  
  29. // Crée un objet de requête
  30. $request = new Services_Request();
  31.  
  32. // Utilise une réponse personnalisée
  33. $server->setResponseClass('Services_Response');
  34.  
  35. echo $server->handle($request);

Note: The server cache file should be located outside the document root.

Example #11 Optimizing XML generation

Zend_XmlRpc_Server uses DOMDocument of PHP extension ext/dom to generate it's XML output. While ext/dom is available on a lot of hosts it is is not exactly the fastest. Benchmarks have shown, that XMLWriter from ext/xmlwriter performs better.

If ext/xmlwriter is available on your host, you can select a the XMLWriter-based generator to leaverage the performance differences.

  1. require_once 'Zend/XmlRpc/Server.php';
  2. require_once 'Zend/XmlRpc/Generator/XMLWriter.php';
  3.  
  4. Zend_XmlRpc_Value::setGenerator(new Zend_XmlRpc_Generator_XMLWriter());
  5.  
  6. $server = new Zend_XmlRpc_Server();
  7. ...

Note: Benchmark your application
Performance is determined by a lot of parameters and benchmarks only apply for the specific test case. Differences come from PHP version, installed extensions, webserver and operating system just to name a few. Please make sure to benchmark your application on your own and decide which generator to use based on your numbers.

Note: Benchmark your client
This optimization makes sense for the client side too. Just select the alternate XML generator before doing any work with Zend_XmlRpc_Client.


Zend_XmlRpc_Client