PHP - Eigene Web Services mit PHP5 bereitstellen? Kein Problem! PHP5 bietet das nötige Rüstzeug, um bequem und schnell eigene Web Services auf die Beine zu stellen. Ein Einblick, wie das funktioniert, bietet dieses Tutorial.

Eigene Web Services mit PHP5 bereitstellen? Kein Problem! Bietet PHP5 doch das nötige Rüstzeug, um bequem und schnell den eigenen Web Service auf die Beine zu stellen. Doch was, wenn der Client nicht in PHP geschrieben sein soll? Soll Ihr Firefox Plugin auf Webservices zugreifen können oder Sie möchten Ihren Kunden ein ansprechendes Administrationsfrontend zu Ihrem CMS anbieten? Dieses Tutorial erklärt die Erstellung eines Soap Servers mit PHP5 und des dazugehörigen Clients in Javascript.

Markus Bopp ist Student der Informatik an der Fachhochschule-Bonn-Rhein-Sieg. Neben dem Studium ist er freiberuflich für verschiedene Unternehmen als Web-Entwickler tätig. Seine Homepage finden Sie unter php-projekte.de.

Was ist Soap?

Soap (Simple Object Access Protocol) ist ein einfaches XML basiertes Protokoll, mit der Anwendungen Informationen über HTTP austauschen können. Das interessante an Soap ist, dass Sprachübergreifend Objekte und deren sichtbare Eigenschaften über das WWW übertragen und individuell aufbereitet werden können (Stichwort WebServices). Ein berühmter Vertreter, der eine Soap Schnittstelle zur Verfügung stellt, ist Amazon. Mit Amazon’s Soap-Schnittstelle kann man z.B. auf der eigenen Homepage Amazon Angebote nach eigenem Gutdünken platzieren. Es gibt jedoch eine Vielzahl weiterer Verwendungsmöglichkeiten, nicht nur im Bereich der Content-Syndication, die dem blossen Inkludieren von Fremden Seiten oder RSS-Feeds haushoch überlegen sind.

PHP5 bietet nun eine eingebaute Extension, um beliebig Soap Server oder Clients zu erstellen. Es gibt noch weitere Soap Implementationen für PHP (z.B. über PEAR), jedoch sind diese meist in PHP verfasst und somit von der Performance her einer in C geschriebenen Extension unterlegen.

Was brauche ich denn so?

Zunächst empfiehlt sich das PHP5 "Rundum-Sorglos-Paket" XAMPP.

Mit XAMPP kann man sich unter Windows und Linux im Handumdrehen eine PHP5 Testumgebung schaffen und die hier bereitgestellten Code-Beispiele ausprobieren. Nach der Installation von XAMPP muss die Soap-Extension in der php.ini aktiviert werden. Falls Sie kein XAMPP verwenden und nicht wissen, welche php.ini gerade benutzt wird, dann erstellen Sie eine PHP Datei mit untenstehendem Inhalt, kopieren diese in Ihr Document Root und öffnen die Datei im Browser. Falls Sie XAMPP verwenden, können Sie im XAMPP Menu den Punkt "phpinfo()" auswählen. Dort steht in der Zeile "Configuration File (php.ini) Path" der Pfad zur php.ini, die Sie anpassen müssen.

<? phpinfo(); ?>

In der php.ini suchen Sie dann nach der Zeile ";extension=php_soap.dll", entfernen das ";", speichern ab und starten den Apachen neu. Als nächstes benötigen wir noch Firefox, um unseren Client zu testen (alternativ funktioniert natürlich auch die Mozilla-Suite). Ein einfacher ASCII-Text Editor reicht für die Beispiele vollkommen aus.

Der Soap Server

