Mehr als kalter Kaffee

Stateless SOAP Web Services mit Java und PHP5
Kommentare

Im Rahmen eines Kundenprojekts der Mayflower GmbH musste ein Web Service zwischen einer in Java 1.4 entwickelten Software und einer in PHP 5 entwickelten Applikation entwickelt werden. Dies wurde vor dem Hintergrund einer leichtgewichtigen Lösung und Hinblick auf Wiederverwendung in neuen Erweiterungen der Anwendung realisiert.

Unter Berücksichtigung der existierenden IT-Landschaft und Prozesse (ITIL) des Kunden wurde für die Entwicklung des Java-Endknotens die kleinstmögliche Plattform auf Basis von Apache Tomcat 5.5 und Apache Axis2 (Apache eXtensible Interaction System), Version 1.3, gewählt. Auf der Seite der PHP-Anwendung wurde die von PHP 5 mitgelieferte ext/soap-Funktionalität mit leichten, in PHP 5 selbst entwickelten Modifikationen genutzt.

Die Nutzung von bestehenden Java-Klassen hat einen interessanten Hintergrund. Beim betreffenden Kunden gibt es für verschiedene Berechnungen ein zentrales, JEE-basiertes Clustersystem. Die PHP-Anwendung, welche seit 2004 im Intranet des Kunden genutzt und seitdem ständig weiterentwickelt wird, verwendete bisher für diese speziellen Berechnungen eigene, in PHP programmierte Methoden, welche anhand der Spezifikationen und Algorithmen für den JEE-Cluster entwickelt wurden. Darüber hinaus gab es außerdem noch eine Microsoft-Access-Anwendung, die diese Logiken in VBA (Visal Basic for Application) nachgebildet hatte. Um nun nicht immer drei Systeme zu pflegen, wurde entschieden, die Berechnungslogiken der Java-Klassen des JEE-Clusters zu nutzen, die Access-Anwendung in die PHP-basierte Lösung zu integrieren, beziehungsweise von VBA nach PHP zu portieren und die selbst entwickelten Codeteile zu entfernen. Der Grund für die Nutzung von SOAP anstelle einer JavaBridge ist die sprachübergreifende Nutzung von Objekten und Eigenschaften. So können auch andere Anwendungen in Zukunft auf diesen Web Service zugreifen und die zentrale Berechnungslogik nutzen.

Einführung in Apache Tomcat und Apache Axis2

Apache Tomcat Version 5.5 ist die quelloffene und frei erhältliche Implementation der Apache Software Foundation des Java Servlets 2.4 und JSP 2.0 (JavaServer Pages)-konformen Containers nach den JSR-152– und JSR-154-Standards. Die aktuelle Version 6 ist die entsprechende Weiterentwicklung für die Unterstützung der neuen Auflagen der JSR-Standards (JSR-315, JSR-245) für die Java-Servet-2.5- und JSP-2.1-Technologien. Apache Tomcat gehört, wie auch der sehr weit verbreitete Apache-HTTP-Webserver, zu den Erfolgsgeschichten der Apache Software Foundation.

Apache Axis2 ist die von Grund auf neu entwickelte Implementierung der Web Service Engine Axis unter dem Schirm der Apache Software Foundation mit einer breiten Unterstützung an W3C-Web-Service-Standards. Der aktuelle Katalog an Features zählt von SOAP 1.1 und 1.2 bis hin zu den Neuheiten wie WSDL 2.0, WS-Addressing und WS-Security. Die neue Implementation wurde im Hinblick auf eine breite Unterstützung von Standards sowie einer effizienteren Bearbeitung von XML und einer leichten Erweiterungsmöglichkeit der Axis Engine selber entwickelt. Axis2 liegt aktuell in der Version 1.3 vor. Außerdem haben die Entwickler auch sehr viel Wert auf Flexibilität gelegt, sodass Web Services mit Axis2 sich sehr leicht und schnell entwickeln lassen, ohne dass dabei die Flexibilität durch Erweiterungsmöglichkeiten von Axis – wie bei größeren Projekten üblich – darunter leidet. Dieser Wert spiegelt sich in der breiten Unterstützung von verschiedenen Data Bindings, wie ADB (Axis2-DataBinding-Framework), XMLBeans und JiBX wider.

