XML-RPC Python vs PHP
| Date: |
2008-04-06 |
| Authors: |
Stéphane KLEIN <stephane at harobed.org> |
| Status: |
Publié |
Il y a quelques mois, j'ai eu à développer un serveur XML-RPC en PHP. Il m'est
alors venu à l'esprit que l'utilisation de ce protocole était un bon exemple
de comparaison entre PHP et Python (avec préalablement une petite idée
derrière la tête).
Le but de ce document est donc de comparer l'utilisation d'une l'API
XML-RPC en langage PHP et en langage python.
Par honnêteté, je tiens à préciser qu'il m'a été difficile d'écrire
une comparaison objective alors que j'avais déjà une conclusion à
l'esprit.
Pour luter contre ce manque d'objectivité, j'ai approfondi mes
connaissances au niveau de la solution la moins efficace. Cela n'a pas
été inutile car j'ai justement trouvé une solution qui simplifie le
code dans le langage le moins mis en valeur.
Par conséquent, je pense que ce document est assez équilibré et pas
trop partisan... enfin... je l'espère...
Avec PHP, le problème se pose déjà au niveau du choix de la librairie... et on
peut dire qu'on a le choix, voici celles que j'ai trouvé (via
XMLRPC.com et Google) :
- XMLRPC-EPI - binding PHP 4.1
- XML-RPC Library for PHP
- phpRPC
- The Incutio XML-RPC Library for PHP
- SimpleXMLRPC for PHP4
- XML-RPC Functions - intégré à PHP mais en expérimental basé sur XMLRPC-EPI
- XML-RPC for PHP
- Pear XML_RPC Package
Liste sans doute incomplète car je n'ai pas beaucoup cherché...
Ca fait du monde tout ça ! En théorie pour faire la même chose !
Normalement, les deux solutions les plus "officiels" sont :
- XML-RPC Functions - intégré à PHP mais en expérimental - basé sur XMLRPC-EPI
- Pear XML_RPC Package
Je ne sais pas pourquoi la librairie XML-RPC interne à PHP est en état
expérimental depuis décembre 2001 dans PHP 4.1 ! Cela ne donne pas vraiment
envie de l'utiliser même si je pense qu'elle doit bien fonctionner. Si je me
réfère aux discussions sur la mailing-list des développeurs PHP
, la librairie XML-RPC interne de PHP est voué à disparaître au
profit de la librairie Pear.
Dans mes précédents développements, j'ai utilisé la libraire Pear
XML-RPC, c'est donc celle-ci que je vais utiliser dans l'exemple PHP
de ce document.
Je ne sais pas si c'est le meilleur choix... mais en tout les cas,
cela me parait la solution la plus officielle.
Je pars du principe que Pear est installé sur votre système. Pour installer le
paquet "XML_RPC" vous devez entrer les commandes suivantes :
$ sudo pear channel-update "pear.php.net"
$ sudo pear install XML_RPC
Normalement, si tout c'est bien passé, le paquet devrait être installé.
Alors ne faite pas comme moi, ne passez pas à coté de la documentation
utilisateur qui se trouve derrière
"End-user Documentation" à
gauche de la page documentation.
Cet exemple met en oeuvre la technique que j'ai utilisé lors de mes
précédents développements. Vous verrez ensuite une solution plus simple
dans le second exemple.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48 | <?php
require_once('XML/RPC/Server.php');
function somme($params)
{
$x = $params->getParam(0)->getval();
$y = $params->getParam(1)->getval();
return new XML_RPC_Response(new XML_RPC_Value($x * $y, 'int'));
}
function renvoi_tableau_fois_2($params)
{
$tableau = $params->getParam(0);
$result = array();
for($i = 0; $i < $tableau->arraysize(); $i++) {
$result[] = new XML_RPC_Value($tableau->arraymem($i)->getval() * 2, 'int');
}
return new XML_RPC_Response(new XML_RPC_Value($result, 'array'));
}
function concat_dict($params)
{
$result = array();
$dict1 = $params->getParam(0);
while($element = $dict1->structeach()) {
$result[$element['key']] = new XML_RPC_Value($element['value']->getval(), 'string');
}
$dict2 = $params->getParam(1);
while($element = $dict2->structeach()) {
$result[$element['key']] = new XML_RPC_Value($element['value']->getval(), 'string');
}
return new XML_RPC_Response(new XML_RPC_Value($result, 'struct'));
}
$server = new XML_RPC_Server(
array(
'somme' => array('function' => 'somme'),
'renvoi_tableau_fois_2' => array('function' => 'renvoi_tableau_fois_2'),
'concat_dict' => array('function' => 'concat_dict')
),
1 // serviceNow
);
?>
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60 | <?php
require_once('XML/RPC.php');
$client = new XML_RPC_Client('/xmlrpc/server2.php', 'localhost');
$client->setDebug(1);
$response = $client->send(
new XML_RPC_Message('somme', array(
new XML_RPC_Value(4, 'int'),
new XML_RPC_Value(8, 'int')
))
);
print $response->value()->getval();
// Affiche : 12
$response = $client->send(
new XML_RPC_Message('renvoi_tableau_fois_2', array(
new XML_RPC_Value(array(
new XML_RPC_Value(1, 'int'),
new XML_RPC_Value(2, 'int'),
new XML_RPC_Value(3, 'int'),
new XML_RPC_Value(4, 'int')
), 'array')
))
);
print("<pre>");
for($i = 0; $i < $response->value()->arraysize(); $i++) {
print $response->value()->arraymem($i)->getval() . "\n";
}
print("</pre>");
/* Affiche :
2
4
6
8
*/
$response = $client->send(
new XML_RPC_Message('concat_dict', array(
new XML_RPC_Value(
array("key1" => new XML_RPC_Value("value1", "string")),
'struct'
),
new XML_RPC_Value(
array("key2" => new XML_RPC_Value("value2", "string")),
'struct'
)
))
);
print("<pre>");
while($element = $response->value()->structeach()) {
print($element['key'] . '=>'. $element['value']->getval() . "\n");
}
print("</pre>");
/* Affiche :
key1=>value1
key2=>value2
*/
?>
|
En réalisant ce document, j'ai découvert les méthodes XML_RPC_Encode et
XML_RPC_Decode. Ces fonctions diminuent fortement la quantité de code nécessaire
pour "coder" et "décoder" les entrées/sorties des fonctions XML-RPC.
Voici le même exemple que le précédent mais en une version réduite grâce à
l'utilisation de XML_RPC_Encode et XML_RPC_Decode.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48 | <?php
require_once('XML/RPC/Server.php');
function somme($params)
{
$x = XML_RPC_Decode($params->getParam(0));
$y = XML_RPC_Decode($params->getParam(1));
return new XML_RPC_Response(XML_RPC_Encode($x * $y));
}
function renvoi_tableau_fois_2($params)
{
$tableau = XML_RPC_Decode($params->getParam(0));
$result = array();
for($i = 0; $i < count($tableau); $i++) {
$result[] = $tableau[$i] * 2;
}
return new XML_RPC_Response(XML_RPC_Encode($result, 'array'));
}
function concat_dict($params)
{
$result = array();
$dict1 = XML_RPC_Decode($params->getParam(0));
foreach($dict1 as $key => $value) {
$result[$key] = $value;
}
$dict2 = XML_RPC_Decode($params->getParam(1));
foreach($dict2 as $key => $value) {
$result[$key] = $value;
}
return new XML_RPC_Response(XML_RPC_Encode($result));
}
$server = new XML_RPC_Server(
array(
'somme' => array('function' => 'somme'),
'renvoi_tableau_fois_2' => array('function' => 'renvoi_tableau_fois_2'),
'concat_dict' => array('function' => 'concat_dict')
),
1 // serviceNow
);
?>
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49 | <?php
require_once('XML/RPC.php');
$client = new XML_RPC_Client('/xmlrpc/server3.php', 'localhost');
$client->setDebug(1);
$response = $client->send(
new XML_RPC_Message('somme', array(
XML_RPC_Encode(4),
XML_RPC_Encode(8)
))
);
print XML_RPC_Decode($response->value());
// Affiche : 12
$response = XML_RPC_Decode($client->send(
new XML_RPC_Message('renvoi_tableau_fois_2', array(XML_RPC_Encode(
array(1, 2, 3, 4)
)))
)->value());
print("<pre>");
for($i = 0; $i < count($response); $i++) {
print $response[$i] . "\n";
}
print("</pre>");
/* Affiche :
2
4
6
8
*/
$response = XML_RPC_Decode($client->send(
new XML_RPC_Message('concat_dict', array(
XML_RPC_Encode(array("key1" => "value1")),
XML_RPC_Encode(array("key2" => "value2")),
))
)->value());
print("<pre>");
foreach($response as $key => $value) {
print($key . '=>'. $value . "\n");
}
print("</pre>");
/* Affiche :
key1=>value1
key2=>value2
*/
?>
|
Cette version est tout de même beaucoup plus simple que le précédent mais nous
allons voir que la version Python est encore plus simple !
Avec Python, le choix de la librairie XML-RPC est très simple, il suffit
d'utiliser les classes disponibles dans la librairie standard de
Python . Encore une fois, la communauté Python évite la
multiplication des projets similaires. La communauté essaie si c'est
possible de n'avoir qu'une librairie et de l'améliorer constamment.
Documentation concernant la partie XML-RPC de la librairie standard
Python :
- Section "XML-RPC client access" qui contient entre autre la documentation
de la classe xmlrpclib, un exemple de client ...
- Section "Basic XML-RPC server" qui contient entre autre la documentation
de la classe SimpleXMLRPCServer, un exemple de serveur...
Cet exemple est identique à la partie serveur des précédents exemples écris en PHP.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 | from SimpleXMLRPCServer import SimpleXMLRPCServer
# Create server
server = SimpleXMLRPCServer(("localhost", 8080))
server.register_introspection_functions()
def somme(x, y):
return x + y
server.register_function(somme)
def renvoi_tableau_fois_2(tableau):
return [v * 2 for v in tableau]
server.register_function(renvoi_tableau_fois_2)
def concat_dict(dict1, dict2):
dict1.update(dict2)
return dict1
server.register_function(concat_dict)
server.serve_forever()
|
On constate que l'écriture de fonctions XML-RPC est très simple. La
méthode est scrupuleusement identique à l'écriture de fonctions python
tout à fait standard (ligne 7 et 8, 12 et 13, 17 à 19). Le passage de
paramètres est réalisé d'une manière totalement transparente.
La seule chose à faire pour rendre une fonction disponible via
XML-RPC est de "l'inscrire" au serveur via la fonction
"register_function" (lignes 10, 15 et 21).
Cet exemple est identique à la partie client des précédents exemples écris en PHP.
1
2
3
4
5
6
7
8
9
10
11 | import xmlrpclib
s = xmlrpclib.Server("http://localhost:8080")
print s.somme(4, 8)
# Affiche : 12
print s.renvoi_tableau_fois_2([1, 2, 3, 4])
# Affiche [2, 4, 6, 8]
print s.concat_dict({"key1": "value1"}, {"key2": "value2"})
# Affiche {'key2': 'value2', 'key1': 'value1'}
|
Pour la partie client, c'est encore plus simple car il suffit de créer
un objet "xmlrpclib.Server" et via cet objet, appeler les fonctions
XML-RPC d'une manière totalement transparente... comme si ces fonctions
étaient des méthodes natives de la classe "xmlrpclib.Server".
Voici un petit bilan de cette comparaison sous la forme d'un tableau :
| |
Python |
PHP |
| Choix de la librairie |
simple : une solution officiel
documenté et maintenu |
- difficile :
- nombreuses librairies
- difficulté à trouver la solution
la plus officiel
|
| Solution retenu |
Librairie standard Python |
Librairie Pear XML-RPC |
| Entrées / Sortie des
fonctions XML-RPC |
Aucun traitement spécifique !
Les fonctions sont écrites comme des
fonctions Python standards |
Il faut coder et décoder toutes les
entrées sorties ! => lourdeur du code,
difficulté de mise au point et de
débogage |
| Déclaration des fonctions
XML-RPC |
Simple passage de la fonction en
paramètre de la méthode
register_function |
Gros tableau imbriqué à passer en
paramètre du constructeur
XML_RPC_Server |
| Appel des fonctions |
Très simple |
Très lourd |
| Avantages |
- quantité de code minimal
- transparence de la solution : très
peu de différence entre
l'utilisation d'une fonction
distante et local.
- choix de la librairie à utiliser
- documentation complète
|
? |
| Inconvénients |
? |
- difficulté de choix de la librairie
- documentation pas très soignée
- aucune transparence : l'utilisation,
la création ou l'utilisation d'une
fonction XML-RPC est totalement
différente à la création et à
l'utilisation de fonctions PHP
standards
- grande quantité de code nécessaire
|
Autre chose : j'ai plus d'expérience en PHP qu'en Python mais la
solution Python est celle qui a été pour moi la plus évidente et qui
m'a demandé le moins de recherche.
L'API XML-RPC de Python est un bon exemple pour illustrer une des
forces de Python : rendre les choses simples et concises.
Vous pourrez faire le même constat dans d'autres librairies Python
traitant du "même" domaine d'activité :
J'essaierais peut être dans un prochain article d'expliquer
pourquoi Python permet de réaliser des API très simples et concises...