Um einen Soap Server mit PHP5 zu erstellen, haben wir zwei Möglichkeiten. Einmal einen Soap Server mit WSDL Defintionsdatei und einmal einen Server ohne. Eine WSDL Datei (Web Services Description Language) ist eine von XML abgeleitete Beschreibungssprache, um Funktions- bzw. Methodenaufrufe und Datentypen zu definieren. Für den Soap Server ist eine WSDL Datei nicht zwingend notwendig, sollte aber bei steigender Komplexität der Webservices auf jeden Fall eingesetzt werden. Nötig wird eine WSDL Datei spätestens dann, wenn die Datenübertragung mit SSL verschlüsselt werden soll. Denn diese Angabe muss explizit in der WSDL Datei vorhanden sein. Ausserdem gibt es Tools, z.B. für Java, die aus WSDL Dateien Programmcode erzeugen. Dies ist für das Rapid-Prototyping eine äusserst nützliche Hilfe. Ein gutes Tool, um WSDL Dateien On-The-Fly zu erzeugen ist der SOA-Editor.

Wir werden in diesem Teil die Möglichkeit ohne WSDL Dateien behandeln.

Öffnen Sie eine leere Datei und speichern Sie diese als "soap_server.php" in Ihrem Document Root ab. Kopieren Sie nun folgenden Code in die Datei und speichern:

<?
// Die Methoden in dieser Klasse werden weiter unten als Soap Service bereit gestellt
require_once('MySoapClass.php');

// Den WSDL Cache abschalten
ini_set("soap.wsdl_cache_enabled", "0");
/*
Erzeugt eine neue SoapServer Instanz. Der erste Parameter (null) bedeutet, dass keine WSDL Datei verwendet werden soll.
Wenn keine WSDL Datei angegeben wird, muss die uri Option gesetzt sein.
*/
$server = new SoapServer(null, array('uri' => "http://localhost/"));

/*
Bestimmt, dass alle öffentlichen Funktionen der Klasse MySoapClass für den Client erreichbar sein sollen
*/
$server->setClass("MySoapClass");

/*
Behandelt den Soap Request des Clients. Die Antwort wird in XML "verpackt" und an den Client zurückgeschickt
*/
$server->handle();?>

Öffnen Sie eine leere Datei und speichern Sie diese als "MySoapClass.php" im selben Ordner des Documents Roots ab, indem Sie die "soap_server.php" abgespeichert haben. Kopieren Sie nun folgenden Code in die Datei und speichern:

<?
/*
Testklasse mit einigen Funktionen, die über den Soap Client erreichbar sein sollen.

Die für den Soap Server bereitgestellte Klasse MySoapClass erzeugt ein Objekt vom Typ
MyAttributesClass. Dieses Objekt kann dann vom Client über die Funktion MySoapClass::getAttributesClass()
empfangen werden.
Mit der Funktion MySoapClass::provokeSoapFault() lässt sich ein Beispiel Soap Fehler erzeugen.
Der Fehler wird dann als Warnung im Client zu sehen sein.
*/

class MySoapClass {
public $myAttributesClass = null;

public function __construct() {

$this->myAttributesClass = new MyAttributesClass();
}

public function getAttributesClass() {
return $this->myAttributesClass;
}

public function putSomethingToServer($something) {
return new SoapFault('Info','Sie haben folgendes zum Soap Server geschickt: ' . $something);
}
}
class MyAttributesClass {
public $attrib1 = 'Inhalt 1';
public $attrib2 = 'Inhalt 2';
public $attrib3 = 'Inhalt 3';
public $attrib4 = 'Inhalt 4';
public $attrib5 = 'Inhalt 5';
}
?>

In der "soap_server.php" haben wir nun festgelegt, dass keine WSDL Datei verwendet wird und dass die Funktionen der Klasse MySoapClass dem Client zur Verfügung stehen sollen. Die Klasse MySoapClass in "MySoapClass.php" stellt den eigentlichen Service dar. Hier werden dem Client die Funktionen MySoapClass::getAttributesClass() und MySoapClass::putSomethingToServer($something) bereit gestellt. Erstere Funktion übergibt dem Client eine Instanz der Klasse MyAttributesClass. Alle öffentlichen Eigenschaften, werden dann vom Client weiterbehandelt. Die zweite Funktion zeigt, dass selbstverständlich auch Parameter an eine Soap Funktion übergeben werden können.