Ein weiteres Juwel im Feature-Katalog von Axis2 ist die vollständige Unterstützung aller Message Exchange Patterns, wie sie im WSDL-2.0-Standard spezifiziert wurden. Folgend bietet sich eine breite Vielfalt an Kommunikationswegen (in-out asychron, in-out sychron, in-only asynchron, in-only sychron) zwischen Web-Service-Endpunkten. Neu in der Axis2-Implementation ist außerdem die Unterstützung von REST (Representational State Transfer) Services, die in jüngster Zeit immer breitere Unterstützung zu Lasten von SOAP finden. Für die Entwicklung von Web Services steht Axis2 in zwei Distributionen auf der offiziellen Webseite zur Verfügung, nämlich einmal als Binär-Distribution, die einen eigenen minimalen HTTP-Webserver beinhaltet, und zweitens als Web Application Archive (WAR), das man in jedem Servlet/JSP-Container als reine Webapplikation betreiben kann.

Im Rahmen dieses Artikels wird die WAR-Distribution genutzt, um dem Leser einen leichteren Einstieg in die Entwicklung von Stateless SOAP Web Services zu ermöglichen und um unseren zu Grunde liegenden Erfahrungen Rechnung zu tragen. Die Besonderheit der Axis2-WAR-Distribution liegt nämlich sowohl in der verständlichen Ordnerstruktur und in der einfachen Fokussierung auf die Entwicklung der Web Services, da durch den Apache Tomcat eine solide Basis mit einer Vielfalt an Bibliotheken gesichert wird, als auch im vernünftig implementierten Class Loading. Letzeres wird von der Axis2-WAR-Distribution erheblich genutzt, um User Web Services eine von außen gesicherte Umgebung zu bieten. Zur tieferen Einarbeitung und zu Verständnisfragen von Apache Axis2 verweisen wir auf die weiterführende Literatur [Link 1], [Link 2], [Link 3].

Java Web Services mit Axis2 in nur 5 Minuten

Die Entwicklung eines Web Services auf Basis von Axis2 kann eine Angelegenheit von 5 Minuten sein, auch ohne ein erfahrener Java-Entwickler zu sein. Es bedarf lediglich solider Grundkenntnisse der Programmiersprache Java und die Auseinandersetzung mit der Axis2-Dokumentation, die diverse Hilfestellungen und Tipps bietet. Trotzdem unterliegt die Entwicklung der Web Services hauptsächlich den Anforderungen, für die sie ausgelegt wurden.

Im folgenden Beispiel versuchen wir, ein in Java entwickeltes IT-System einer Wetterstation als Web Service zur Verfügung zu stellen, um anderen in PHP entwickelten IT-Systemen einen autarken Zugriff zur Ablesung und Berechnung von wetterspezifischen Informationen zu ermöglichen. Unsere Wetterstation bietet folgende Schnittstelle, die wir unverändert in einem Axis2 Web Service realisieren möchten:

Minute 1

Listing 1: Die Klasse Weather.java

package sample.pojo.data;

public class Weather {
    float temperature;
    String forecast;
    boolean rain;
    float howMuchRain;
    
    public void setTemperature(float temp){
        temperature = temp;
    }
    public float getTemperature(){
        return temperature;
    }
    public void setForecast(String fore){
        forecast = fore;
    }
    public String getForecast(){
        return forecast;
    }
    public void setRain(boolean r){
        rain = r;
    }
    public boolean getRain(){
        return rain;
    }
    public void setHowMuchRain(float howMuch){
        howMuchRain = howMuch;
    }
    public float getHowMuchRain(){
        return howMuchRain;
    }
}

Listing 2: WeatherService.java

package sample.pojo.service;
import sample.pojo.data.Weather;

public class WeatherService{
    Weather weather;
    
    public void setWeather(Weather weather){
        this.weather = weather;
    }
    public Weather getWeather(){
        return this.weather;
    }
}
Minute 3

Für die Entwicklung des Axis2 Web Services bedarf es nur der Erstellung der Beschreibungsdatei services.xml und der Paketierung in einem AAR-Archiv (Axis2 Archive). Die services.xml ist die grundlegende Konfigurations- und Beschreibungsdatei für jeden Axis2-Web-Service. Sie dient dazu, die Schnittstelle des/der Web Service(s) zu definieren und zu konfigurieren. In Listing 3 sehen Sie unsere services.xml.

Listing 3: Die services.xml

        Weather POJO Service
    
        sample.pojo.service.WeatherService
    
Minute 4

Unsere services.xml besteht aus den Bereichen Module, Description, MessageReceiver und Parameter. Module wird hier verwendet, um ein Axis2-Modul zu laden, welches in der normalen Konfiguration von Axis2 nicht mitgeladen wird. In diesem Fall handelt es sich um das Modul für die WS-Addressing-Funktionalität. Wir nutzen das Modul, um zwischen den Web-Service-Endknoten Sessions zu ermöglichen, was man auch durch das Attribut scope des Wurzelelement-Service schnell nachlesen kann. Der Hintergrund ist, dass die Endknoten für die Berechnung und Ablesung der wetterspezifischen Informationen unabhängig voneinander interagieren müssen.

Der Bereich Parameter mit dem Attribut name und dem Wert ServiceClass darf einmalig in der services.xml definiert sein und weist dem Service eine Schnittstelle zu, die in diesem Fall unserer Klasse WeatherService entspricht. Im Bereich MessageReceiver kann man die Kommunikationwege mit dem Web Service definieren, die den WSDL-spezifizierten Message Exchange Patterns entsprechen. In unserem Beispiel tun wir das für die komplette Schnittstelle, was bei anderen Anwendungsfällen auch feingranular auf Funktionsebene definiert werden kann. Das XML-Element Description entspricht der Beschreibung des Web Services, die für ihre Nutzer ersichtlich sein soll.

Minute 5

Die letzte Aufgabe ist die Paketierung des kompilierten Quelltextes und der Konfigurationsdatei sowie das Laden in unsere laufende Tomcat/Axis2-Installation. Dieser Schritt wird im Java-Jargon auch Deployment genannt. Zum Verständnis eine kleine Exkursion in das in Java weit verbreitete Paketieren (Packaging). Übliche Paketierungsformate, die man immer wieder vorfindet, sind das Web Application Archive, das Java Application Archive (JAR), das Enterprise Application Archive (EAR) und das weniger verbreitete Persistence Application Archive (PAR). Jedes dieser Formate ist ein mit dem Kompressionsverfahren ZIP erstelltes Paket mit einer vorher definierten Datei- und Ordnerstruktur, die den kompilierten Applikationsquelltext sowie zusätzliche Deskriptoren im XML-Format beinhaltet. Die Paketierung dient oft zur Entwicklung modularer Software und hilft bei der Definition von Grenzen der Software selbst als auch der Interaktion mit ihr. Ein gutes Beispiel dafür ist das EAR-Paketformat, das die Applikation komplett von außen abschottet, aber wiederum andere Java-Application-Archive beinhalten kann, die untereinander ohne jegliche Beschränkungen interagieren dürfen.

Unsere in Tomcat installierte Axis2-WAR-Distribution ist auch eine übliche Webapplikation – nach dem Namen des Archivs – die vom Tomcat-Container definierten Beschränkungen unterliegt. Die wichtigste Grenze dabei ist das Class Loading, dass von Tomcat aus guten Gründen sehr strikt geregelt wird. Unser Axis2-Web-Application-Archiv wird in der Class-Loader-Hierachie von Tomcat mit einem eigens neu initialisierten Class Loader geladen. Das gleiche Konzept haben sich die Axis2-Entwickler auch zu Nutze gemacht, um Web Services voneinander autark und versionsunabhängig betreiben zu können. Folgend haben die Axis-Entwickler das Axis-Archiv spezifiziert, das eine leichte Variante des WAR-Paketformats darstellt. Die Struktur eines solchen Formats entnehmen sie Abbildung 1.

