Zend_Amf_Server
bietet einen RPC-artigen Server
für die Behandlung der Anfragen die vom Adobe Flash Player durchgeführt werden indem das
AMF Protokoll verwendet wird. Wie alle Zend Framework Serverklassen,
folgt es der SoapServer API, und bietet ein einfach zu merkendes
Interface für die Erstellung von Servern.
Beispiel 29. AMF Server Grundlagen
Angenommen wir haben eine Klasse Foo
mit einer Anzahl von
öffentlichen Methoden erstellt. Man kann einen AMF Server erstellen
indem der folgende Code verwendet wird:
$server = new Zend_Amf_Server(); $server->setClass('Foo'); $response = $server->handle(); echo $response;
Alternativ kann stattdessen man eine einfache Funktion als Callback anhängen:
$server = new Zend_Amf_Server(); $server->addFunction('myUberCoolFunction'); $response = $server->handle(); echo $response;
Man kann auch mehrere Klassen und Funktionen mischen und verwenden. Wenn man das macht
wird empfohlen das jede von Ihnen einen Namespace erhält um sicherzustellen das keine
Kollision von Methodennamen stattfindet; das kann durchgeführt werden indem man einfach
ein zweites Stringargument entweder an addFunction()
oder an
setClass()
übergibt:
$server = new Zend_Amf_Server(); $server->addFunction('myUberCoolFunction', 'my') ->setClass('Foo', 'foo') ->setClass('Bar', 'bar'); $response = $server->handle(); echo $response;
Zend_Amf_Server
erlaubt es auch Services das Sie dynamisch
geladen werden, basierend auf dem angegebenen Verzeichnispfad. Man kann dem Server so
viele Verzeichnisse wie man will hinzufügen. Die Reihenfolge in der man die
Verzeichnisse zum Server hinzufügt ist die Reihenfolge in der die
LIFO Suche auf den Verzeichnissen durchgeführt wird um die Klasse
zu finden. Das Hinzufügen von Verzeichnissen wird mit der
addDirectory()
Methode durchgeführt.
$server->addDirectory(dirname(__FILE__) .'/../services/'); $server->addDirectory(dirname(__FILE__) .'/../package/');
Wenn entfernte Services aufgerufen werden kann der Quellname einen Unterstrich ("_")
oder Punkt (".") Begrenzer im Verzeichnis haben. Wenn ein Unterstrich verwendet wird
werden die Namenskonventionen der PEAR und Zend Framework Klassen
verwendet. Das bedeutet das wenn man das Sevice com_Foo_Bar
aufruft wird der Server nach der Datei Bar.php
suchen, und zwar in
jedem der eingefügten Pfade unter com/Foo/Bar.php
. Wenn die Punkt
Notation für entfernte Services wie für com.Foo.Bar
verwendet
wird, wird jedem eingefügten Pfad com/Foo/Bar.php
am Ende
hinzugefügt um Bar.php
automatisch zu laden.
Alle AMF Anfragen die an das Skript gesendet werden, werden dann durch den Server behandelt, und eine AMF Antwort wird zurückgegeben.
Alle angehängten Methoden und Funktionen benötigen einen Docblock
Wie alle anderen Serverkomponenten im Zend Framework müssen die Klassenmethoden dokumentiert werden indem PHP Docblocks verwendet werden. Mindestens muß für jedes benötigte Argument eine Beschreibung angegeben werden sowie ein Rückgabewert. Als Beispiel:
// Funktion zum Anhängen: /** * @param string $name * @param string $greeting * @return string */ function helloWorld($name, $greeting = 'Hello') { return $greeting . ', ' . $name; }
// Angehängte Klasse class World { /** * @param string $name * @param string $greeting * @return string */ public function hello($name, $greeting = 'Hello') { return $greeting . ', ' . $name; } }
Andere anmerkungen können verwendet werden, werden aber ignoriert.
Zum eigenen Zend_Amf_Server
von einem Flex Projekt aus zu
verbinden ist recht einfach; man muß zur Endpunkt URI des
Zend_Amf_Server
Sripts zeigen.
Nehmen wir zum Beispiel an das man einen Server erstellt hat und Ihn in der
server.php
Datei im Anwendungsroot platziert, und die
URI deswegen http://example.com/server.php
ist.
In diesem Fall würde man die services-config.xml
Datei so
modifizieren damit das channel endpoint uri Attribut auf diesen Wert gesetzt ist.
Wenn man noch keine service-config.xml
Datei erstellt hat kann man
das tun, indem man das Projekt im Navigator Fenster öffnet. Auf dem Projektnamen
rechts-klickt und 'properties' auswählt. Im Fenster der Projekteigenschaften muß man in
das 'Flex Build Path' Menü, auf den 'Library path' Tab und sicherstellen das die
'rpc.swc
' Datei bei den Projektpfaden hinzugefügt ist und auf Ok
drücken um das Fenster zu schließen.
Man muß dem Compiler auch mitteilen das er die service-config.xml
verwenden soll um den Endpunkt des RemoteObjects zu finden. Um das zu tun muß das
Fenster der Projekteigenschaften nochmals durch einen rechts-klick auf das
Projektverzeichnis vom Navigator aus geöffnet und Eigenschaften ausgewählt werden. Vom
Eigenschaften-Popup muß 'Flex Compiler' aufgewählt und der String:
-services "services-config.xml" hinzugefügt werden. Auf Apply
drücken, anschließend auf OK um die Option zu aktualisieren. Was man jetzt getan hat,
ist dem Flex Compiler zu sagen das er in der Datei
services-config.xml
nach Laufzeitvariablen schauen soll die von
der RemotingObject Klasse verwendet werden.
Jetzt müssen wir Flex mitteilen welche Konfigurationsdateien der Services für die
Verbindung zu unseren entfernten Methoden zu verwenden sind. Aus diesem Grund muß eine
neue 'services-config.xml
' Datei im Flexprojekt src Verzeichnis
erstellt werden. Um das zu tun, muß man auf den Projektfolder rechts klicken und 'new'
'File' auswählen was ein neues Fenster öffnet. Anschließend das Projektverzeichnis
auswählen und dann die Datei 'services-config.xml
' benennen und
auf Beenden drücken.
Flex hat eine neue services-config.xml
erstellt und Sie geöffnet.
Verwende den folgenden Beispieltext für die services-config.xml
Datei. Es muß sichergestellt werden das der Endpunkt so aktualisiert wird das er zu dem
des eigenen Testservers passt. Anschließend sicherstellen das die Datei gespeichert
wird.
<?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://example.com/server.php" class="flex.messaging.endpoints.AMFEndpoint"/> </channel-definition> </channels> </services-config>
Es gibt zwei Schlüsselpunkt im Beispiel. Erstens, aber letztes im Code, erstellen wir
einen AMF Kanal, und spezifizieren den Endpunt als die
URL zu unserem Zend_Amf_Server
:
<channel-definition id="zend-endpoint" <endpoint uri="http://example.com/server.php" class="flex.messaging.endpoints.AMFEndpoint"/> </channel-definition>
Es ist zu beachten das wir diesem Kanal einen Identifikator, "zend-endpoint", gegeben haben. Das Beispiel erstellt ein Ziel für den Service auf zu diesen Kanal zeigt, und fügt es auch als ID hinzu -- in diesem Zall "zend".
In unseren Flex MXML Dateien müssen wir ein RemoteObject an das Service binden. In MXML kann das wie folgt getan werden:
<mx:RemoteObject id="myservice" fault="faultHandler(event)" showBusyCursor="true" destination="zend">
Hier haben wir ein neues entferntes Objekt definiert das durch "myservice"
identifiziert an das Serviceziel "zend" gebunden ist das wir in der
services-config.xml
Datei definiert haben. Dann rufen wir die
Methoden auf Ihnen in unserem ActionScript einfach durch Aufruf von
"myservice.<method>" auf. Als Beispiel:
myservice.hello("Wade");
Wenn wir Namespaces aktivieren würden wir "myservice.<namespace>.<method>" verwenden:
myservice.world.hello("Wade");
Für weitere Informationen über den Aufruf von Flex RemoteObject, besuchen Sie die Adobe Flex 3 Hilfeseite.
Standardmäßig werden alle Exceptions die in den angehängten Klassen oder Funktionen geworfen werden gefangen und als AMF Fehlermeldungen zurückgegeben. Trotzdem wird der Inhalt des ErrorMessage Objekts variieren basierend darauf ob der Server im "Produktions" Modus ist (der Standardzustand) oder nicht.
Wenn er in Produktionsmodus ist wird nur der Exceptioncode zurückgegeben. Wenn der Produktionsmodus ausgeschaltet wird -- etwas das nur für das Testen getan werden sollte -- werden die meisten Exceptiondetails zurückgegeben: Die Meldung der Exception, die Zeile, und der Backtrace werden alle angehängt.
Um den Produktionsmodus auszuschalten muß das folgende getan werden:
$server->setProduction(false);
Um Ihn wieder einzuschalten, muß stattdessen einfach ein boolscher
TRUE
Wert übergeben werden:
$server->setProduction(true);
Der Produktionsmode sollte sparsam deaktiviert werden!
Wir empfehlen den Produktionsmode nur während der Entwicklung auszuschalten. Exceptionmeldungen und Backtraces können sensitive Systeminformationen enthalten auf die nicht von Aussenstehenden zugegriffen werden darf. Selbst wenn AMF ein binäres Format ist, ist die Spezifikation offen, was bedeutet das jeder den Payload potentiell deserialisieren kann.
Ein Feld bei dem man im speziellen Vorsichtig sein muß ist bei PHP Fehlern selbst. Wenn die INI Direktive display_errors aktiviert ist, wird jeder PHP Fehler für das aktuelle Error Reporting Level direkt in der Ausgabe dargestellt -- was den AMF Antwortpayload potentiell unterbrechen kann. Wir empfehlen die display_errors Direktive in der Produktion auszuschalten um solche Probleme zu verhindern.
Fallweise ist es gewünscht das Antwortobjekt leicht zu manipulieren, typischerweise um
zusätzliche Nachrichtenheader zurückzugeben. Die handle()
Methode des Servers gibt das Antwortobjekt zurück, was es erlaubt das zu tun.
Beispiel 30. Nachrichtenheader der AMF Antwort hinzufügen
In diesem Beispiel fügen wir einen 'foo' Nachrichtenheader mit dem Wert 'bar' zu der Antwort hinzu bevor sie zurückgegeben wird.
$response = $server->handle(); $response->addAmfHeader(new Zend_Amf_Value_MessageHeader('foo', true, 'bar')) echo $response;
Ähnlich wie SOAP, erlaubt es AMF Objekte zwischen dem Client und dem Server zu übergeben. Das erlaubt eine große Flexibilität und Bindung zwischen den zwei Umgebungen.
Zend_Amf
bietet drei Methoden für das Mappen von ActionScript und
PHP Objekten.
-
Erstens kann man explizite Bindungen auf Serverlevel erstellen indem die
setClassMap()
Methode verwendet wird. Das erste Argument ist der ActionScript Klassenname, das zweite ist der Name der PHP Klasse auf die gemappt wird:// Die ActionScript Klasse 'ContactVO' auf die PHP Klasse 'Contact' mappen: $server->setClassMap('ContactVO', 'Contact');
-
Zweitens kann die öffentliche Eigenschaft
$_explicitType
in der PHP Klasse gesetzt werden, wobei der Wert die ActionScript Klasse repräsentiert auf die gemappt wird:class Contact { public $_explicitType = 'ContactVO'; }
-
Drittens, in ähnlicher Art und Weise, kann eine öffentliche Methode
getASClassName()
in der PHP Klasse definiert werden; diese Methode sollte die passende ActionScript Klasse zurückgeben:class Contact { public function getASClassName() { return 'ContactVO'; } }
Auch wenn wir nun den ContactVO auf dem Server erstellt have müssen wir nun seine korrespondierende Klasse in AS3 für das Server Objekt erstellen das gemappt werden soll.
Einen Rechtsklick auf das src Verzeichnis des Flex Projekts und New -> ActionScript File auswählen. Name der Datei ContactVO und finish drücken um die neue Datei zu sehen. Den folgenden Code in die Datei kopieren um die Erstellung der Klasse fertigzustellen.
package { [Bindable] [RemoteClass(alias="ContactVO")] public class ContactVO { public var id:int; public var firstname:String; public var lastname:String; public var email:String; public var mobile:String; public function ProductVO():void { } } }
Die Klasse ist syntaktisch identisch zu der von PHP mit dem gleichen Namen. Die Variablennamen sind exakt die gleichen und müssen im gleichen Fall sein um korrekt zu arbeiten. Es gibt zwei eindeutige AS3 Metatags in dieser Klasse. Das Erste kann gebunden werden was ein Änderungsevent wirft wenn es aktualisiert wird. Das zweite Tag ist das RemoteClass Tag welches definiert das diese Klasse ein gemapptes entferntes Objekt haben kann, in diesem Fall mit dem Aliasnamen ContactVO. Es ist erforderlich das dieses Tag und der Wert der in der PHP Klasse gesetzt wurde, strikt identisch sind.
[Bindable] private var myContact:ContactVO; private function getContactHandler(event:ResultEvent):void { myContact = ContactVO(event.result); }
Das folgende Ergebnisevent vom Serviceaufruf wird sofort zu Flex ContactVO gecastet. Alles das bei myContact gebunden ist, wird mit den von ContactVO zurückgegebenen Daten aktualisiert.
Zend_Amf
bietet Tools für das Mappen von Ressource Typen die
von Service Klassen in von ActionScript verwendbaren Daten zurückgegeben werden.
Um spezielle Ressource Typen zu behandeln, muss der Benutzer eine Plugin Klasse
erstellen die nach dem Ressource Namen benannt ist, mit großgeschriebenen Wörtern
und entfernten Leerzeichen (ein Ressouce Typ "mysql result" wird zu MysqlResult),
mit einem Präfix, z.B. My_MysqlResult
. Diese Klasse sollte eine
Methode implementieren, parse()
, die ein Argument - die
Ressource - annimmt und den Wert zurückgibt der an das ActionScript gesendet werden
sollte. Die Klasse sollte in der Datei vorhanden sein nachdem die letzte Komponente des
Namens benannt ist, z.B. MysqlResult.php
.
Das Verzeichnis das die Plugins für das Ressource Handling enthält sollte beim Typloader
von Zend_Amf
registriert sein:
Zend_Amf_Parse_TypeLoader::addResourceDirectory( "My", "application/library/resources/My" );
Für eine detailierte Diskussion für das Laden von Plugins, sehen Sie bitte in das Kapitel Plugin Loader.
Das Standardverzeichnis für Ressourcen von Zend_Amf
wird
automatisch registriert und enthält aktuell Handler für "mysql result" und "stream"
Ressourcen.
// Beispiel für die Implementierung von Ressourcen und die Behandlung // von Mysql Ergebnis Typen class Zend_Amf_Parse_Resource_MysqlResult { /** * Parse resource into array * * @param resource $resource * @return array */ public function parse($resource) { $result = array(); while($row = mysql_fetch_assoc($resource)) { $result[] = $row; } return $result; } }
Der Versuch einen unbekannten Ressource Typ zurückzugeben (z.B. einen für den kein Handler Plugin existiert) führt zu einer Exception.
Auf den Zend_Amf_Server
vom Flash Projekt aus zu verbinden ist
etwas anders als von Flex aus. Trotzdem sind die Funktionen mit
Zend_Amf_Server
die gleichen wie mit Flex sobald die Verbindung
erstellt wurde. Das folgende Beispiel kann auch von einer Flex AS3
Datei aus verwendet werden. Wir werden die selbe Zend_Amf_Server
Konfiguration mit der World Klasse unserer Verbindung wiederverwenden.
Öffne Flash CS und erstelle eine neue Flash Datei (ActionScript 3). Benenne das
Dokument ZendExample.fla
und speichere das Dokument in einem
Verzeichnis das wir für dieses Beispiel verwenden werden. Erstelle eine neue
AS3 Datei im selben Verzeichnis und benenne die Datei
Main.as
. Öffne beide Dateien im Editor. Wir werden jetzt diese
zwei Dateien über die Document Klasse verbinden. Wähle ZendExample aus und klicke auf
"stage". Im Eigenschaftsfenster von "stage" ändere die Document Klasse auf Main. Das
verbindet die Main.as
ActionScript Datei mit dem Benutzer
Interface von ZendExample.fla
. Wenn die Flashdatei ZendExample
ausgeführt wird, dann wird die Klasse Main.as
gestartet. Als
nächstes werden wir ein ActionScript hinzufügen um den AMF Aufruf
durchzuführen.
Jetzt werden wir eine Main Klasse erstellen damit wir die Daten zum Server schicken und
das Ergebnis anzeigen lassen können. Kopiere den folgenden Code in die
Main.as
Datei und wird werden den Code anschauen um zu erklären
was die Rolle eines jeden Elements ist.
package { import flash.display.MovieClip; import flash.events.*; import flash.net.NetConnection; import flash.net.Responder; public class Main extends MovieClip { private var gateway:String = "http://example.com/server.php"; private var connection:NetConnection; private var responder:Responder; public function Main() { responder = new Responder(onResult, onFault); connection = new NetConnection; connection.connect(gateway); } public function onComplete( e:Event ):void{ var params = "Zum Server geschickt"; connection.call("World.hello", responder, params); } private function onResult(result:Object):void { // Die zurückgegebenen Daten anzeigen trace(String(result)); } private function onFault(fault:Object):void { trace(String(fault.description)); } } }
Wir müssen zuerst zwei ActionScript Bibliotheken importieren die den Haufen an Arbeit erledigen. Das erste ist NetConnection welches wie eine Zweiwege-Leitung, zwischen dem Client und dem Server, funktioniert. Das zweite ist ein Responder Objekt welches die Rückgabewerte des Servers behandelt relativ zum Erfolg oder Mißerfolg des Aufrufs.
import flash.net.NetConnection; import flash.net.Responder;
In der Klasse benötigen wir drei Variable um NetConnection, Responder, und die Gateway
URL zu unserer Zend_Amf_Server
Installation
zu repräsentieren.
private var gateway:String = "http://example.com/server.php"; private var connection:NetConnection; private var responder:Responder;
Im Main Contructor erstellen wir einen Responder und eine neue Verbindung zum
Zend_Amf_Server
Endpunkt. Der Responder definiert zwei
unterschiedliche Methoden für die Behandlung des Servers. Der Einfachheit halber haben
wir Sie onResult und onFault benannt.
responder = new Responder(onResult, onFault); connection = new NetConnection; connection.connect(gateway);
In der onComplete Funktion, welche ausgeführt wird sobald das Konstrukt fertiggestellt
wurde, senden wir die Daten zum Server. Wird benötigen eine weitere zusätzliche Zeile
die den Aufruf der Zend_Amf_Server
World->hello Funktion
durchführt.
connection.call("World.hello", responder, params);
Als wir die Responder Variable erstellt haben, haben wir auch eine onResult und eine onFault Funktion definiert welche die Antwort des Servers behandeln. Wir haben diese Funktionen für ein erfolgreiches Ergebnis der Servers hunzugefügt. Ein erfolgreicher Eventhandler wird immer dann ausgeführt wenn die Verbindung zum Server richtig handgehabt wird.
private function onResult(result:Object):void { // Display the returned data trace(String(result)); }
Die onFault Funktion wird aufgerufen wenn eine ungültige Antwort vom Server zurückgekommen ist. Das passiert wenn auf dem Server ein Fehler stattgefunden hat, die URL zum Server ungültig ist, der entfernte Service oder die Methode nicht existiert, und bei jedem anderen Verbindungsrelevanten Problem.
private function onFault(fault:Object):void { trace(String(fault.description)); }
Das ActionScripts für die Erstellung der entfernten Verbindung ist jetzt fertiggestellt.
Der Aufruf der ZendExample Datei führt jetzt die Verbindung zu
Zend_Amf
aus. Rückblickend haben wir die benötigten Variablen
hinzugefügt um eine Verbindung zum entfernten Server zu öffnen, definiert welche
Methoden in der Anwendung verwendet werden sollen wenn die Anwendung eine Antwort vom
Server empfängt, und schlußendlich die Anzeige der zurückgegebenen Daten über
trace()
.
Zend_Amf_Server
erlaubt es Authentifizierung und Authorisierungs-
Hooks zu spezifizieren um den Zugriff auf Services zu kontrollieren. Es wird die
Infrastruktur verwendet die von den
Zend_Auth
und
Zend_Acl
Komponenten angeboten
wird.
Um Authentifizierung zu definieren, muß der Benutzer einen Authentifizierungs-Adapter
anbieten der die abstrakte Klasse Zend_Amf_Auth_Abstract
erweitert. Der Adapter sollte die authenticate()
Methode
implementieren so wie jeder normale
Authentifizierungs-Adapter.
Der Adapter sollte die Eigenschaften _username und
_password von der Vorgängerklasse
Zend_Amf_Auth_Abstract
verwenden um authentifizieren zu können.
Diese Werte werden vom Server gesetzt, indem die
setCredentials()
Methode verwendet wird, bevor
authenticate()
aufgerufen wird wenn die Zugangsdaten in den
AMF Anfrage-Headern empfangen wurden.
Die Identität die vom Adapter zurückgegeben wird sollte ein Objekt sein das die Eigenschaft role enthält damit die Zugriffskontrolle von ACL funktioniert.
Wenn das Authentifizierungs Ergebnis nicht erfolgreich war, wird die Anfrage nicht weiter bearbeitet und eine Fehlermeldung wird zurückgegeben mit den Gründen für den Fehlschlag genommen vom Ergebnis
Der Adapter wird zum Server verbunden indem die setAuth()
Methode verwendet wird:
$server->setAuth(new My_Amf_Auth());
Die Zugriffskontrolle wird durchgeführt indem das Zend_Acl
Objekt verwendet wird das von der setAcl()
Methode gesetzt
wurde:
$acl = new Zend_Acl(); createPermissions($acl); // Zugriffs-Struktur erstellen $server->setAcl($acl);
Wenn das ACL Objekt gesetzt ist, und die Klasse die aufgerufen wird
die initAcl()
Methode definiert wird diese Methode, mit dem
ACL Objekt als Argument, aufgerufen. Die Klasse kann dann
zusätzliche ACL Regeln erstellen und TRUE
zurückgeben, oder FALSE
wenn keine Zugriffskontrolle für diese
Klasse benötigt wird.
Nachdem die ACL gesetzt wurde, wird der Server prüfen ob mit der,
von der Authentifizierung gesetzten, Rolle Zugriff erlaubt ist, die Ressource im
Klassennamen ist (oder NULL
für Funktionsaufrufe) und ob die
Privilegien der Funktionsname sind. Wenn keine Authentifizierung angegeben wurde, wird
die anonymous verwendet, wenn diese definiert wurde, andernfalls
wird der Zugriff verweigert.
if($this->_acl->isAllowed($role, $class, $function)) { return true; } else { require_once 'Zend/Amf/Server/Exception.php'; throw new Zend_Amf_Server_Exception("Access not allowed"); }