Der Soap Client

Um den Rahmen dieses Tutorials nicht zu sprengen, liegt Ihnen eine vorgefertigte und einfach zu verwendende Javascript Klasse zur Steuerung des Soap Clients vor. Diese Klasse habe ich im Rahmen meines CM Frameworks erstellt, um bequemer Anfragen an den Soap Server stellen zu können.

Öffnen Sie eine leere Datei und speichern Sie diese als "BlasterSoapClient.js" ab. Allerdings brauchen Sie dies nicht im Document Root zu tun. Sie erfahren später wieso. Kopieren Sie folgenden Code in die Datei und speichern:

/**
* The SoapClient Class
*/
function BlasterSoapClient() {
        this.transportURI = '';
        this.urn = 'urn:noxmethods';    
        this.enablePrivilege = 'UniversalBrowserRead';
}  

/*
*    Method BlasterSoapClient::asyncInvoke() does not halt script when receiving result!
*/
BlasterSoapClient.prototype.asyncInvoke = function (method,args,callback){
    try {
        netscape.security.PrivilegeManager.enablePrivilege(this.enablePrivilege);
    } catch (e) {
        alert(e);
        return false;
    }
    
    var soapCall = new SOAPCall();
    var prot = BlasterSoapClient.prototype;
    
    soapCall.transportURI = this.transportURI;
    
    var params = new Array(args.length);
    for(i=0;i<args.length;i++){
        params[i] = new SOAPParameter(args[i],'')
    }
    
    soapCall.encode(0, method, this.urn, 0, null, params.length, params);
    
    soapCall.asyncInvoke(
      function (response, soapcall, error)
       {
          var r = prot.handleSOAPResponse(response,soapcall,error);
          callback(r);
        }
    );
};

/*
*    Response handler for BlasterSoapClient::asyncInvoke()
*/
BlasterSoapClient.prototype.handleSOAPResponse = function (response,call,error)
{
    if (error != 0)
    {
        throw "Service failure";
    } else
    {
        var fault = response.fault;
        if (fault != null) {
            var code = fault.faultCode;
            var msg = fault.faultString;
            throw "SOAP Fault:\n\n" +
                "Code: "  + code +
                "\n\nMessage: " + msg
        } else
        {
            return response;
        }
    }
    
    throw "Failure";
};

/*
* When using this, all scripts will halt until result could be retrieved
*/
BlasterSoapClient.prototype.invoke = function (func, args)
{
      try {
          netscape.security.PrivilegeManager.enablePrivilege(this.enablePrivilege);
       } catch (e) {
           throw e;
    }
    
       var soap_call = new SOAPCall();
       soap_call.transportURI = this.transportURI;
       
       var p = new Array(args.length);
       for(i=0;i<args.length;i++){
           p[i] = new SOAPParameter(args[i],'');
       }
        
       soap_call.encode(0, func, this.urn, 0, null, p.length, p);
       
       var temp = soap_call.invoke();
       
       if (temp.fault) {
           throw 'Code: ' + temp.fault.faultCode + '\n String: ' + temp.fault.faultString;
       } else {
           var response = new Array();
           response = temp.getParameters(false, {});
           return response[0].value;
       }
}

/*
* User defineable method to handle result when asyncInvoke() is called. add it as third param there!
*/
BlasterSoapClient.prototype.showResults = function (results)
{
    if (!results)
    {
      return;
    }
    
    var params = results.getParameters(false,{});
      alert(params[0].value);
};

Öffnen Sie eine leere Datei und speichern Sie diese als "soap_client_getattrib.html" im selben Ordner ab, indem Sie die Datei "BlasterSoapClient.js" abgespeichert haben. Kopieren Sie folgenden Code in die Datei und speichern:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<title>Ein Soap Client in Javascript</title>
<!-- Die Soap Klasse einbinden -->
<script type="text/javascript" src="BlasterSoapClient.js"></script>
</head>
<body>
<script>
// neue Instanz des Soap Clients erzeugen
var soapClient = new BlasterSoapClient();