Abb. 1: Das Axis-Archive-Paketformat

Diese Paketerstellung kann mithilfe von Werkzeugen wie Ant oder Maven sehr stark automatisiert werden, wobei ersteres die einfachere Methode ist und von vielen IDEs wie Eclipse mitgeliefert wird. Ist das Paket erstellt, muss man es jetzt nur in den Ordner services unserer bereits laufenden Tomcat- und Axis2-Installation kopieren. Folgend wird der Web Service – je nach Axis2-Konfiguration – von Axis2 geladen und zur Verfügung gestellt. Aus der Administrationsverwaltung von Axis2 können Sie jetzt die URL der automatisch generierten WSDL-Datei beziehen.

Der PHP-SOAP-Client

Während der Axis2 Web Service als SOAP Server fungiert, ist PHP in diesem Fall der SOAP Client. Die SOAP-Erweiterung von PHP 5 hilft bei der Erstellung von SOAP Servern und -Clients und unterstützt dabei die Spezifikationen von SOAP 1.1, SOAP 1.2 und WSDL 1.1, also leider einen im Vergleich zu Axis2 eingeschränkten Umfang von Funktionen. Inzwischen gibt es von der Firma WSO2 das Web Services Framework for PHP (WSO2 WSF/PHP), welches allerdings bei unserem Kunden nicht eingesetzt werden kann. WSF/PHP ist dabei API-kompatibel zur PHP-SOAP-Erweiterung und ergänzt den Funktionsumfang um WSDL 2.0, WS-Addressing, WS-Security und einige weitere Spezifikationen.

Ein in PHP entwickelter SOAP Client ist dabei sehr schnell programmiert, wie das folgende Listing zeigt:

 SOAP_1_2,
          'trace'        => true));
$temperature = $client->getTemperature();

print $temperature;
?>

Im betreffenden Kundenprojekt ist es aufgrund der API der Java-Klassen notwendig, bis zu 70 Requests zwischen der PHP-Anwendung und dem Axis2-basierten Web Service zu behandeln. Da die Applikation von bis zu 1600 Nutzern verwendet wird, war es darüber hinaus erforderlich, SOAP-Sessions zu nutzen, damit die verschiedenen SOAP Requests eindeutig zugeordnet werden konnten. Die Session ID von Axis2 wird dabei in den SOAP Header im Parameter serviceGroupId hinterlegt, welcher die in Listing 4 angegebene Struktur in XML hat:

Listing 4

http://www.w3.org/2005/08/addressing/anonymous
urn:uuid:65E9C56F702A398A8B11513011677354

Wie bereits erwähnt, ist der Funktionsumfang der SOAP-Erweiterung in PHP nur auf die Grundmethoden rund um SOAP und Webservices beschränkt und es gibt keinen nativen Support für SOAP Sessions. Allerdings kann man sich behelfen, indem man die SOAPClient-Klasse von PHP überschreibt. Zunächst muss eine von Rob Richards entwickelte Klasse mit dem Namen WSASoap integriert werden, welche unter der BSD-Lizenz zu erhalten ist. Mithilfe dieser Klasse hat man dann eine Unterstützung für WS-Addressing in PHP. Die überschriebene SOAPClient-Klasse sieht dann aus wie in Listing 5:

Listing 5: SOAPClient-Klasse

<?php require_once ′WSASoap.php′;

class WeatherService_SOAPClient extends SoapClient
{
    /**
     * Performs a SOAP request
     *
     * @param  string  $request  Request
     * @param  string  $location Location
     * @param  string  $saction  Action
     * @param  integer $version  Version
     * @return SOAP response
     * @access public
     * @author Thorsten Rinne 
     */
    public function __doRequest($request, $location, $saction, $version)
    {
        $dom = new DOMDocument();
        $dom->loadXML($request);

        $wsasoap = new WSASoap($dom);
        $wsasoap->addAction($saction);
        $wsasoap->addTo($location);
        $wsasoap->addMessageID();
        $wsasoap->addReplyTo();

        $request = $wsasoap->saveXML();
        return parent::__doRequest($request, $location, $saction, $version);
    }
}
?>

Die Funktionsweise ist recht einfach, es wird die für die SOAP-Anfrage zuständige Methode __doRequest() überschrieben. Die Methode hat vier Parameter, mit denen der XML String des SOAP Requests, die Adresse des Web Services, die SOAP-Aktion und die SOAP-Version übertragen wird. In der Methode wird der XML String in ein DOM-Objekt transformiert und dieses wird dann mit der WSASoap-Klasse mit den Parametern für die Unterstützung von WS-Addressing erweitert. Nach der Transformation wird das angepasste DOM-Objekt dann wieder als XML String gespeichert und an die originale __doRequest() Methode der PHP-SOAP-Erweiterung weitergereicht.

Um nun die serverseitigen Methoden per SOAP aufzurufen, kann aber nicht mehr direkt über das SOAPClient-Objekt auf die Methoden zugegriffen werden, sondern es muss die SOAP-Low-Level-Methode __soapCall() verwendet werden. Das Beispiel von oben sähe damit so aus:

 SOAP_1_2,
          'trace'        => true));
$temperature = $client->__soapCall('getTemperature', array());

print $temperature;
?>

Nachdem wir nun eine Unterstützung für SOAP Header und damit SOAP Sessions haben, benötigen wir noch eine Methode, um nach dem ersten SOAP Request aus der Antwort von Axis2 die Session ID zu extrahieren. Um die Session ID zu erhalten, wird der XML String aus der SOAP-Antwort in ein XMLReader-Objekt geladen und nach der eindeutigen Beschreibung des Session-ID-Parameters gesucht (Listing 6):

Listing 6

soapClient->initSession();

    $axis2session = $this->_getSoapSession($this->soapClient->__getLastResponse());
    $soapHeader   = new SoapHeader('http://ws.apache.org/namespaces/axis2',
                                   'ServiceGroupId',
                                   $axis2session);

    $this->soapClient->__soapCall('getTemperature', 
     array(), 
     null, 
     $soapHeader);

    /* . */

    /**
     * Returns the SOAP Session string supplied by Tomcat/Axis2
     *
     * @param  string $response SOAP Response
     * @return string
     * @access private
     * @author Thorsten Rinne 
     */
    private function getSoapSession($response)
    {
        $soapsession = '';

        $xml = new XMLReader();
        $xml->XML($response);
        while ($xml->read()) {
            if (strpos($xml->name, 'axis2:ServiceGroupId') !== false) {
                $xml->read();
                $soapsession = $xml->value;
                $xml->read();
            }
        }

        return $soapsession;
    }
}
?>
Zusammenfassung

Seit einigen Monaten läuft nun der Web Service produktiv beim Kunden und es gab bisher keinerlei Probleme bezüglich der SOAP-Implementierung in Axis2 und PHP. Für jede Berechnung benötigt die Anwendung zwischen 70 und 80 SOAP Requests, was etwa 2,5 Sekunden dauert. Das alles läuft unsichtbar für die Anwender, die diese massive Änderung in der Architektur der Applikation nicht bemerkt haben. Inzwischen gab es auch eine Änderung der Berechnungslogiken und diesmal musste in der PHP-Anwendung nicht eine Zeile Code angefasst werden, die Aktualisierung wurde mit der Einbindung der neuen Java-Klassen durchgeführt.

Thorsten Rinne (thorsten.rinne@mayflower.de), Diplom-Informatiker (FH), arbeitet bei der Mayflower GmbH / ThinkPHP in München als Projektleiter und Consultant. In seiner Freizeit ist er Maintainer der Open Source FAQ-Managementsoftware phpMyFAQ.

Periklis Tsirakidis (periklis.tsirakidis@mayflower.de) studiert seit 2003 Informatik (Dipl.) an der Technischen Universität München und ist ebenfalls seit 2003 Angestellter im Bereich Softwareentwicklung der Mayflower GmbH.

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -