Nehmen und geben

Unterschiedliche Formen von Web Services, welche davon im konkreten Fall die richtige Wahl darstellen und wie man die einzelnen Varianten in Java umsetzt, erläutert der zweite Teil des Web-Service-Tutorials.

vorlesen Druckansicht 1 Kommentar lesen
Lesezeit: 15 Min.
Von
  • Lars Röwekamp
Inhaltsverzeichnis

Sinnvoll werden Web Services vor allem, wenn man anderen Systemen eigene Dienste zur Verfügung stellt. Die Zielgruppe kann dabei von der eigenen IT über die Nutzer eines VPNs bis zum gesamten Internet reichen. Was sich wie die Lösung aller Probleme anhören mag, ist jedoch mit Vorsicht zu genießen: Aufgrund der verwendeten Protokolle und Verfahren kommen Web Services für Systeme mit hohen Leistungsanforderungen kaum infrage.

Die im ersten Teil des Tutorials zur Anbindung von AltaVistas Übersetzungsdienst benutzte JAXM-API wurde ursprünglich für Web Services auf Messaging Basis konzipiert. Dieser Dienst basiert jedoch auf RPC (Remote Procedure Call), wofür eine eigene API namens JAX-RPC existiert. In einem RPC-Szenario hat der Web Service Request die Form eines Methodenaufrufs samt Ein- und Ausgabeparameter. Er ist an genau eine Ressource gerichtet, die die angefragte Methode ausführt und eine Antwort zurückliefert (SOAP Response). Dabei wartet der Aufrufer auf die Beantwortung seiner Anfrage.

Nachrichtenbasierte Verfahren - auch als dokumentenbasiert bezeichnet - bieten demgegenüber mehr Möglichkeiten. Bei ihnen ist der SOAP-Aufruf eine Botschaft, die der Empfänger asynchron in mehreren Schritten verarbeiten kann. In der Regel beinhalten solche Web Services komplexere XML-Dokumente, etwa komplette Bestellungen, die der Dienst verstehen und verarbeiten kann.

Eine einfache Regel, wann welche Variante anzuwenden ist, gibt es leider nicht, obwohl diese Entscheidung über Erfolg oder Misserfolg eines Web Service entscheiden kann. Eine spätere Umstellung vertreibt mit Sicherheit viele Nutzer. Als Anhaltspunkt für die Wahl dient häufig der Kern des Web Service: Geht es um die zu verarbeitenden Daten oder handelt es sich um eine typische Prozedur, wie das Überprüfen eines Artikellagerbestands? Im ersten Fall ist die Message-basierte Variante zu wählen, im zweiten Fall eignet sich eine RPC-Implementierung besser.

Vor der Realisierung eines eigenen Dienstes soll die Anbindung eines weiteren Web Service mit JAX-RPC an die Webanwendung den Einsatz dieser API demonstrieren. Dabei handelt es sich um eine von xMethods (services.xMethods.net) angebotene Börsenauskunft, die bei Übergabe eines gültigen Aktiensymbols den (fast) aktuellen Börsenwert der Aktie zurückliefert.

Für die Implementierung eines RPC-basierten Web Service muss der Entwickler lediglich die gewünschten Methoden als Java-Interface beschreiben und als -Klasse implementieren. Alle weiteren zur Kommunikation notwendigen Klassen und Interfaces kann man mit dem Tool xrpcc erzeugen. Es gehört zur JAX-RPC-Referenzimplementierung, die wiederum im WSDP (Web Service Development Pack) enthalten ist.

Ähnlich einfach wie das Schreiben eines Web Service ist dessen Anbindung. Auch hier lassen sich vorab generierte Klassen für die notwendige Kommunikation verwenden. Der Client richtet den gewünschten Aufruf an ein lokales Objekt, den so genannten Stub, der die Kommunikation mit dem Web Service Provider abstrahiert.

Insgesamt besteht die Entwicklung eines JAX-RPC-Client aus fünf Schritten:

  • Generieren der notwendigen Hilfs- und Kommunikationsklassen (Stubs);
  • Implementieren des Clients unter Verwendung der generierten Klassen;
  • Kompilieren;
  • optionales Packen als JAR oder WAR;
  • Anwenden des Clients.

Mit xrpcc und einer Beschreibung des anzubindenden Web Service lassen sich alle notwendigen Klassen zur Kommunikation automatisch erzeugen. Zur Beschreibung des Web Service dient der XML-Dialekt WSDL (Web Service Description Language). Da der Aufbau einer solchen Beschreibung Thema des dritten Tutorials ist, seien hier nur ihre entscheidenden Teile erwähnt (siehe Listing 1):

  • Der StockQuoteService beinhaltet genau einen Port namens StockQuote-Port.
  • Das StockQuoteBinding bindet diesen an die Protokollkombination SOAP 1.1 über HTTP.
  • Die Adresse des StockQuotePort ist der URL http://66.28.98.121:9090/soap zugeordnet.
  • Sein Typ ist als StockQuotePortType definiert und verfügt genau über eine Operation namens getQuote.

getQuote nimmt einen Parameter namens symbol vom Typ xsd:string entgegen und liefert als Rückgabe einen Wert vom Typ xsd:float.

Mehr Infos

Listing 1: Web Service Description `StockQuote'

Wesentliche Teile der zur Generierung herangezogenen Web Service Description `StockQuote'.

<?xml version="1.0" encoding="UTF-8" ?> 
<definitions name="StockQuote" ... >
...
<portType name="StockQuotePortType">
<operation name="getQuote" parameterOrder="symbol">
...
</operation>
</portType>
<binding name="StockQuoteBinding" type="tns:StockQuotePortType">
<soap:binding style="rpc"
transport="http://schemas.xmlsoap.org/soap/http" />
...
</binding>

<service name="StockQuoteService">
<port name="StockQuotePort" binding="tns:StockQuoteBinding">
<soap:address location="http://66.28.98.121:9090/soap" />
</port>
</service>
</definitions>

Die eigentliche Generierung der Stubs erfolgt durch den Aufruf von xrpcc; für die im Tutorial verwendete Pfadstruktur etwa so (aus Gründen der Übersicht auf mehrere Zeilen aufgeteilt):

xrpcc -client -d classes -keep -s src/java src/xml/rpc/stockquote/config.xml

Aus der WSDL-Beschreibung erzeugt xrpcc die nötigen Stubs, mit deren Hilfe der Client einen RPC-basierten Web Service anspricht (Abb. 1).

-client sorgt für die Erzeugung der clientseitigen Kommunikationsklassen; -d [Pfad] stellt das Basisverzeichnis für ihre Speicherung ein. Um einen besseren Eindruck davon zu bekommen, was xrpcc generiert, steht im obigen Aufruf zusätzlich -keep, was das Löschen der erzeugten Quellen nach dem Übersetzungslauf verhindert. -s [Pfad] legt fest, wo sie zu finden sind.

Im Wesentlichen verweist die Konfigurationsdatei config.xml auf die WSDL-Datei und gibt die Package-Struktur für die zu generierenden Klassen und Interfaces vor:

<?xml version="1.0" encoding="UTF-8"?> 
<configuration
xmlns= "http://java.sun.com/xml/ns/jax-rpc/ri/config">
<wsdl name="StockQuote"
location= "http://localhost:8080/ws-tutorial/StockQuote.wsdl"
packageName= "de.ix.wstutorial.stockquote.generated">
</wsdl>
</configuration>

Unter http://localhost:8080/ws-tutorial/StockQuote.wsdl steht die Beschreibung des Web Service. Dies ist eine leicht modifizierte Version des Originals (http://services.xmethods.net/soap/urn:xmethods-delayed-quotes.wsdl). Leider zeigte sich bereits an diesem kleinen Beispiel, dass die vielen Auslegungen der Web-Service- und SOAP-Spezifikation nicht immer kompatibel sind: xrpcc konnte das Original wegen der dort enthaltenen Namespace-Angaben nicht korrekt verarbeiten.

Für die Generierung muss man den Tomcat-Server mit der neuen Version der Tutorial-Applikation starten, damit xrpcc auf die angegebene WSDL-Beschreibung zugreifen kann. Nach dem xrpcc-Lauf finden sich die neuen Klassen und Interfaces wie in der Konfigurationsdatei angegeben im Package de.ix.wstutorial.stockquote.generated.

Im zweiten Schritt beginnt die Implementierung des Client. Wie schon beim Übersetzungsdienst soll der Börsenservice mit Hilfe eines eigenen Servlets (StockQuoteServlet) aufzurufen sein. Die Abarbeitung des Requests übernimmt dessen doGet-Methode. Nachdem sie zunächst den Parameter für das Symbol der gewünschten Aktie ausliest

String symbol = request.getParameter(PARAM_SYMBOL); 
if (symbol == null || symbol.equals("")) {
symbol = DEFAULT_SYMBOL;
}

ruft sie den Web Service auf:

StockQuoteService service =  new StockQuoteService_Impl(); 
StockQuotePortType stub = (StockQuotePortType_Stub)service.getStockQuotePort();
float stockQuote = stub.getQuote(symbol);

Unter Verwendung der vorab generierten Klassen und Interfaces sind nur diese drei Zeilen Quellcode nötig, um den Web Service auf einem entfernten Rechner auszuführen. Vergleicht man dies mit dem Aufwand zur JAXM-Anbindung von Babelfish, zeigt sich deutlich der Vorteil dieser Variante.

Wie in der WSDL-Beschreibung gefordert, generierte xrpcc im ersten Schritt ein Interface namens StockQuotePortType, das die Methode getQuote beschreibt. Die zugehörige Implementierung findet sich in StockQuotePortType_Impl. Zusätzlich entstanden ein Interface namens StockQuoteService und dessen Implementierung StockQuoteService_Impl. Die genaue Bedeutung von WSDL-Services, -Ports, -Types und -Operationen sind Thema des dritten Tutorialteils. Hier soll die Information genügen, dass ein Service mehrere Ports beinhalten kann, von denen jeder wiederum über einen Typ verfügt.

Für die Implementierung des StockQuoteServlet bedeutet dies, dass die zur Kommunikation mit dem Web Service notwendige Implementierung des StockQuotePortType (Stub) über den StockQuote-Service erreichbar ist. Steht der Stub zur Verfügung, kann man schließlich den gewünschten Web Service aufrufen. Der Zugriff erfolgt transparent, das heißt als handele es sich um die Methode eines lokalen Objekts. Die Zuordnung zwischen dem Java-Typ des Rückgabeparameters und einem eventuell abweichenden SOAP-Typ erledigen die generierten Klassen beziehungsweise das JAX-RPC-API automatisch.

Als Beispiel für einen eigenen Dienst fungiert ein einfacher Währungsrechner. Da es sich um einen RPC-basierten Web Service handelt, kommt das JAX-RPC-API zum Einsatz. Wieder sind mehrere Schritte notwendig:

  • Schreiben der Web-Service-Definition (Interface) und -Implementierung (Klasse);
  • Kompilieren der erstellten Sourcen:
  • Erstellen einer Konfigurationsdatei für den nächsten Schritt;
  • Generieren der notwendigen Kommunikationsklassen mit xrpcc;
  • Deployment Descriptor erstellen beziehungsweise modifizieren.

Besonders einfach verläuft der erste Schritt: Das Interface CurrencyConverter deklariert lediglich eine Methode namens convertCurrency.

public interface CurrencyConverter extends Remote { 
public double convertCurrency(float value, String fromCurrency, String toCurrency)
throws RemoteException;
}

Um die Kommunikation mit einem entfernen Rechner zu ermöglichen, muss CurrencyConverter von Java.rmi.Remote abgeleitet sein und convertCurrency eine RemoteException werfen können.

Ähnlich einfach wie das Interface ist die Umsetzung des Web Service namens IXCurrencyConverter. Die Klasse implementiert lediglich das Interface, also die Umrechnung zwischen zwei Währungen. Wie alle anderen Listings befindet sich auch das dieser Klasse auf dem iX-Listingsserver.

Ohne weitere Eingriffe sollte das anschließende Übersetzen der Quellen funktionieren (Schritt 2). Interessanter ist die automatische Generierung der Kommunikationsklassen. Wie schon im Beispiel der Börsenauskunft kommt xrpcc zum Einsatz, allerdings mit deutlich anderer Konfigurationsdatei (siehe Listing 2).

Mehr Infos

Listing 2: Serverseitige xrpcc-Konfiguration

Konfigurationsdatei für die automatische Erstellung der Kommunikationsklassen.

  <?xml version="1.0" encoding="UTF-8"?> 
<configuration xmlns="http://java.sun.com/xml/ns/jax-rpc/ri/config">
<service name="CurrencyConverter"
targetNamespace="http://currencyconverter.org/wsdl"
typeNamespace="http://currencyconverter.org/types"
packageName="de.ix.wstutorial.currencyconverter.generated">
<interface
name=
"de.ix.wstutorial.currencyconverter.CurrencyConverter"
servantName=
"de.ix.wstutorial.currencyconverter.IXCurrencyConverter"
/>
</service>
</configuration>

Dies liegt daran, dass im ersten Fall sämtliche Informationen zur Generierung der Kommunikationsklassen aus der WSDL-Beschreibung zu beschaffen waren. Im aktuellen Beispiel dagegen müssen diese Informationen aus der Web Service Definition (Currency-Converter) und der zugehörigen Implementierung IXCurrencyConverter extrahiert werden. Die schwierig von Hand zu erstellende WSDL-Datei entsteht erst bei Erzeugung der Kommunikationsklassen und kann dann anderen bei der Erstellung von Clients helfen.

Bei Generierung der serverseitigen Klassen sieht der xrpcc-Aufruf etwas anders aus als beim Client:

xrpcc -classpath classes -server -d classes -keep \
-s src/java src/xml/rpc/currencyconverter/config.xml

-classpath setzt den relativen Pfad, in dem sich die kompilierten Interfaces und Klassen (CurrencyConverter und IXCurrencyConverter) des Web Service befinden. -server sorgt für die Erzeugung der serverseitigen Kommunikationsklassen. -d, -keep und -s haben dieselbe Bedeutung wie bei der Erzeugung der Client-Stubs. Letzter Parameter des xrpcc-Aufrufs ist stets die Konfigurationsdatei.

Neben den Stubs generiert xrpcc zwei Dateien innerhalb des angegebenen Zielpfades: Zum einen die WSDL-Repräsentation des Web Service, zum anderen CurrencyConverter_Config.properties mit Properties, die im nächsten Schritt zum Aktivieren des Web Service benötigt werden.

Mit der Generierung der Kommunikationsklassen ist die Implementierung des Web Service abgeschlossen. Nun geht es darum, ihn der Allgemeinheit zur Verfügung zu stellen. In der Regel würde man eine eigene Webapplikation dafür bauen. Der Einfachheit halber wird im Beispiel lediglich die bestehende Anwendung um den neuen Dienst erweitert.

Für den Einsatz des Service ist der Web Deployment Descriptor web.xml um den Eintrag für das den Request entgegennehmende Servlet und ein Mapping für dieses zu erweitern. Die Ergänzungen enthält Listing 3. Die anfängliche Bearbeitung des Web Service Request erledigt ein spezielles Servlet der JAX-RPC-Laufzeitumgebung (com.sun.xml.rpc.server.http.JAXRPCServlet). Welche Klassen es für einen SOAP-Aufruf aktivieren muss, steht in der Konfigurationsdatei CurrencyConverter_Config.properties.

Mehr Infos

Listing 3: WSDL-Descriptor

Diese WSDL-Beschreibung erstellt xrpcc automatisch beim Erzeugen der serverseitigen Klassen.

  ... 
<web-app>

<servlet>
<servlet-name>CurrencyConverterEndpoint</servlet-name>
<description>
Endpunkt des Currency Converter Service
</description>
<servlet-class>
com.sun.xml.rpc.server.http.JAXRPCServlet
</servlet-class>
<init-param>
<param-name>configuration.file</param-name>
<param-value>CurrencyConverter_Config.properties</param-value>
</init-param>
<load-on-startup>0</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>CurrencyConverterEndpoint</servlet-name>
<url-pattern>/rpc/currencyConverter/*</url-pattern>
</servlet-mapping>
...

<web-app>
...

Erstes Lebenszeichen eines Web Service (Abb. 2).

Nach dem Ergänzen des Deployment Descriptor und dem Neustart der Webapplikation steht ein Test an. Da es bisher noch keinen Client gibt, kann er lediglich das korrekte Deployment prüfen. Ein Aufruf der URL http://localhost:8080/ws-tutorial/rpc/currencyConverter/ sollte die in Abbildung 2 dargestellte Seite liefern.

Einen Client für den eben erstellten Web Service könnte man genau so konzipieren und entwickeln wie das StockQuoteServlet. Wer den Service nutzen will, ohne selber Hand anzulegen, kann sich des CurrencyConverter-Servlet im Package de.ix.wstutorial.currencyconverter bedienen.

Wie sich gezeigt hat, ist das reine Schreiben eines RPC-basierten Web Service relativ simpel: Mit lediglich einem Interface und einer Klasse ist es erledigt. Die eigentliche Arbeit fällt erst bei der Generierung der Kommunikationsklassen und dem Einsatz des Service an, da dafür spezielle Konfigurations- und Property-Dateien benötigt werden.

SOAP-Nachrichten können neben den eigentlichen XML-Elementen Attachments beinhalten. Während die zugehörigen Klassen und Interfaces bislang in der JAXM-API 1.0 zu finden waren, bilden sie seit Version 1.1 eine eigene API namens SAAJ. Die wichtigste Attachment-Klasse innerhalb dieser API ist javax.xml.soap. AttachmentPart. Mit ihrer Hilfe kann ein Attachment erzeugt und einer SOAP-Nachricht hinzugefügt werden. Dabei sind ein paar Regeln einzuhalten:

  • Das Attachment muss konform zu den MIME-Standards sein (www.ietf. org/rfc/rfc2045.txt).
  • Es muss über einen Inhalt verfügen.
  • Der Attachment-Header muss den Content-Type-Header enthalten.

Das folgende Beispiel zeigt das Erzeugen und Anhängen zweier Attachments vom Typ Text und JPEG:

/* Hinzufügen eines Text-Attachments */ 
AttachmentPart textAttachment = soapMessage.createAttachmentPart();
textAttachment.setContent(myContent, "text/plain");
soapMessage.addAttachmentPart(textAttachment);
/* Hinzufügen eines JPEG-Attachments,
das als Byte Array vorliegt*/
AttachmentPart jpegAttachment = soapMessage.createAttachmentPart();
jpegAttachment.setContent(new ByteArrayInputStream(jpegByteArray), "image/jpeg");
soapMessage.addAttachmentPart(jpegAttachment);

Für den Zugriff auf die Anhänge einer SOAP-Nachricht gibt es die Methode getAttachments(), die einen Iterator über alle Attachments liefert. Mit getAttachments(MimeHeaders headers) bekommt man ebenfalls einen Iterator, aber nur für die Attachments mit dem angegebenen MIME-Header. Die Zahl der Attachments lässt sich mit countAttachments() ermitteln.

Dass Web Services im J2EE-Umfeld eine wichtige Rolle spielen, verdeutlicht die neue J2EE-Spezifikation 1.4 (Public Draft). Ein Großteil der dort enthaltenen Erweiterungen beziehen sich auf Web Services. Schon in der aktuellen Version 1.3 stellt die Verbindung zwischen J2EE auf der einen und Web Services auf der anderen Seite keine Hürde dar. Wie immer ist die Wahl der richtigen Architektur für den Erfolg entscheidend. Leider gibt es kein generelles Rezept, und ein detailliertes Aufzeigen verschiedener Architekturansätze würde bereits eine eigene Artikelserie füllen.

Im Umfeld von J2EE reicht der Web Service gegebenenfalls Anfragen an die EJB-Schicht weiter (Abb 3).

An dieser Stelle sollen daher lediglich die Prinzipien erläutert werden.

Abbildung 3 (entnommen aus [2]) zeigt eine typische Architektur zur Behandlung von Web Requests und Responses. Entscheidend an diesem Ansatz ist der `Single Point of Access', über den alle Anfragen - SOAP oder nicht SOAP - laufen müssen. Dies ermöglicht eine zentrale Installation von Sicherheitsdiensten.

Der Web Service Endpoint behandelt alle eingehenden SOAP-Anfragen und delegiert sie an die Anwendungen der EJB-Schichten. Dies ermöglicht eine vorgelagerte Fehlerbehandlung sowie ein effektives Caching innerhalb der Webschicht. Synchrone Dienste können via JAX-RPC direkt mit den Enterprise Beans kommunizieren. Asynchrone Web Services dagegen müssen zusätzlich über eine JMS-Schicht verfügen. Sie puffert die eingehenden Anfragen in so genannten Message Queues und Topics und leitet diese im Anschluss weiter an die Message Driven Beans der EJB-Schicht des Application Server.

LARS RÖWEKAMP
ist als IT-Berater mit den Schwerpunkten Neue Technologien und OO/Java für seine Firma OpenKnowledge GmbH tätig.

[1] Web Services Home Page: java.sun.com/webservices/

[2] Using Web Services Effectively: java.sun.com/blueprints/webservices/using/webservbp.html

[3] JAXR-Homepage: java.sun.com/xml/jaxr/

[4] xMethods Homepage: www.xMethods.com

[6] Lars Röwekamp; Web-Programmierung; Dienste leisten; Java Web Service Tutorial - Teil 1: Grundlagen; iX 9/2002, S. 121

[7] Java Web Services, D.A. Chappell & T. Jewell, OíReilly 2002, ISBN 0-596-00269-6

Mehr Infos

iX-TRACT

  • Für Vorgänge wie das Erfragen von Kursen oder Beständen via Web Service bietet das JAX-RPC-API gegenüber dem nachrichtenbasierten JAXM große Vorteile.
  • JAX-RPC-Clients erfordern wesentlich weniger Code als die vergleichbare JAXM-Implementierung.
  • Zum Erzeugen der nötigen Kommunikationsklassen dient für Services wie Clients das Werkzeug xrpcc.
Mehr Infos

(ck)