// Webpfad zum SoapServer
soapClient.transportURI = 'http://localhost/soap_server.php';
   
try{
    // Parameter Array erzeugen. Da wir keine Parameter in der Funkion des Soap Servers erwarten, hat das Array die länge null.
    var p = new Array(0);
    
    // Aufruf (invoke) der Funktion MySoapClass::getAttributesClass() auf dem SoapServer
    var attributesClass = soapClient.invoke('getAttributesClass',p);
    
    // die Ausgabe sollte [object SOAPPropertyBag] lauten
    document.writeln(attributesClass+'<br /><br />');
    
    // ausgabe der eigenschaften des Objekts MyAttributesClass
    document.writeln('Empfangen:<br />');
    document.writeln(attributesClass.attrib1+'<br />');
    document.writeln(attributesClass.attrib2+'<br />');
    document.writeln(attributesClass.attrib3+'<br />');
    document.writeln(attributesClass.attrib4+'<br />');
    document.writeln(attributesClass.attrib5+'<br />');
    
    
} catch(e) {
    alert(e);
}
</script>
</body>
</html>

Öffnen Sie nun die Datei "soap_client_getattrib.html" mit dem Mozilla Firefox Browser über "Datei öffnen". Sie werden dann darum gebeten, Ihr Einverständnis für die Ausführung des Soap Aufrufs zu erteilen. Warten Sie ein paar Sekunden und klicken Sie dann auf "Allow". Nun müssten Sie folgendes als Ausgabe erhalten:

[object SOAPPropertyBag]    
Empfangen:
Inhalt 1
Inhalt 2
Inhalt 3
Inhalt 4
Inhalt 5

Wenn wir nun die Datei nicht über die "Datei öffnen" Funktion des Browsers, sondern über http://localhost/... aufgerufen hätten, dann wären die Sicherheitseinstellungen von Mozilla Firefox auf den Plan gerufen worden und der Client hätte keine Antwort vom Soap Server erhalten. Das ist auch vollkommen in Ordnung, denn ein Webserver soll dem Client keinen Soap Code unterjubeln können, der dann beliebig Daten nachlädt.

Der Aufruf "BlasterSoapClient.invoke('getAttributesClass',new Array(0));" bewirkt, dass der Client (sprich Mozilla Firefox) wartet, bis vom Server eine Rückmeldung kam. Daher ist die Verwendung von invoke auch nur dann zu empfehlen, wenn nicht viele Daten hin und her transportiert werden sollen (z.B. Login Prozeduren). Für grössere Datenmengen empfiehlt sich der Aufruf der Funktion "BlasterSoapClient.asyncInvoke(funktion,argumente,callbackfunktion)". Der asynchrone Aufruf bewirkt, dass das Skript normal weiterläuft. Zur Behandlung der empfangenen Daten wird jedoch eine Callback Funktion benötigt, die dem Funktionsaufruf mitgegeben wird. Da bei Javascript Funktionen bereits Objekte sind, können wir die Funktione direkt als Parameter an "BlasterSoapClient.asyncInvoke" übergeben.

Für das Beispiel des asynchronen übertragens von Daten mit Soap öffnen Sie eine leere Datei und speichern Sie diese als "soap_client_getattrib_async.html" ab (im gleichen Ordner wie "soap_client_getattrib.html"). Kopieren Sie folgenden Code in die Datei und speichern:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<title>Ein Soap Client in Javascript</title>

<!-- Die Soap Klasse einbinden -->
<script type="text/javascript" src="BlasterSoapClient.js"></script>

</head>
<body>

<script>
// neue Instanz des Soap Clients erzeugen
var soapClient = new BlasterSoapClient();

// Webpfad zum SoapServer
soapClient.transportURI = 'http://localhost/soap_server.php';
   
try{
    // Parameter Array erzeugen. Da wir keine Parameter in der Funkion des Soap Servers erwarten, hat das Array die länge null.
    var p = new Array(0);
    
    // Aufruf (invoke) der Funktion MySoapClass::getAttributesClass() auf dem SoapServer (asynchron)
    soapClient.asyncInvoke('getAttributesClass',p,callbackHandler);
} catch(e) {
    alert(e);
}

// der Rückruf Handler für asyncInvoke
function callbackHandler (results)
{
    if (!results)
    {
      return;
    }
    
    var params = results.getParameters(false,{});
      var attributesClass = params[0].value;
      
    alert(attributesClass+'\n'
     + 'Empfangen:\n'
     + attributesClass.attrib1+'\n'
     + attributesClass.attrib2+'\n'
     + attributesClass.attrib3+'\n'
     + attributesClass.attrib4+'\n'
     + attributesClass.attrib5+'\n');
};
</script>
</body>
</html>

Wenn Sie nun das Beispiel im Browser öffnen, dann sehen Sie, dass es im Grunde nur eine Änderung gab. Statt das Ergebnis das Aufrufs der Soap Funktion direkt in die Variable "attributesClass" zu speichern, geschieht dies beim asynchronen Aufruf erst in der Callback Funktion. Man kann bei grösseren Datenmengen leicht einen Ladebalken einbauen, damit der Benutzer sieht, dass im Hintergrund etwas passiert. Im folgenden werden wir der einfachheit halber mit der synchronen Variante fortfahren.

Nun wollen wir auf unserem Soap Server die Funktion "MySoapClass::putSomethingToServer($something)" ausführen und einen Parameter mitübergeben. Öffnen Sie eine leere Datei und speichern Sie diese als "soap_client_putargs.html" im selben Ordner ab, indem Sie die Datei "BlasterSoapClient.js" abgespeichert haben. Kopieren Sie folgenden Code in die Datei und speichern:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<title>Ein Soap Client in Javascript</title>
<!-- Die Soap Klasse einbinden -->
<script type="text/javascript" src="BlasterSoapClient.js"></script>
</head>
<body>
<script>
// neue Instanz des Soap Clients erzeugen
var soapClient = new BlasterSoapClient();

// Webpfad zum SoapServer
soapClient.transportURI = 'http://localhost/soap_server.php';
   
try{
    // Parameter Array erzeugen. Die Funktion des Soap Server, die wir weiter unten aufrufen erwartet genau einen Parameter.
    var p = ['Irgendein String'];
    
    // Aufruf (invoke) der Funktion MySoapClass::putSomethingToServer($something) auf dem SoapServer
    soapClient.invoke('putSomethingToServer',p);
    
} catch(e) {
    alert(e);
}
</script>
</body>
</html>

Der Parameter mit dem Inhalt "Irgendein String" wurde nun an "MySoapClass::putSomethingToServer($something)" übergeben und es wurde ein SoapFault Objekt an den Client zurückgeschickt mit dem Inhalt der Nachricht. SoapFaults sind wichtig, um Fehler, die auf dem Server passiert sind (z.B. Eingabefehler, Exceptions etc.) dem Client mitzuteilen. Das heisst, der Client braucht nicht auf die Antwort warten und man kann sich Tests sparen, in denen geprüft wird ob alle Values auch wirklich angekommen sind.

Ich hoffe, Ihnen hat meine kleine Einführung in PHP5/Javascript SOAP gefallen. Im zweiten Teil werden wir sehen, wie die über den Client empfangenen Daten mit XUL verwendet werden können und beschäftigen uns näher mit WSDL.

(mb)

Bookmark setzen... These icons link to social bookmarking sites where readers can share and discover new web pages.
  • Print
  • Digg
  • Sphinn
  • del.icio.us
  • Facebook
  • Mixx
  • Bloglines
  • MisterWong
  • MySpace
  • Reddit
  • SEOigg
  • Technorati
  • TwitThis
  • Y!GG
  • Google Bookmarks

Weiterführende Links: