Série de tests du Zend_Amf sur plusieurs versions

by Joris on juin 3, 2009

Ayant passé toute la semaine dernière à essayer de comprendre pourquoi est-ce que mon application Flex ne communiquait pas avec mon serveur AMF fourni par le module Zend_Amf, je pense avoir mis le doigt dessus.

Un collègue m’a fourni exemple simpliste d’une application Flex « Hello World » qui fonctionnait chez lui en local. Une fois envoyé, j’ai installé WAMP 2.x et lancé l’application qui marcha. :o

Voulant fusionner mon application avec la sienne, j’ai supprimé tous les dossiers/fichiers inutiles de sa distribution du Zend Framework. Bien évidemment, j’en avais trop supprimé donc j’ai tout remplacé par ma distribution (1.8.1) et là, plus rien ne fonctionnait.

Dans la précipitation, j’ai remis les portions de code initiales dans mon fichier MXML en pensant que c’était mon code qui merdouillait mais même après avoir tout restauré, rien ne fonctionnait… Flex ne communiquait pas avec mon serveur.

J’ai alors comparé ma version du Zend Framework et la sienne et là, j’ai compris que cela venait de là…

Procédure

Une nouvelle version du Zend Framework est sortie il y a quelques jours en version 1.8.2 (changelog) mais qui n’a apparemment pas encore réglé ce soucis.
J’ai alors téléchargé toute les versions depuis la 1.7.7 pour faire des tests sur une machine virtuelle.

J’ai donc sur ma machine virtuelle, une version 5.2.3 de PHP (5.2.9-2 en local) et une arborescence qui me permet de changer aisément de version du Zend Framework via un système de liens symboliques. Faisons un tour des fichiers sources :

Serveur Zend_Amf

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
<?php
set_include_path(
    'library'
    . PATH_SEPARATOR . get_include_path()
);
 
// Inclusion du serveur
require_once 'Zend/Amf/Server.php';
require_once 'Zend/Version.php';
 
 
// Inclusion des services
require_once 'services/Test_Amf.php';
require_once 'services/Hello_Service.php';
 
// Création d'un object Zend_Amf_Server
$server = new Zend_Amf_Server();
 
// Assignation des services
$server->setClass('Test_Amf');
$server->setClass('Hello_Service');
 
// Env' de dev
$server->setProduction(false);
 
// Réponse serveur
echo $server->handle() . Zend_Version::VERSION;
?>

Si je vais sur ma page de serveur, j’obtiens donc ceci :

Zend Amf Endpoint
1.7.7

Comme nous le voyons, j’ai assigné deux services qui sont respectivement Hello_Service.php et Test_Amf.php :

Hello_Service

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
class Hello_Service
{
  public function __construct()
  {
  }
 
  public function getHelloWorld()
  {
    return 'Hey, you requested a Hello from ' . __CLASS__ . '.php class!';
  }
}
?>

Test_Amf

1
2
3
4
5
6
7
8
9
10
11
12
<?php
class Test_Amf {
 
    /**
     *    @return string
     */
    public function helloWorld() {
        // var_dump('foobar');
        return 'Hey, you requested a Hello from ' . __CLASS__ . '.php class!';
    }
}
?>

Fichier source de l’application Flex

Mon fichier test_amf.mxml sera compilé autant que fois qu’il y a de version avec son fichier service-config.xml associé qui pointera sur les différentes versions des serveurs AMF.

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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">
	<mx:RemoteObject
		id="myRemote"
		destination="zend"
		result="resultHandler(event)"
		fault="faultHandler(event)"
		showBusyCursor="true">
	</mx:RemoteObject>
	<mx:Script>
		<![CDATA[
			import mx.rpc.events.InvokeEvent;
			import mx.events.CloseEvent;
			import mx.containers.TitleWindow;
			import mx.charts.GridLines;
			import mx.containers.GridItem;
			import mx.rpc.events.FaultEvent;
			import mx.rpc.events.ResultEvent;
			import mx.controls.Alert;
			import mx.controls.Text;
			import mx.containers.GridRow;
			import mx.utils.ObjectUtil;
 
			private var popup:TitleWindow = new TitleWindow;
 
			public function getHelloWorld():void {
				myRemote.addEventListener(InvokeEvent.INVOKE, setEndPt);
  				myRemote.getHelloWorld();
			}
 
			public function helloWorld():void {
				myRemote.addEventListener(InvokeEvent.INVOKE, setEndPt);
				myRemote.helloWorld();
			}
 
			public function resultHandler(e:ResultEvent):void {
				var gRow:GridRow = new GridRow;
				var gItem:GridItem = new GridItem;
				var child:Text = new Text;
 
				child.text = e.result.toString();
				gItem.addChild(child);
				gRow.addChild(gItem);
				grid.addChild(gRow);
			}
 
			public function faultHandler(e:FaultEvent):void {
 
				var errStr:Text = new Text;
				errStr.text = e.fault.message;
				errStr.width = 450;
 
				popup.width = 500;
				popup.height = 500;
				popup.horizontalScrollPolicy = "no";
				popup.showCloseButton = true;
 
				popup.addEventListener(CloseEvent.CLOSE, closePopup);
 
				popup.title = e.fault.faultString;
 
				if (popup.getChildren().length > 4) {
					popup.removeAllChildren();
				}
 
				popup.addChild(errStr);
 
				if (main.getChildren().indexOf(popup) < 0) {
					main.addChild(popup);
				}
			}
 
			private function closePopup(e:Event):void {
				main.removeChild(popup);
			}
 
			private function setEndPt(e:Event):void {
				var currentEndPt:String = "Current AMF Endpoint : " + myRemote.channelSet.channels[0].endpoint;
				endpt_text.text = "";
				endpt_text.text = currentEndPt;
			}
		]]>
	</mx:Script>
	<mx:Panel id="main"
		width="100%"
		height="100%"
		title="Test Zend_Amf"
		paddingBottom="20"
		paddingLeft="20"
		paddingRight="20"
		paddingTop="20">
		<mx:Text id="endpt_text"
			text="Current AMF Endpoint : " />
		<mx:Grid>
			<mx:GridRow>
				<mx:GridItem>
					<mx:Button
						label="Say hello from Hello_Service"
						click="getHelloWorld()"/>
				</mx:GridItem>
				<mx:GridItem>
					<mx:Button
						label="Say hello from Test_Amf"
						click="helloWorld()"/>
				</mx:GridItem>
				<mx:GridItem>
					<mx:Button
						label="Clear"
						click="{grid.removeAllChildren();}"/>
				</mx:GridItem>
			</mx:GridRow>
		</mx:Grid>
		<mx:Grid id="grid"/>
	</mx:Panel>
</mx:Application>

Et voici donc le fichier de service associé :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?xml version="1.0" encoding="UTF-8"?>
<services-config>
    <services>
        <service id="zend-service"
            class="flex.messaging.services.RemotingService"
            messageTypes="flex.messaging.messages.RemotingMessage">
            <destination id="zend">
                <channels>
                    <channel ref="zend-endpoint"/>
                </channels>
                <properties>
                    <source>*</source>
                </properties>
            </destination>
        </service>
    </services>
    <channels>
        <channel-definition id="zend-endpoint"
            class="mx.messaging.channels.AMFChannel">
            <endpoint uri="http://php-lyon.sqli.com/php-flex/current/1.7.7/"
                class="flex.messaging.endpoints.AMFEndpoint"/>
        </channel-definition>
    </channels>
</services-config>

Pour chaque version, je changeais l’URI du endpoint. Cela aurait très bien pu être fait dynamiquement en Flex mais n’étant pas un pro-Flex, je n’ai pas trop voulu perdre de temps dessus.

Résultats

Une fois l’ensemble mis en place, j’ai procédé à tester chacune des applications en vérifiant rigoureusement les versions du Zend_Amf_Server associé. Voici les captures d’écran des résultats :

zend_amf_177

zend_amf_178

zend_amf_182

J’ai volontairement mis 3 captures différentes, celle qui fonctionne (1.7.7) et dans celles qui ne fonctionnent pas : j’ai mis de la branche 1.7.* et la toute dernière version (1.8.2).

Pour finir…

N’étant pas expérimenté en Flex et AMF, je découvre un peu cette nouvelle technologie qui est de plus en plus demandée. Aussi, j’espère avoir un retour intéressant de Romain qui peut-être pourra m’éclairer sur ces tests. Si vous avez réussi à développer une application viable avec d’autres versions que la 1.7.7 du Zend Framework, je vous serais reconnaissant de me faire partager vos retours d’expérience.
Merci.

9 comments

C’est vraiment étrange ! Je ne vais pas pouvoir t’aider pour le moment, vu que ce que j’ai développé au boulot utilise le composant AMF de Zend 1.7, et je ne voulais pas le mettre à jour avant d’être sur que ça ne casse rien…

Du coup, j’avoue que tu me mets le doute, du coup je testerai demain avec Zend 1.8, et je verrais si je suis dans le même cas, qu’on puisse croiser nos expériences !

by Palleas on 03/06/2009 at 21:17. #

Bonjour,

Je suis dans le même cas, seul la version 1.7.7 fonctionne et me retourne un resultat depuis une fonction PHP.

Par contre je n’arrive pas à retourner des données depuis une BDD pour les introduirent dans une datagrid. La requête passe bien pourtant.
J’ai essayé plusieurs solutions, fonction PHP (mysql_query), Zend (fetchAll), directement en créen un tableau, sans succes.
Les données doivent êtrent formatées?? je ne sais pas.

Pourrais tu donner un exemple de récupération de données depuis une BDD pour les inclurent dans une datagrid?

Merci d’avance

Et bravo pour ce Blog !!

++
;-)

by Fuse on 04/06/2009 at 16:06. #

Bonjour,

J’ai peut être une piste sur le problème : dans HelloService il manque
/**
* @return string
*/
devant la méthode getHelloWorld. Comme c’est requis ça peut peut être expliquer le problème pour ce service (l’erreur affichée dans les deux derniers exemples concerne à chaque fois ce service, l’autre n’a peut être pas le même comportement ?).
Je pense que les services non commentés sont purement ignoré par setClass, d’où l’erreur.

Ensuite la concaténation du server->handle() avec . Zend_Version::VERSION me choque un peu : la méthode handle renvoie un code binaire au format AMF il me semble !

Enfin, pour finir, j’ai moi même mis en place un exemple Zend_Amf avec ZF 1.8, en faisant même transiter mes propres objets, et ça marche très bien (après beaucoup de débug quand même…). Je n’ai pas testé avec la 1.7.

by Fromain on 16/06/2009 at 10:17. #

Bonjour,

Je pense que la méthode getHelloWorld n’est pas trouvée parce qu’elle n’est pas commenté selon la norme PHP Doc.
Ca marche peut être en 1.7.7 parce que la vérification n’était pas faite (comme la méthode n’a pas d’arguments mais selon un type de retour, ce n’est pas « vraiment » indispensable pour fonctionner), mais j’ai lu que ces commentaires étaient indispensable pour que Zend_Amf puisse fournir la description du service à Flex.

Sinon le echo $server->handle() . Zend_Version::VERSION; me paraît étrange. Pourquoi concaténer un numéro de version à la réponse binaire ?

Pour information, j’ai réussi à faire fonction Zend_Amf avec ZF 1.8, même en transférant mes propres objets. Les seuls différences avec ton exemple sont les deux que je cite ci-dessus.

Romain

by Fromain on 16/06/2009 at 12:53. #

Bonjour Romain,

Je te remercie beaucoup pour tes retours ! Après de nouveaux tests sur les nouvelles versions du ZF, je n’ai toujours pas réussi à faire fonctionner mon application. Je l’ai pourtant bien décortiqué mais impossible de la faire communiquer avec le serveur AMF.
J’ai bien mis tous les commentaires, supprimé la concaténation avec Zend_Version mais rien n’y fait. Dans une autre version de l’exemple, j’essaie même de faire un transfert d’objet.

Toujours pareil : en version 1.7.7, tout va bien mais alors en 1.8.2 ou 1.8.3 ça merde… Je ne comprends pas sérieux.

by Joris on 19/06/2009 at 19:34. #

Bonjour,

J’ai le même le soucis que toi en terme de perfomance sur une grosse application qui utilise les mêmes techno :

- flex coté client et Zend_AMF coté serveur.

Effectivement après une mise à niveau vers 1.8.4 des meilleurs performances.

Toutefois il fallait mettre à jour Zend/Loader/PliuginLoader.php

Voir : http://wadearnold.com/blog/flash/zf-184-breaks-adddirectory

Je pense que tu vas te rendre compte des performances sur des objets complexes échangés et sur des volumes importants. Ce qui est mon cas !

J’ai vu sur un site d’une société qu’ils ont réussi à gagner 30 à 40% de performance sur ce type d’application en passant par une socket Unix avant d’attaquer le serveur zend_amf. En faisant comme ceci le serveur zend_amf après la première connexion ne fait plus d’introspection des services. Ce qui forcément augmente les performances. Car sinon à chaque requête, le serveur zend_amf effectue une introspection des services coté serveur.

J’ai comme projet d’étudier un tel système de communication.

Si il y a des personnes qui souhaitent se lancer dans une telle étude je suis partant.

A bientôt
Goela

by Goela on 02/07/2009 at 15:25. #

Bonjour,

Tu as écris un très bon tutoriel. Cependant je suis dans la position des erreurs de méthodes non trouvées.

Quelqu’un a-t-il trouvé la solution pour résoudre ce problème ?

Merci

by cdm on 31/07/2009 at 14:32. #

Bonjour,

J’ai réussi à faire fonctionner le code en remplaçant dans le mxml

Cependant je ne sais pas si c’est la bonne méthode.

by cdm on 31/07/2009 at 16:15. #

J’essaie de remplacer les balises xml car dans le post ils ne s’affichent pas.

1
2
3
4
5
<mx:RemoteObject id="myRemote" destination="zend" source="Hello_Service">
	<mx:method name="getHelloWorld" 
		result="resultHandler(event)"
		fault="faultHandler(event)">
</mx:RemoteObject>

by cdm on 31/07/2009 at 16:17. #

Leave your comment

Not published