6 XML Remote Procedure Calls (XML-RPC) Eine bewährte und sehr verbreitete Technik zur Entwicklung von Client-Server-Anwendungen ist der so genannte Remote Procedure Galt (RPC). Der Client ruft mit einem lokalen Prozeduraufruf einen Dienst auf, der in der Regel auf einem anderen Rechner angeboten wird. Implementierungen des RPC-Mechanismus unterscheiden sich im Allgemeinen für verschiedene Programmiersprachen. In diesem Kapitel wird am Beispiel von XML-RPG gezeigt. wie das Protokoll HTIP und XML als Kommunikationsformat für den entfernten Prozeduraufruf eingesetzt werden können. Vom Konzept her ist diese Technik programmiersprachenunabhängig. Wir werden uns hauptsächlich mit der Java-Implementierung beschäftigen. Die Aufgabe 5 dieses Kapitels nutzt eine PHP-Implementierung auf der Client-Seite. 6.1 Grundkonzept und ein erstes Beispiel XML-RPG ist ein einfaches Protokoll für den entfernten Prozeduraufruf (RPC). XML-RPC verwendet XML als Datenaustauschformat. Anfrage (Methodenname, Parameter) und Antwort (Rückgabewert des Methodenaufrufs) werden als XML-Dokumente mit HTTP übermittelt. Bild 6.1 zeigt den Nutzdatenteil der HTTP-Anfrage (HTIPMethode ist POST) und den Nutzdatenteil der HTIP-Antwort beim Aufruf der entfernten Methode get Echo. Bild 6.1' XML-RPC-Anfrage < ? xrnl v er e ion= " l .O" e n c o d i n g = " UT F - S " ? ><m e thodC a l l ><m e thodN am e>- echo. g etEcho </m e thodNam e ><p aram e >< pa rem>< v al u e > Hall o < / v alu e></p ar am></p aram ~>- </m ethodC a l l>- EchoClient ,.,---,-_--='~~~=--,.,----,--____,-,,---_. EchoServer < ? xml v er ~ ion="l.O" e n c o di n g = " UT F - S " ?>- <m e thodR e ~p on ~ e ><p aram ~>- < pa r a m><v a lu e>-H a l lo </ v a lu e></param></p ar am ~>< / met h o d Re~ p o n~e >- XML-RPC-Antwort XML-RPC-Anfrage und Antwott 6 XML Remote Procedure Calls 206 Die Nutzdatenteile werden auch als -Antwort bezeichnet. (XML~RPC) XML~RPC~Anfrage bzw. Eine XML-RPC-Anfrage kann mehrere Parameterwerte enthalten (gemäß der Signatur der entfernten Methode). Eine XML~RPC~ Antwort enthält jedoch genau einen Wert. Struktur der XML~RPC~Anfrage <rrethodCa11> <methodName>Methodenna~/methodName> <params> <param> <va 1ue>Parameterwert</va 1ue> </param> </params> </methodCa11 > Struktur der XML-RPC-Antwort <methodResponse> <params> <pararn> <val ue>Rückgabewert</val ue> </param> </params> </methodResponse> Datentypen Parameter- und Rückgabewerte haben jeweils einen bestimmten Datentyp. XML~RPC unterstützt insgesamt acht Datentypen einfache Datentypen (z. B. Zeichenketten. Zahlen) und zusammengesetzte Datentypen (z. B. Arrays). Die Datentypen werden im nächsten Abschnitt einzeln anhand von Programmbeispielen vorgestellt. Spezifikation Die Spezifikation von XML~RPC wurde 1999 von Dave Winer veröffentlicht. Sie umfasst nur wenige Seiten und kann unter der Adresse http://www.xm1rpc.com nachgeschlagen werden. Implementierungen von XML-RPC existieren für verschiedene Programmier- und Skriptsprachen (z. B. Java. Perl. PHP. Python), Somit kann beispielsweise ein PHP-Client mit einem Java-Server kommunizieren (siehe Aufgabe 5). Apache XML~RPC Wir nutzen im Folgenden die Java~Implementierung Apache XML~RPC der Apache Software Foundation in der Version 3.1.3. 6.1 Grundkonzept und ein erstes Beispiel 207 Die erforderlichen Bibliotheken GAR-Dateien) können von der Website http://ws.apache.org/xmlrpc/ heruntergeladen werden. Die JAR-Dateien können in ein Verzeichnis Ihrer Wahl kopiert werden. Die Version 3.1.3 von Apache XML-RPC erfüllt die offizielle XMLRPC-Spezifikation. bietet aber auch Erweiterungen (Unterstützung zusätzlicher Datentypen, Performancesteigerung durch Nutzung des so genannten Streaming-Verfahrens, Datenkompression), die in einer reinen Java-Umgebung sinnvoll eingesetzt werden können. Wir beschränken uns hier hauptsächlich auf die Vorstellung der zur XML-RPC-Spezifikation kompatiblen Aspekte. Lediglich Programm 6.10 geht in einem Aspekt auf die Erweiterungen ein. Eine Weiterentwicklung von XML-RPC ist SOAP. das vom World Wide Web Consortium (W3C) definiert wird und neben anderen Standardtechnologien zur Entwicklung von so genannten Web Services genutzt wird (siehe Kapitel 9). SOAP war ehemals Abkürzung für "Simple übject Access Protocol" und ist heute ein eigenständiger Name. SOAP Im Vergleich zu SOAP hat XML-RPC einen geringeren Leistungsumfang, ist aber auch wesentlich einfacher in der Handhabung. Für viele Anwendungen erweist sich XML-RPC als völlig ausreichend und kann auch für die Implementierung von Web Services eingesetzt werden. Zur Implementierung eines XML-RPC-Servers stellt Apache XML- WebServer und RPC die Klassen XmlRpcServer arg. apache. xm1rpc webserver . WebServer und org.apache.xmlrpc server. XmlRpcServer zur Verfügung. WebSer ver implementiert einen speziell für die Behandlung von XML-RPC-Anfragen geeigneten HTTP-Server. der in eigene Applikationen eingebettet werden kann. Dieser ist zu Testzwecken sehr gut geeignet. Für höhere Ansprüche in Bezug auf Performance und Stabilität sind ausgereifte Servlet-Container wie beispielsweise Apache Tomcat besser geeignet. Die Klasse Xm1RpcS er ver verarbeitet die XML-RPC-Anfragen. WebServer( int port) erzeugt einen Server mit der Portnummer port. 6 XML Remote Procedure Calls 208 (XML~RPC) WebServer~Methoden, vOl d start() throws java.lo. IOException startet den Server. voi d shutdown() stoppt den Server. Die Methode getXm1RpcServer () liefert ein Xm1RpcServer~Objekt. Handler Ein so genannter Hand/er ist eine entfernt aufrufbare Methode. Folgende Bedingungen müssen erfüllt sein, • Die Methode muss pub1i c sein, nicht stati c sein und darf nicht den Rückgabetyp voi d haben. • In der Klasse, die den Handler implementiert, muss der parameterlose Standardkonstruktor implizit oder explizit vorhanden sein. Die Klasse arg. apache. xm1rpc ser ver. PropertyHandl erMappi ng kann mehrere Handler verwalten. Ihre Methode void addHandler(String key. Class typ ) t hrows XmlRpc Exception fügt die Handler-Klasse typ mit dem Namen key hinzu. Hierzu wird das Class-Objekt der Handler-Klasse angegeben, Klassenname. cl ass. Eine Ausnahme vom Typ arg. apache. xm 1rpc. Xm 1Rpc Excepti on wird generell ausgelöst, wenn der Server einen Fehler meldet. voi d removeHandler(String key) entfernt alle Handler mit dem Namen key. Mit dem Aufruf der Xm1RpcServer~Methode set Handl erMappi ng werden die Handler für das Xm1RpcServer~Objekt registriert. setHandlerMapping(propertyHandlerMapping) Programm 6.1 Das folgende Beispiel demonstriert eine einfache Anwendung. XML~RPC~ Die Klasse Echo implementiert die Methoden get Echo und get Echo Wi thDate, die ein "Echo" ohne bzw. mit Serverdatum zurückgeben. 6.1 Grundkonzept und ein erstes Beispiel lmport java text SlmpleDateFormat; lmport java.utll.Date; 209 Echo public class Echo ( public String getEcho(String s) ( return 5; public String getEchoWithDate(String s) ( SimpleDateFormat f ~ new SimpleDateFormat( "dd.l'M.yyyy HH:mm:ss"): return "[" + f.format(new Date()) + "] " + s: Die Klasse EchoServer erzeugt eine Instanz der Klasse WebServer, registriert den Echo-Dienst mit dem Namen "echo" und startet dann den Server. lmport arg apache xmlrpc server.PropertyHandlerMapplng; lmport arg apache xmlrpc server.XmlRpcServer; lmport arg apache xmlrpc webserver.WebServer; EchoServer public class EchoServer ( public static void main(String[] args) throws Exception ( int port ~ Integer.parselnt(args[O]): PropertyHandlerMapping phm ~ new PropertyHandlerMapping(): phm. addHandler("echo". Echo.cl ass ) : WebServer webServer = new WebServer(port); XmlRpcServer server = webServer.getXmlRpcServer(); server.setHandlerMapping(phm): webServer.start(): Zur Implementierung eines XML-RPC-Client stellt Apache XML- XmlRpcClient RPC die Klasse org.apache xmlrpc.client. XmlRpcClient zur Verfügung. Mit Hilfe der Klasse org.apache.xmlrpc client.XmlRpcClientConfiglmpl kann ein Xm 1RpcCl i ent-Objekt konfiguriert werden. 6 210 XML Remote Procedure Calls (XML~RPC) Die Xm1RpcCl i ent Conf i 9Imp1-Methode voi d setServerURL(java.net.URL url) legt den URL des Servers fest. Im Beispiel, http ://l oca 1host: 50000. Mit dem Aufruf der Xm1RpcCl i ent-Methode setConfi 9 wird die Konfiguration confi 9 für den Client gesetzt. setConfi g(confi q ). execute Die Xm1RpcCl i ent-Methode Object execute(Str ing method. Object[] params) throws XmlRpc Exception erzeugt eine XML~RPC~Anfrage und sendet sie mittels HTIP zum Server. Die zurückgeschickte XML~RPC~Antwortwird geparst und als Objekt vom Typ Object zurückgegeben. Der String rret hod hat den Aufbau: Dienstname.Methodenname Diens t name ist der Name, unter dem der Dienst auf der Serverseite registriert ist. t1et hodenname ist der Name der Methode, die der Dienst implementiert hat. Das Array params enthält die erforderliehen Parameter. Wie die Parameter passend zur Signatur der Methode erzeugt werden müssen, zeigen die nächsten Programmbeispiele. EchoCl i ent ruft beide Methoden des Echo-Dienstes mit dem Argument "Hallo" auf. EchoClient import ja va.net.URL: import org.apache xmlrpc client XmlRpcClient: import org.apache xmlrpc client XmlRpcCli entConfig Impl : public class Ec hoCl i ent ( public static voi d main(String args[]) throws Exception ( URL url ~ new URL(args[O]): Xml RpcCli ent Conf i gl mpl config ~ new Xm1RpcCl i ent Conf i 9Imp1 ( ) : confi g. setServerURl(ur1 ): XmlRpcClient cli ent ~ new XmlRpcCli ent(): cli ent.setConfig(config): Object[] params ~ {"Hallo"}: String s ~ (String) client.execute( "echo. get Echo". params) : System.out.println (s) : 6.1 Grundkonzept und ein erstes Beispiel 211 String t ~ (String) client execute( "echo. getEchoWi thDate". params); System.out.println(t); Die einzelnen Schritte beim entfernten Methodenaufruf sind: Ablauf beim 1. Das Client-Programm erzeugt eine Xm l RpcCll ent-Instanz, kon- entfernten figuriert sie und ruft dann die Methode execute mit Angabe Metbodenaufruf des Dienstnamens, des Methodennamens und der Parameter auf. 2. Diese Angaben werden in ein XML-Dokument verpackt und per HTTP-POST an den Server geschickt. 3. Der Server empfängt die HTIP-Anfrage und leitet die Verarbeitung des XML-Dokuments ein. 4. Das XML-Dokument wird geparst und anschließend die angegebene Methode des Dienstes aufgerufen. 5. Die Methode übergibt das Ergebnis an den XML-RPCVerarbeitungsprozess, der dann das Ergebnis in ein XMLDokument verpackt. 6. Der Server schickt das XML-Dokument als Antwort auf die HTIP-Anfrage zurück. 7. Der XML-RPC-Client parst das Xlvll-Dokument, extrahiert den Rückgabewert und übergibt diesen als Objekt an das ClientProgramm. Dienste _~f-------------1~ HTTP Client Transformation Server Transfonnation Bevor das Werkzeug Ant mit build.xml aus der Programmsammlung genutzt werden kann, muss die Umgebungsvariable XMLRPC_PATH gesetzt werden. Hier müssen alle erforderlichen JARDateien aufgeführt werden: set XMLRPC _PATH~Verzei chni si xxx. ja r: . 1. Compilieren: ant compll e 2. Server starten: ant server 3. Client starten: ant cl i ent Ausgabe: Ha 11 0 [20.02.2010 16;34;34J Hallo Bild 62.' Kommunikation zwischen Client und Server 6 XML Remote Procedure Calls 212 (XML~RPC) Alternativ können die obigen drei Schritte im Eingabeaufforderungsfenster wie folgt ausgeführt werden, mkdir bin javac -cp %XMLRPC_PATH% -d bin src/*.java start java -cp bin;%XMLRPC_PATH% EchoServer 50000 java -cp bin;%XMLRPC_PATH% EchoClient http;//10calhost;50000 6.2 XML-RPC-Datentypen Zu den einfachen Datentypen gehören: • Ganzzahl, • Gleitkommazahl, • Wahrheitswert, • Zeichenkette, • Datum/Zeit, • Binärdaten. Zusammengesetzte Datentypen sind: • Array und • Struktur. Ein Array-Element kann einen Wert vom einfachen oder zusammengesetzten Datentyp enthalten. Eine Struktur ist eine Folge von Elementen, die jeweils aus einem Namen und einem Wert bestehen. Der Name muss eine Zeichenkette sein, der Wert kann vom einfachen oder zusammengesetzten Datentyp sein. Die folgende Tabelle gibt eine Übersicht. Datentypen XML-Tag-Name i4 Java-Typ für execute java 1ang. Integer Java-Typ für Handler lnt doub 1e java 1ang. wub1e doub 1e boolean java 1ang. Boo 1ean boolean str i ng java 1ang String ja va 1ang String dateTi lTl2 i s08601 java uti 1. Date java uti 1. Date base64 byte[] byte[] array java 1ang Object[] java 1ang Object[] struct java uti 1.Map java util .Map 6.2 XML-RPC-Datentypen 213 Die erste Tabellenspalte enthält die Namen der Tags für das vom XML-RPC-Protokoll verwendete XML-Dokument. Diese Tags markieren den Datenwert zum entsprechenden Datentyp. Die zweite Spalte enthält die Entsprechungen in Java für den Aufruf der XmlRpcCl i ent-Methode execute. Die Datentypen der dritten Spalte werden als Parameter- bzw. Rückgabetypen der Handler benutzt. void-Metho den sind als Handler-Methoden nicht erlaubt. Der Wert null ist weder als Argumentwert noch als Rückgabewert erlaubt. Mit Programm 6.2 kann die Handhabung der verschiedenen Programm 62 Datentypen getestet werden. lmport arg apache xmlrpc server.PropertyHandlerMapping; import arg apache xmlrpc server.XmlRpcServer; import arg apache xmlrpc webserver.WebServer; DatentypTestServer public class Datenty pTestSer ver ( public static voi d main(String[] args) throws Except i on ( in t port ~ Int eger .par selnt( ar gs [O] ) ; PropertyHandl erMapping phm ~ new PropertyHandlerMapping(); phm. addHandl er ("test". DatentypTest . cl ass) ; WebServer webServer Xml RpcServer server = = new WebServer(port); webServer .getXmlRpcServer(); server .s et HandlerMapping (phm); webServer.s tart(); Die Handler-Klasse DatentypTest enthält für jeden XML-RPC- DatentypTest Datentyp eine Testmethode. import import import import import import java. i o ByteArrayOutputStream; java. io Fi l el nput St ream; java. io. IO Excepti cn: java. io. InputStream; java. util.Date; java. util.Map; public class DatentypTest / / Ganzzahl public int test lnt(int x) return x: 6 XML Remote Procedure Calls (XML~RPC) 214 11 Gleitkommazahl public double testDouble(double x) re turn x; 11 Wahr hei t swert public boolean testßoolean(boolean x) return x; 11 Zei chenket t e publ i c String testStr ing(String x) return x; 11 Datum/Zei t public Date t est Dat eTime(Dat e x) return x; 11 Bi närdaten public byte[] testBase64() throws IOExcept i on ( Input St ream in ~ new File lnputStream("java.gif"); ByteArrayOutputStream out ~ new ByteArrayOutputStream(); l nt c : while ((c ~ in.read()) !~ -1) ( out.write(c); } in.close(); return out. toByteArray(); 11 Array publ i c Object[] testArray(Object[] x) ( return x; 11 Struktur public Map<String, String> tes tStruct( Map<String, String> x) { return x; DatentypTestClient Die Klasse DatentypTestCl ient enthält für jeden Datentyp den Aufruf der zugehörigen Testmethode. XML~RPC~ 6.2 XML-RPC-Datentypen lmport java.io.FileOutputStream; import java.io.OutputStream; import java.net.URL; import java.utll.Date; import java.util.HashMap; import java.utll.Map; import arg apache.xmlrpc.XmlRpcException; import org apache.xmlrpc.client.XmlRpcClient; import org apache.xmlrpc.client.XmlRpcClientConfiglmpl; public class DatentypTestClient ( @SuppressWarnings("unchecked") public static void main(String args[J) throws Exception ( URL url ~ new URL(args[OJ); String typ ~ args[lJ; XmlRpcClientConfiglmpl config ~ new XmlRpcClientConfiglmpl(); config.setServerURL(url); XmlRpcClient client ~ new Xml RpcCl i ent ( ) ; client.setConfig(config); / / Ganzzahl if (typ.equals("int")) ( System.out.println(typ); Object[J params ~ { 1234 }; int r = (Integer) client.execute( "test.testlnt". params); System.out.println(r); System.out.println(); } // Gleitko mmazahl else if (typ.equals("double")) System.out.println(typ); Object[J params ~ { 123.456 }; double r ~ (Doubl e ) client.execute("test.testDouble". params); System.out.println(r); System.out.println(); } // Wahrheitswert else if (typ.equals("boolean")) System.out.println(typ); Object[J params ~ { true }; boolean r = (Boolean) cl ient.execute("test.testBoolean" , params); System.out.println(r); System.out.println(); 215 6 XML Remote Procedure Calls 216 (XML~RPC) // Zeichenkette else if (typ.equals("string")) System.out.println(typ); Object[J pararns ~ ("<- A & 0 -->" ); String r ~ (String) client.execute("test.testString". params) ; System.out.println(r); System.out.println(); ) //Oatum/Zeit else if (typ.equals("dateTirne")) System.out.println(typ); Object[] params ~ ( new Date() ); Date r ~ (Date) client.execute("test.testDateTi rne". params) ; System.out.println(r); System.out.println(); ) /I Bi närdaten else i f (typ. equa ls ("base64")) System.out.println(typ); Object[] params ~ (); byte[] r ~ (byte[]) client.execute("test.testBase64". params) ; OutputStream out ~ new Fi leOutputStream("test.gi f"); for (int i ~ 0; i < r.length; i++) ( out.write(r[i]); ) out. fl ush () ; out.close() ; System.out.println(); ) // Array else if (typ.equals("array")) System.out.println(typ); Object[] array ~ ( "Das ist ein String". 4711 ); Object[] params ~ ( array ); Object[] r ~ (Object[]) client.execute("test.testArray". params) ; for (Object obj r) ( System.out.println(obj); ) System.out.println(); ) / / Struktur else i f (typ. equa ls ("struct")) System.out.println(typ); Map<String. String> map ~ new HashMap<String. String>(); map.put("Vorname". "Hugo"); map.put("Nachnarne". "Meier"); Object[] pararns ~ ( map ); 6.2 XML-RPC-Datentypen Map<String. String> r ~ (Map<St r i ng . String» .execute ("test. testStruct", params); System.out.println(r.get("Vorname") + " " + r. get( "Nachname")); System.out.println(); 217 client ) 11 Ausnahme else if (typ.equals("fault")) try ( System.out.println(typ); Objecte] params ~ (); c II ent. execute ("test. testF aul t". params); catch (XmlRpcException e) ( System.out.println(e.getMessage()); Für jeden XML-RPC-Datentyp werden nun im Folgenden • der Parameterwert im XML-Dokument der XML-RPC-Anfrage C<value>-Tag). • der Rückgabewert im XML-Dokument der XML-RPC-Antwort C<value>-Tag) und • die Ausgabe des Client aufgeführt. XML-RPC-Anfrage; Ganzzahl <i4>1234</i4> XML-RPC-Antwort <i4>1234</i4> Ausgabe des Client-Programms: 1234 XML-RPC-Anfrage; <double>123.456</double> XML-RPC-Antwort <double>123.456</double> Ausgabe des Client-Programms: 123.456 Gleitkommazahl 6 XML Remote Procedure Calls 218 wabraeosioen (XML~RPC) XML~RPC~Anfrage, <boolean>l</boolean> XML~RPC~Antwort <boolean>l</boolean> Ausgabe des Client-Programms: true Zeichenkette XML~RPC~Anfrage, &lt;- A &amp; 0 --&gt; XML~RPC~Antwort, &lt;- A &amp; 0 --&gt; Ausgabe des Client-Programms: <- A & 0 --> Wenn wie hier kein Datentyp angegeben ist, wird <st r i ng> unterstellt. Die Zeichen <; > und & haben in XML eine Sonderrolle und werden daher umcodiert. Datum/Zeit XML~RPC~Anfrage, <dateTi me.iso8601>20100220TI7 11;36</dateTi me.iso8601> XML~RPC~Antwort, <dateTi me .iso8601>20100220TI7 11 36</dateTi me.iso8601> Ausgabe des Client-Programms: Sat Feb 20 17;11;36 CET 2010 Binärdaten Die XML~RPC~Anfrage enthält kein <va1ue--Tag. XML~RPC~Antwort, <base64>ROl GOOl hNABYAPcAAP ... </base64> nutzt das Codierungsverfahren Base64, um Binärdaten technisch gesichert in Xlvll-Srrukturen zu übertragen. 24 Bit lange Gruppen der Binärdaten werden in vier Bitfolgen von jeweils 6 Bit zerlegt. Diese 6 Bit werden mit Hilfe der US~ASCll~Zeichen A ~ Z, a ~ z, 0 ~ 9, -t-, / und ~ codiert (http;//www.ietf.org/rfc/ rfc2045. txt). XML~RPC Ausgabe des Client-Programms: Der Client speichert die übertragenen Daten in der Datei test.gif 6.2 XML-RPC-Datentypen XML-RPC-Anfrage, 219 Array <array> <data> <val ue>Das lst eln Str ing</ value> <val ue><i 4>4711</i 4></value> </data> </array> XML-RPC-Antwort <array> <data> <value>Das ist ein String</ value> <value><int>4711</int></ value> </data> </array> Ausgabe des Client-Programms: Das ist ein String 4711 XML-RPC-Anfrage, Struktur <struct> <member> <name>Nachname</name> <value>Meier</ value> </ member> <member> <name>Vorname</name> <val ue>Hugo</val ue> </ memb er> </struct> XML-RPC-Antwort <struct> <member> <name>Nachname</name> <value>Meier</value> </ member> <member> <name>Vorname</name> <val ue>Hugo</ val ue> </member> </struct> Ausgabe des Client-Programms: Hugo Mei er Es wird eine Ausnahme vom Typ Xm1RpcExcept i on ausgelöst, da Ausnahme die Methode testFault nicht existiert. 6 XML Remote Procedure Calls (XML~RPC) 220 Die komplette XML~RPC~Antwort <?xml verslon="1.0" encodlng="U TF 8"?> <rrethodRespose> <fault><value><struct> <rrember> <name>faultCode</name> <value><l4>O</l4></value> </member> <rrember> <name>faultStrlng</name> <value>No such handler: test testFault</value> </member> </struct></value></fault> </methodResponse> Ausgabe, No such handler: test.testFault Das Programm MyReporter (Kapitel 5.6) kann genutzt werden, um die zwischen Client und Server ausgetauschten Daten aufzuzeichnen. MyReporter starten (Kommando in einer Zeile): start java -cp .. /Prog0506/bin MyReporter 40000 localhost 50000 logRequest.txt logResponse.txt logRequest.txt enthält die H'I'Tl'<Anlragen und logResponse.txt die HTIP~Antworten. Server starten: start java -cp bin %XMLRPC PATH% OatentypTestServer 50000 Client starten (Kommando in einer Zeile): java -cp bin:%XMLRPC_PATH% OatentypTestClient http://localhost:40000 int 6.3 6.3 Komplexe Datenstrukturen 221 Komplexe Datenstrukturen Die Beispiele dieses Kapitels zeigen, wie komplexe Datenstrukturen mit Hilfe einfacher und zusammengesetzter XML-RPCTypen in ein für XML-RPC geeignetes Format transformiert werden können. Die Handler-Klasse War enkorb verwaltet eine Reihe von Bestell- Programm 63 positionen mit den Attributen id, name, preis und menge in einem Vector-Objekt und bietet die folgenden Methoden an, int addPosltlon(lnt rd . String name, double pr eis, i nt menge) fügt eine Position ein. Obj ect[] getPositionen() liefert alle Positionen des Warenkorbs. Jedes Arrayelement des Rückgabewertes ist selbst wieder ein Arra y, das als Elemente die Attribute einer Position enthält. Bild 6.3 zeigt die zugehörige XML-Struktur. <a r r ay> <data> <va lue> Bild 63· Warenkorb als Array von Arrays <a r r-ay> <da ta> <val ue><i4>lOOO</ i4></val ue> <value>Hammer</va lue> <value><doub le>2.S</ double></va lue> <value><i4>lO</ i4></va lue> </data> <y ar r ay> </va l ue> <va lue> <a r r -ay > <data> <value><i4>lOlO</ i4></value> <value>Zange</va lue> <value><doub le>3.99</ doub le></va lue> <value><i4>8</ i4></va lu e> </da ta> </ar: r:ay> </va lue> </ data> </ar: r:ay> publlc class Pos l t l on prlvate int td: prlvate Strlng name; prl vate double prels; prl vate lnt me nge ; Position 6 XML Remote Procedure Calls 222 (XML~RPC) publlc Posltlon(lnt ld, Strlng name, double prels, i nt rre nge ) ( th is.id ~ id; th is name = name; this preis = preis; this me nge = me nge ; public Object[] getPosition() { Object[] array ~ new Object[ 4] ; array[O] ~ id; array[!] ~ narre; array[2] ~ preis; array[3] ~ rre nge ; r eturn array; Warenkorb import ja va.util .Vector; public class Warenkor b ( private static Vector<Posit ion> korb Vector<Position>(); = new publ i c boolean addPos ition(int id. String name. double pre is. int rre nge ) ( korb.add(n ew Position(id. name. preis. rre nge) ) ; r eturn true; public Object[] get Posi t i onen( ) ( Vector<Object> v = new Vector<Object> (); f or (Position pos korb) ( v.add (pos.getPosition()); } return v. toArray(); Server import arg. apache xml rpc server. PropertyHandl erMapping; import arg. apache xmlrpc server.XmlRpcServer; import arg. apache xmlrpc webserver.WebServer; public class Server ( public static voi d main(String[] args) throws Except i on ( int port ~ Int eger .parsel nt (args [ O] ) ; PropertyHandlerMapping phm ~ new Property HandlerMapping(); phm. add Handl er( "waren korb". Warenkorb.cl ass); 6.3 Komplexe Datenstrukturen 223 W ebSer ver webServer = new W ebSer ver (port ) ; XmlRpcServer server = webServer.getXmlRpcServer(); server.set HandlerMapping (phm); webServer.start(); import java.net.URL; Client import org apache xmlrpc client.XmlRpcCli ent; import org apache xmlrpc client.XmlRpcCli entConfig Impl; public class Client ( public static void main(String args[]) throws Except i on ( URL url ~ new URL(args[O]); Xml RpcCl ientConfig Impl config ~ new XmlRpcClientConfig Impl(); config.setServerURL(url); XmlRpcClient client ~ new XmlRpcClient(); client.setConfig(config); Object[] params l ~ ( 1000. "Hammer". 2.5. 10 l: cl i ent .execute ("wa renkorb . addPos i ti on". pararns 1) ; Object[] params2 ~ ( 1010. "Zange". 3.99. 8 I: cl i ent .execute ("wa renkorb . addPos i ti on". params2) ; Object[] params3 ~ (); Object[] result ~ (Object[] ) client.execute( "waren korb .getPosi ti onen". params3); for (Object obj resul t ) ( Object[] array ~ (Object[]) obj; + (Integer) array[O]); System.out.println(" Id; System.out.println ("Narre; " + (String) array[l]); System.out.println ("Preis; + (DJuble) array[2]); System.out.println(" l'enge; " + ( Integer) array[3]); System.out.println(); Das Beispiel zeigt auch, dass die Kenntnis der Signatur (mit Achtung Rückgabetyp) eines Handlers in der Regel alleine nicht ausreicht, um das Ergebnis des Methodenaufrufs weiterzuverarbeiten. Zusätzlich sind die Datenstruktur (Array von Arrays) und ihre Semantik zu erläutern. 6 XML Remote Procedure Calls 224 Programm 6.4 (XML~RPC) Der folgende Dienst ermöglicht die Suche nach Personen, die auf der Serverseite in einer Datenbank gespeichert sind. Das Suchergebnis ist eine Liste von Personen mit den zugehörigen Adressen. Personen und Adressen sind in einer MySQL-Datenbank in zwei miteinander verknüpften Tabellen gespeichert. create table person ( persld lnteger not null auto_lncrement, name varchar(30), vorname varchar(30), gebdatum date. primary key (persid) create table adresse ( adressld lnteger no t null auto_lncrement, persl d l nteger not null, plz char(5). ort varchar(30). primary key (adressid). forelgn key (persld) references person (persid) Die Klasse PersonQuery enthält die Methode ArrayList<Person> getPersonen(String name), die mit Hilfe des Suchbegriffs narre, der auch so genannte Wild~ card-Zeichen wie % und enthalten darf, in der Datenbank nach Personen, deren Namen dem Kriterium entsprechen, sucht und eine Liste von Person-Objekten zurückliefert. Ein Person-Objekt enthält eine Liste von möglicherweise mehreren AdresseObjekten. Bild 6.4.· Die Person-Adresse- Beziehung Person name vorname gebdatum adressen 1 0..* Adresse piz ort 6.3 Komplexe Datenstrukturen lmport java text.SlmpleDateFormat; lmport java.utll.ArrayLlst; lmport java.utll.Date; public class Person ( prlvate Strlng name; prlvate Strlng vorname; private Date gebdatum; prlvate ArrayLlst<Adresse> adressen; public void setName(String name) ( thls.name = name; public String getName() ( return name; public void setVorname(String vorname) ( thls.vorname = vorname; public String getVorname() return vorname; public void setGebdatum(Date gebdatum) ( this.gebdatum ~ gebdatum; public Date getGebdatum() return gebdatum; publlC vOld setAdressen(ArrayLlst<Adresse> adressen) { thls.adressen = adressen; public ArrayList<Adresse> getAdressen() return adressen; public void print() { System. out. pri nt 1n(" Name; + name); System.out. pr i nt ln ("Vorname: " + vorname); SimpleDateFormat f ~ new SimpleDateFormat("dd l+1 .yyyy" ) ; if (gebdatum !~ null) System. out. pri nt 1n( "Gebdatum; " + f. format (gebdatum)); else System.out println("Gebdatum; null "); 225 Person 6 XML Remote Procedure Calls 226 if (adressen !~ null) { für (Adresse adr adressen) adr.print(); Adresse publlC class Adresse private String plz; prlvate Strlng ort; public void setPlz(String plz) ( this plz ~ plz; public String getPlz() ( return pl z: public voi d setOrt(String ort) ( this ort = ort; public String getOrt() ( return ort; public void print() ( System.out.println("PLZ; System. out. pri nt 1n( "Ort; PersonQuery import import import import java java java java + pl z); + ort); io.File InputStream; io.IOException; sql .Connection; sq l . Dri verManager ; import java sql.PreparedStaterrent; import java sql.ResultSet; import java sql .SOLException; import java uti l.ArrayL ist; impor t java util.Properties; public class PersonQuery pri vate Stri ng ur l ; private String user; private String password; (XML~RPC) 6.3 Komplexe Datenstrukturen public PersonQuery() { try ( FilelnputStream input ~ new FilelnputStream( "dbconnect. propertles"); Propertles prop = new Propertles(); prop.load (i nput) ; input.close(); url ~ prop.getProperty("url"); user ~ prop. getProperty( "user"); password ~ prop. getProperty( "password"); catch (IOException e) ( System.err.println(e); publlC ArrayLlst<Person> getPersonen(Strlng name) { ArrayLlst<Person> personen = new ArrayLlst<Person>(); Connectlon con = null; try ( con = DrlverManager.getConnectlon(url, user, password); Strlng sql = "select persld, name, vorname, gebdatum " + + "fram person where name II ke ? " "order by name, vorname"; PreparedStatement pstmt ~ con.prepareStatement(sql); pstmt.setString(I. name); ResultSet rs ~ pstmt.executeQuery(); while (rs.next()) ( Person p = new Person(); int persid ~ rs.getlnt(l); p.setName(rs.getString(2)); p.setVorna me(rs.getString(3)); p.setGebdatum(rs.getDate(4)); p.setAdressen(getAdressen(persid. con)); personen.add(p); } rs .close(); pstmt.close(); catch (SQLException e) System.err.println(e); finally { try { if (con !~ null) con.close(); catch (SQLException e) { } return personen; 227 6 XML Remote Procedure Calls (XML~RPC) 228 prlvate ArrayLlst<Adresse> getAdressen(lnt persld, Connection con) { ArrayList<Adresse> adressen = new ArrayList<Adresse>(); try ( String sql = "select pl z , ort fram adresse" + "where persid = ? order by pl z": PreparedStatement pstmt ~ con prepareStatement(sql); pstmt.setlnt(l. persid); ResultSet rs ~ pstmt.executeQuery(); whi 1e (rs. next ()) ( Adresse a = new Adresse(); a.setPlz(rs.getString(l)); a.setürt(rs.getString(2)); adressen.add(a); } rs.close(); pstmt. cl ose () ; catch (Exception e) ( System.err.println(e); } return adressen; 11 Testprogramm public static void main(String[] args) throws Exception PersonQuery query = new PersonQuery(); ArrayList<Person> personen for (Person pers = query.getPersonen("%"); personen) { pers.print(); System.out.println(); Die DB-Verbindungsparameter befinden sich in der Datei dbconnect.properties. Die Methode getPersonen kann unabhängig von XML~RPC getestet werden (main). Um das Suchergebnis mittels XML~RPC zum Client zu übertragen, muss das Modell aus Bild 6.4 in eine für XML~RPC geeignete Struktur transformiert werden. Diese darf nur die Datentypen aus Kapitel 6.2 enthalten. Diesem Zweck dienen die beiden Hilfsklassen PersonHelper und AdresseHel per. Sie enthalten jeweils zwei statische Methoden: eine, die ein Adresse- bzw. Person-Objekt in eine Map transformiert, und eine, die aus einer Map wiederum ein Adresse- bzw. Person-Objekt erzeugt. Erstere wird auf der Serverseite, letztere auf der Clientseite benötigt. 6.3 Komplexe Datenstrukturen 229 import java.util.HashMap; lmport java.utll.Map; AdresseHelper public class AdresseHelper ( private final static String PLZ private final static String ORT ~ "plz": ~ "ort.": public static Map<String. Object> toMap(Adresse adr) ( Map<String. Object> map ~ new HashMap<String. Object>(); String plz ~ adr.getPlz(); if (plz !~ null) map.put(PLZ. plz); String ort ~ adr.getOrt(); if (ort !~ null) map.put(ORT. ort); re turn map; public static Adresse fromMap(Map<String. Object> map) ( Adresse adr = new Adresse(); Object obj ~ map.get(PLZ); if (obj !~ null) adr.setPlz((String) obj); obj ~ map.get(ORT); if (obj !~ null) adr.setOrt((String) obj); return adr; lmport lmport lmport lmport java. uti 1.ArrayList; java. uti 1.Date; java. uti 1.HashMap; java. uti 1.Map; public class PersonHelper ( private final static String pri vate final static String pri vate final static String private final static String PersonHelper NAME ~ "narre"; VORNAME ~ "vornarre"; GEBDATUM ~ "gebdatum"; ADRESSEN ~ "adressen"; public static Map<String. Object> toMap(Person pers) ( Map<String. Object> map ~ new HashMap<String. Object>(); 6 XML Remote Procedure Calls 230 (XML~RPC) String name ~ pers getName (); if (name !~ null) map.put(NAME. name); Strlng vorname = pers.getVorname(); i f (vorname ! ~ null) map.put(VORNAME. vorname); Date gebdatum ~ pers.getGebdatum(); if (gebdatum !~ null ) map.put(GEBDATUM. gebdatum); ArrayLlst<Adresse> adressen = pers getAdressen(); if (adressen !~ null) ( Object[] array ~ new Object[adressen.size()]; i nt i ~ D; for (Adresse adr adressen) ( array[i++] ~ AdresseHelper.toMap(adr); } map .put (ADRESSEN. array); return map; @Suppress Warnings("unchecked" ) public static Person fromMap(Map<String. Object> map) ( Person pers = new Person(); Object obj ~ map.get(NAME); if (obj !~ null) pers.setName((String) obj); obj ~ map.get(VORNAME); if (obj !~ null) pers.setVorname((String) obj); obj ~ map.get(GEBDATUM); i f (obj !~ null) pers.setGebdatum((Date) obj) ; obj ~ map.get(ADRESSEN); i f (obj !~ null) ( ArrayLlst<Adresse> adressen = new ArrayLlst<Adresse>(); (Object[]) obj) { for (Object 0 adressen. add(AdresseHelper. fromMap( (Map<String. Object» 0)); pers setAdressen(adressen); 6.3 Komplexe Datenstrukturen 231 return pers; Der Handler getPersonen der Klasse PersonQueryHandler nutzt die PersonQuery-Methode getPersonen und transformiert die PersonObjekte. Rückgabewert ist ein Array von Maps. Eine jede Map entspricht einer gefundenen Person. Bild 6.5 zeigt das Ergebnis einer Transformation. lmport java.utll.ArrayLlst; public class PersonQueryHandler ( prlvate statlc PersonQuery query PersonQueryHandler = new PersonQuery(); public Object[] getPersonen(String name) ( ArrayLlst<Person> personen = query.getPersonen(name); Object[] array ~ new Object[personen.size()]; if (personen !~ null) { int l = 0; for (Person pers personen) { array[i++] ~ PersonHelper.toMap(pers); } return array; Bild 6.5' Eine Person Abbildung der Person-AdresseBeziehung auf Map und Array Map name ... vorname ... Object [l gebdatum ... adressen I Map piz ... ort ... ... I 6 XML Remote Procedure Calls 232 Server (XML~RPC) lmport arg. apache xmlrpc server. PropertyHandl erMapPlng; lmport arg. apache xmlrpc server.XmlRpcServer; lmport arg. apache xmlrpc webserver.WebServer; public class Server ( public static voi d main(String[J args) throws Except i on ( int port ~ Int eger .parsel nt (args[ OJ ) ; PropertyHandlerMapping phm ~ new Property HandlerMapping(); phm. add Handl er( "personQuery". PersonQueryHandl er. cl ass) ; W ebServer webServer = new WebServer(port); Xml RpcServer ser ver = webServer.getXmlRpcServer(); server.setHandlerMapping(phm); webServer.start(); Client l mpor t ja va net. URL; l mpor t ja va util.Map; l mport arg. apache xmlrpc cllent Xm1RpcCl i ent : l mport arg. apache xmlrpc cllent Xm1RpcCl i entConfigImp 1; public class Cli ent ( @Suppress Warnings("unchecked" ) public static voi d main(String args[J) throws Except i on ( URL url ~ new URL(args[OJ); String name ~ args[ IJ; Xml RpcCli entCon fig Impl config ~ new Xm1RpcCl i entConfi 9Imp1( ) ; confi g. setServerURl(ur1); Xml RpcCl i ent client ~ new XmlRpcClient(); client.setConfig(config); Object[J params ~ Object resu lt c II ent. execute( "personQuery. getPersonen" , = ( name ); params) ; if (result !~ null) ( for (Object obj (Object[J) resul t ) ( Person pers = PersonHelper.fromMap( (Map<String. Object» obj); pers.print(); System.out.println(); 6.4 Dynamische Proxies 233 Hier wird jede Map des von execute gelieferten Arrays in ein Person-Objekt transformiert. 6.4 Dynamische Proxies Mit Hilfe so genannter dynamischer Proxies kann die Programmierung des Clients vereinfacht werden. Ein dynamischer Proxy ist eine Klasse, die erst zur Laufzeit dynamisch generiert wird. Ziel ist es , die entfernte, vom Server implementierte Methode wie eine "normale" lokale Methode auf der Clientseite aufzurufen. Dazu muss zunächst die Handler-Klasse ein Interface implementieren. Dasselbe Interface wird auch vom Client verwendet. Wir erläutern die Vorgehensweise anhand des Echo-Dienstes aus Programm 6.5 Programm 6.1. public inter face Echo ( String get Echo(String s); String get EchoWithDate(String s); Interface Echo lmport java text Slmpl eDateFormat; Implementierung Echolmpl lmport java.utll.Date; public class Echolmpl implements Echo public String get Echo(String s) ( return 5; public Str ing get EchoWithDate(String s) ( Simpl eDateFormat f ~ new Simpl eDateFormat( "dd.l+1.yyyy HH ;mm;ss ") ; return "[" + f.format(new Date()) + "J " + s; Der beim Aufruf der Methode add Hand ler benutzte Key für die Handler-Klasse muss mit dem voll qualifizierten Namen des Interfaces (Paket .Interface) übereinstimmen. 6 XML Remote Procedure Calls (XML~RPC) 234 EchoServer lmport arg. apache xmlrpc server. PropertyHandl erMapPlng; lmport arg. apache xmlrpc server.XmlRpcServer; lmport arg. apache xmlrpc webserver.WebServer; public class EchoServer ( public static void main(String[] args) throws Exception ( int port ~ Integer.parselnt(args[O]); PropertyHandlerMapping phm ~ new PropertyHandlerMapping(); phm.addHandler("Echo". Echolmpl.class); WebServer webServer = new WebServer(port); XmlRpcServer server = webServer.getXmlRpcServer(); server.setHandlerMapping(phm); webServer.start(); Auf der Clientseite wird nun nicht das Xm 1RpcCl i ent-Obiekt cl i ent direkt zum Aufruf der entfernten Methode benutzt, sondem zunächst eine Instanz der Klasse arg. apache. xm1rpc cII ent. ut i l .Cll entF actory wie folgt erzeugt ClientFactory factory ~ new ClientFactory(client); Die Cl i ent.Factory-Methode Object newlnstance(Class c) wird mit dem Interface Echo. cl ass als Argument aufgerufen. Zurückgeliefert wird eine Implementierung dieses Interfaces, die intern cII ent nutzt, um die entfernte Methode aufzurufen: Echo echo ~ (Echo) factory.newlnstance(Echo.class); Nun können die Stri ng s EchoClient ~ Echo~Methoden aufgerufen werden, z: B. echo.getEcho ("Ha 11 0"); import java.net.URL; import org.apache xmlrpc client XmlRpcClient; import org.apache xmlrpc client XmlRpcClientConfiglmpl; lmport arg. apache xmlrpc cllent utll .CllentFactory; public class EchoClient ( public static void main(String args[]) throws Exception ( URL url ~ new URL(args[O]); XmlRpcClientConfiglmpl config ~ new Xm1RpcCl i entConfi 9Imp1( ) ; 6.5 Filterung von IP-Adressen config.setServerURL(url); XmlRpcClient client ~ new XmlRpcClient(); client.setConfig(config); ClientFactory factory ~ new ClientFactory(client); Echo echo ~ (Echo) factory.newlnstance(Echo.class); String s ~ echo.getEcho("Hallo"); System.out.println(s); String t ~ echo.getEchoWithOate("Hallo"); System.out.println(t); 6.5 Filterung von IP-Adressen Mit den folgenden WebServer-Methoden kann der Webserver nur bestimmten Clients den Methodenaufruf gestatten bzw, verbieten. void setParanoid(boolean p) schaltet die Filterung von IP-Adressen ein (true) bzw, aus (false). Der Webserver akzeptiert standardmäßig Anfragen aller IP-Adressen. Mit true wird zunächst keine Client-Verbindung akzeptiert. void acceptClient(String address) fügt die IP-Adresse address in die Liste der akzeptierten IPAdressen ein. address kann * enthalten, um einen Nummernkreis anzugeben (z. B. 194.94 .124.*). acceptCl ient wirkt nur, wenn setParanoid(true) aufgerufen wurde. void denyClient(String address) fügt die IP-Adresse address in die Liste der ausgeschlossenen IPAdressen ein. address kann * enthalten, um einen Nummernkreis anzugeben (z. B. 194.94.124.*). denyClient wirkt nur, wenn setParanoid(true) aufgerufen wurde. Das folgende Beispiel zeigt den Einsatz der IP-Filterung. Die Programm 6,6 Liste der akzeptierten (accept) bzw. ausgeschlossenen (deny) IPAdressen wird der Property-Datei ip.txt entnommen. Beispiel, accept; 127.0.0.110.108.105.* 10.108.1.51 235 236 EchoServer 6 XML Remote Procedure Calls (XML~RPC) lmport java lo.FlleInputStream; lmport java util.Properties; import java util.StringTokenizer; import arg. apache xmlrpc server. PropertyHandl erMapping; import arg. apache xmlrpc server.XmlRpcServer; import arg. apache xmlrpc webserver.WebServer; public class EchoServer ( public static void main(String[] args) throws Exception ( int port ~ Integer.parselnt(args[O]); PropertyHandlerMapping phm ~ new PropertyHandlerMapping(); phm.addHandler("echo". Echo.class); WebServer webServer = new WebServer(port); webServer setParanoid(true); FilelnputStream in ~ new FilelnputStream("ip txt"); Properties ip = new Properties(); ip.load(in) ; in.close(); Stri ng addr ~ i p. getProperty(" accept"); if (addr !~ null) ( StringTokenizer st = new StringTokenizer(addr, " "); while (st.hasMoreTokens()) webServer.acceptClient(st.nextToken()); add r ~ i P.getProperty ("deny" ) ; if (addr !~ null) ( StringTokenizer st = new StringTokenizer(addr, " "); while (st.hasMoreTokens()) webServer.denyClient(st.nextToken()); XmlRpcServer server = webServer getXmlRpcServer(); server.setHandlerMapping(phm); webServer.start(); Das Client-Programm löst eine Ausnahme aus, wenn die IPAdresse des Client-Rechners nicht zugelassen ist. 6.6 Einbettung von XML-RPC in Apache Tomcat 6.6 237 Einbettung von XML-RPC in Apache Tomcat Bisher "WUrde in allen Beispielen die Klasse WebServer benutzt. Sie implementiert einen einfachen HTTP-Server, der zudem XMLRPC-Anfragen verarbeiten kann. Bisweilen ist es aus Performance- und Sicherheitsgründen erforderlich. die XML-RPC-Verarbeitung in einen anderen, marktgängigen Webserver zu integrieren. Wir zeigen diese Integration am Beispiel von Apache Tomcat, Programm 6.7 der einen Servlet-Container als Ablaufumgebung für Webanwendungen bietet. Im Folgenden setzen wir den Umgang mit Tomcat und Grundlagen zur Servlet-Technologie voraus. Die Klasse arg. apache xmlrpc.webserver.XmlRpcServletServer ist Subklasse von Xm1RpcServer und besitzt die folgende Methode, void execute(javax.servlet .http.HttpServletRequest. javax.servlet.http.HttpServletResponsel throws javax.servlet.ServletExceptlon, java.lo. IOExceptlon Sie verarbeitet die XML-RPC-Anfrage und liefert die XML-RPCAntwort ZUlÜCk. Diese Methode kann nun sehr einfach in der doPost-Methode eines Servlets genutzt werden. Die folgende abstrakte Klasse xmlrpc.AbstractXmlRpcSer vlet dient als universelle Basisklasse für eigene Servlets. package xmlrpc; lmport java.lo.IOExceptlon; lmport lmport import import import javax.servlet javax.servlet javax.servlet javax.servlet javax.servlet ServletConflg; ServletExceptlon; http.HttpServlet; http.HttpServletRequest; http.HttpServletResponse; import org apache xmlrpc Xml RpcExcepti on; lmport org apache xmlrpc server.PropertyHandlerMapplng; lmport org apache xmlrpc webserver.XmlRpcServletServer; public abstract class AbstractXmlRpcServlet extends HttpServlet AbstractXmlRpcServlet 6 XML Remote Procedure Calls (XML~RPC) 238 prlvate XmlRpcServletServer server; public final void init(ServletConfig config) throws ServletException { 5 uper . i m t (canf i g) ; try ( PropertyHandlerMapping phm ~ new PropertyHandlerMapping(); addHand 1ers (phm) ; server = new XmlRpcServletServer(); server.setHandlerMapping(phm); catch (XmlRpcException e) ( throw new ServletException(e); public abstract void addHandlers(PropertyHandlerMapping phm) throws XmlRpcException; public final void doPost(HttpServletRequest request. HttpServletResponse response) throws ServletException, IOExcepti on ( server.execute(request, response); Zur Demonstration ist die Klasse echo. EchoServ let von dieser ab- strakten Klasse abgeleitet. Sie implementiert die Methode addHandlers. Somit haben Subklassen nur noch die Aufgabe, die geforderten Handler zu registrieren. EchoServlet package echo; import arg. apache xmlrpc.XmlRpcException; import arg. apache xmlrpc server. PropertyHandl erMapping; import xmlrpc.AbstractXmlRpcServlet; public class EchoServlet extends AbstractXmlRpcServlet public voi d addHandlers(PropertyHandlerMapping phm) throws XmlRpcException ( phm.addHandler("echo". Echo.class); 6.6 Einbettung von XML-RPC in Apache Tomcat 239 Die hier verwendete Klasse echo. Echo entspricht der in Programm 6.1 verwendeten Klasse. Der einzige Unterschied ist , dass eine package-Klausel hinzugekommen ist. Die übersetzten Klassen Abstra ctXm1RpcSe r v1et, EchoServ l et und Echo gehören gemäß ihrer Paketstruktur in das Verzeichnis classes unter WEB-INF. Die für XML-RPC erforderlichen JARDateien müssen in das Verzeichnis lib unter WEB-INF kopiert werden. Die Deskriptordatei web.xml hat den folgenden Inhalt <?xml ve rs l on="l . O" encodlng=" ISO-8859-1"?> <web-app xmlns=''http://java.sun.com/xml/ns/javaee'' xm lns:xsi~''http://www.w3.org/2001/XHLSchema-instance'' XSl :schemaLocatl on-" http ://java.s un.com/xml/ns/ j avaee http://java.sun . coml xm 1Insl j avaee/web- app_2_5. xsd" verslon="2.5"> <serv 1et > <ser vlet name>EchoServlet</s ervlet-name> <servlet class>echo.EchoSer vlet</servlet-class> </ serv 1et > <ser vlet-mapplng> <ser vlet ~name >EchoServlet</servlet name> <url-pattern>/echo</url-pattern> </servlet-mapplng> </web-app> Zum Schluss muss diese Webanwendung Tomcat bekannt gemacht werden. Wir zeigen eine von mehreren Möglichkeiten. Die Datei xmlrpc.xml mit dem Inhalt <Context dccßas e-" C: I ... IProg0607lserver" re 1oadab1e- "true" I> wird in das Verzeichnis <CATAL INA HOHE>l conf l Cat al i nal l ocal hos t kopiert. <CATALlNA_HOHE> bezeichnet das Installationsverzeichnis von Tomcat. Nun kann Tomcat gestartet werden. Der Client EchoCl i ent entspricht der gleichnamigen Klasse in Programm 6.1. URL ist hier, http://l oca1host :80801 xm1rpcl echo web.xml 6 XML Remote Procedure Calls (XML~RPC) 240 Baste Authentication Apache XML~RPC unterstützt auch die Authentifizierung mit Benutzemame und Passwort. XML~RPC nutzt das HTIP~Verfahren Basic Authentication (http://www .i etf. org/ rfc/ rfc2617. txt), Die Klasse Xm1RpcCl i entConfi 9Imp1 bietet hierzu die Methoden voi d setBasicUserName(Strlng user) und vOld setBas;cPassword(Strlng password). user und password werden nach dem Base64-Verfabren codiert (siehe Kapitel 6.2) und in einem Basic Authentication Header per HTIP an den Server geschickt. Zu beachten ist, dass es sich hierbei um keine echte Verschlüsselung von Benutzemame und Passwort mit dem Ziel der Geheimhaltung handelt. Beispiel: Ist der User "hugo" und das Passwort "oguh", so lautet der HTIP~Header, Authorization: Basic aHVnbzpvZ3Vo "aHVnbzpvZ3Vo" wird nach dem Baseö-l-Verfahren Serverseite zu "hugo: oguh" decodiert. Programm 6.8 import java.net.URL: EchoClient import org.apache xmlrpc client XmlRpcClient: import org.apache xmlrpc client XmlRpcClientConfiglmpl: auf der public class EchoClient { public static void main(String args[Jl throws Exception ( URL url ~ new URL(args[OJl: String user ~ args[IJ: String password ~ args[2J: XmlRpcClientConfiglmpl config new XmlRpcClientConfiglmpl(l: config.setServerURL(urll: conflQ.setBaslcUserName(user); conflQ.setBaslcPassword(password); XmlRpcClient client ~ new XmlRpcClient(l: client.setConfig(configl: Object[J params ~ { "Hallo" }: String s ~ (Stringl client.execute( "echo. getEcho". params 1: System.out.println(sl: String t ~ (Stringl client execute("echo.getEchoWithOate". params) ; System.out.println( tl: 6.6 Einbettung von XML-RPC in Apache Tomcat 241 Tomcat muss für die Authentifizierung konfiguriert werden. Die Deskriptordatei web.xml ist wie folgt zu ergänzen: <?xml vers l on="l . O" encodlng=" ISO-8859-1"?> <web-app xmlns=''http://java.sun.com/xml/ns/javaee'' xmlns:xsi~''http://www.w3 .org/200 1/ XMLSchema-ins tance'' XSl :schemaLocatl on-" ht tp ://java .sun .com/xml/ns/ j avaee http://java.sun.com/xml/ns/javaee/web-app 2 5 xsd" verslon="2.5"> <serv 1et> <ser vlet name>EchoServlet</servlet-name> <ser vlet class>echo.EchoServlet</ser vlet cl ass> </ serv 1et> <ser vlet-mapping> <servlet -name>EchoServlet</servlet name> <url-pattern>/echo</url-pattern> </servlet-mapping> <security-constraint> <web-resource-collection> <web -resource-name>echo</web-resource name> <url -pattern>/echo</url-pattern> </web-resource-collectlon> <auth-constralnt> <role-name >user</role name> </auth-constralnt> </securlty-constralnt> <logln-conflg> <auth-method>BAS IC</auth method> <realm-name>Test</realm name> </l Ogl n-confl g> <securl ty- rol e> <role-name>user</role name> </securlty-role> </web- app> Die zugriffsberechtigten User werden der Einfachheit halber in der XML-Datei <CATAL INA HOME>l conf l t omcat users.xml erfasst: <tomcat users> <rol e rol ename="user"/> <user username=" hugo" password=" oguh" rol es="user" /> </torrcat-users> web.xml 6 XML Remote Procedure Calls 242 (XML~RPC) Zusätzlich kann eine Verschlüsselung mit Server-Authentifizierung genutzt werden. Secure Socket Layer (SSL) ist ein weit verbreitetes Verfahren. das auf Public-Key-Verschlüsselung basiert. Programm 6.9 Zur Nutzung von SSL müssen Server und Client vorbereitet werden. Der Quellcode der Programme ist der gleiche wie in Programm 6.8. Konfiguration des Servers Zunächst wird ein Schlüsselpaar (private key, public key) mit einem "self-signed" Zertifikat erzeugt. Dies reicht für Testzwecke aus. Anschließend wird das Zertifikat (mit dem öffentlichen Schlüssel des Servers) exportiert. createKeystore Wir benutzen hierzu das JDK~Tool keytool. keytool -genkey -dname "CN~localhost. OU~FB 08. ü-Hcchschul e Niederrhein. C~DE" -alias tomcat -keyalg RSA -validity 60 -keystore keystore -keypass tomcat -storepass tomcat keytool -export -rfc -allas tomcat -flle tomcat.cer -keystore keystore -storepass tomcat (Die beiden Kommandos sind im Eingabeaufforderungsfenster jeweils in einer Zeile einzugeben.) server.xml In <CATALINA HOME>lconflserver .xml wird nun ein Konnektor für den Port 8443 eingerichtet <Connector port~"8443" protocol~"HTTP/1.1" SSLEnabled~"true" maxThreads=" 150" scherre=" https" secure="t rue" cl i ent/urth-" fa 1se" ss 1Protoco 1~"TLS" keystoreFi 1e-" ... IProg0609lkeystore" keystorePass="tomcat" /> Konfiguration des Client Auf der Clientseite truststore importiert. wird das Zertifikat in den Speicher createTruststore keytool -lmport -noprompt -allas tomcat -flle tomcat.cer -keystore truststore -storepass catallna (Das Kommando ist im Eingabeaufforderungsfenster in einer Zeile einzugeben.) 6.7 Nutzung einer Erweiterung in Apache XML-RPC 243 Angaben zum Speicherort des Zertifikats werden beim Aufruf des Aufruf des Client Client-Programms als Systemeigenschaften mitgegeben, java -Djavax.net.ssl.trustStore=truststore -Djavax.net.ssl.trustStorePassword=catallna -cp bin;%XMLRPC_PATH% EchoClient https;//localhost;8443/xmlrpc/echo hugo oguh (Das Kommando ist im Eingabeaufforderungsfenster in einer Zeile einzugeben.) Zu beachten ist, dass statt http hier https (HTIP over SSL) anzugeben ist. 6.7 Nutzung einer Erweiterung in Apache XML-RPC In diesem Kapitel gehen wir auf die in Kapitel 6.1 angesprochene Erweiterung in Apache XML-RPC 3.1.3 ein. Dazu muss auf beiden Seiten (Client und Server) Apache XML-RPC zum Einsatz kommen. Da Apache XML-RPC in der Voreinstellung die offizielle XMLRPC-Spezifikation erfüllt, müssen für die Nutzung der nicht dem Standard entsprechenden Erweiterung sowohl der Client als auch der Server besonders konfiguriert werden. Hierzu ist die Eigenschaft enab1edForExtensl ans zu setzen. Programm 6.10 zeigt, dass Klassen, die das Interface java.lo. Programm 6.10 Seri al izab 1e implementieren, als Datentypen für XML-RPCHandler verwendet werden können. Zudem nutzen wir die Programmiertechnik aus Kapitel 6.4 ("Dynamische Proxies"). Die fachliche Aufgabenstellung wurde aus Programm 6.3 übernommen. publlc class Posltlon lmplements java.lo.Serlallzable { prlvate int td: prlvate Strlng name; prlvate double prels; prlvate lnt me nge ; public Position() } publlc Posltlon(lnt ld, Strlng name, double prels, i nt menge) { this.id~id; thlS.name = name; thls.prels = prels; Klasse Position 6 XML Remote Procedure Calls 244 thls. menge = me nge ; public int getld() return l d: public void set ld(int id) ( th is.id ~ id; publ i c String getName() ( return name; public void setName(String name) ( thlS name = name; public double getPreis() return prel 5; public void setPreis(double preis) ( thlS prels = prels; public int getMenge() return me nge ; publ i c void setMenge( int me nge ) thls. menge = me nge ; Interface Warenkorb Implementierung Warenkotblmpl lmport ja va.utll .Vector; public interface Warenkorb ( boolean addPosltlon(Posltlon pos); Vector<Posltlon> getPosltlonen(); lmport ja va.util .Vector; public class Warenkor blmpl implements Warenkorb private static Vector<Position> korb = new Vector<Position>(); (XML~RPC) 6.7 Nutzung einer Erweiterung in Apache XML-RPC 245 public boolean addPosition (Position pos) ( korb.add (pos); return true; publlc Vector<Posltion> getPositionen() return korb; Mit Hilfe der Klasse org.apache.xmlrpc server.XmlRpcSer verCon fig Impl kann ein Xm1RpcServer-Objekt konfiguriert werden. Die Xm1RpcServerConfi gImp l-Methode void setEnabledForExtensions(boolean ext) aktiviert (true) bzw. deaktiviert (fa 1se) die Erweiterungen. Mit dem Aufruf der Xml RpcServer-Methode s etConfig wird die Konfiguration confi 9 für den Server gesetzt s etConfi 9 (confi g). import import import import org org org org apache apache apache apache xm 1rpc s erver. PropertyHandlerMapping; xm 1rpc s erver. XmlRpcServer; xm 1rpc s erver. Xm1RpcServerConfi g Imp1 ; xm 1rpc webserver.WebServer; public class Server ( public static void main(String[] args) throws Except i on ( int port ~ I nt eger .par sel nt( ar gs [ O] ) ; PropertyHandlerMapping phm ~ new PropertyHandlerMapping(); phm. addHandler (" Warenkorb" . War enkor bl mp1. cl ass) ; WebServer webServer XmlRpcServer server = = new WebServer(port); webServer.getXmlRpcServer(); XmlRpcSer verConfig lmpl config ~ new XmlRpcSer verConfig lmpl(); config.setEnabledForExtensionsetrue); server.setConfig(config); server.set HandlerMapping(phm); webServer.start(); Server 246 6 XML Remote Procedure Calls (XML~RPC) Die Xm1RpcCl i ent Conf i 9 Imp1-Methode vOld setEnabledForExtensions(boolean ext) aktiviert (true) bzw. deaktiviert (fa 1se) die Erweiterungen. Client import ja va.net.URL; lmport ja va.utll .Vector; import org.apache xmlrpc client Xml RpcCl i ent ; import org.apache xmlrpc client Xml RpcCl i ent Conf i glmpl ; l mport arg. apache xmlrpc cllent ut il .Cli ent Factory; public class Cli ent ( public static voi d main(String args[]) throws Except i on ( URL url ~ new URL(args[O]); XmlRpcCl i entCon fig lmpl config ~ new Xm1RpcCl i entConfi 9Imp1( ) ; confi q. setServerURl(ur1); config.setEnabledForExtensionsetrue); XmlRpcCl i ent cli ent ~ new XmlRpcClient(); client.setConfig (config); ClientFactory factory ~ new ClientFactory(client); Warenkorb korb ~ (Warenkorb) factory .newInstance( Warenkorb.class); Position posl ~ new Position(lOOO. "Hamrrer". 2.5. 10) ; korb.addPosition(pos l); Position pos2 ~ new Position(lO lO. "Zange". 3.99. 8); korb.addPosition(pos2); Vector<Position> v = korb getPositionen(); for (Position pos v) ( + pos .getld()); System.out.println(" ld; System.out.println("Name; " + pos. getNarre ()); + pos.getPreis()); System.out.println("Preis; System. out .pri nt 1n( "Menge; " + pos. getMenge () ) ; System.out .println(); 6.8 Aufgaben 6.8 247 Aufgaben 1. Entwickeln Sie einen XML-RPC-SelVer. der an ihn übermittelte Nachrichten (Username, Text) in einer Datei speichert. 2. Entwickeln Sie einen XML-RPC-Server, der Adressen in einer Datenbank verwaltet. id nachname 1 Mei er vorname Hug o telef on ema i l 02 102 /1 12233 h . mei er @xy z . de Der Client kann • einen Eintrag erfassen: boolean add(String nachname, String vor name , String telefon. String email) • einen Eintrag löschen: boolean remo ve(int id) • alle Einträge auflisten: Objecte] getA11 () • Einträge suchen: Objecte] lookup(String spalte. String must er) Die einzelnen Funktionen sollen parametergesteuert (Aufrufparameter) abgerufen werden können: 3. Funktion Parameter getA 11 <ur I> re lTDve <ur I> <id> lookup <ur I> <spalte> <muster> add <ur I> <nachname> <vorname> <telefon> <email> Entwickeln Sie als Ergänzung zur Aufgabe 2 einen Client mit grafischer Oberfläche. mit dem nach Adressen gesucht werden kann Clookup). XML Remo t e Procedure Calls (XM"L-RPC) 6 248 B ild 6.6 : /jd Ad~= Adressen suchen nactowone 1.. IIM 'lb N~chn~m " Iu sen " eier 4. _00 1..- 11 Vornam e T,, ~ron 021111 2JH6 02102111 2233 -- I .. E·IU,i anz.maassen hme,.' Gl bc.de Ein iQ.-1L-RPC-5erve r so ll Eintritts- und Austrittsze iren in eine r Daten bank ve rwalten. ende id begin n hugo hugo 2010 -02 -2 1 08 :01 :43 20 10-02 -2 1 12 :15 :43 2010 -02 - 2 1 13 :10 : 12 20 10 -02 -2 1 15 :45 :02 hu go 2010 - 0 2-2 1 1 6 : 25 : 11 Der dient kann • Beginn. und Ende zu einer ID erfassen : Str ing setTime(Str i ng id) • die Ges amt zeit zu ein er ID abfragen: St r i ng getTot alTi me (St ring i d) 5. Dies e Aufgabe setzt PHP-Gru n dken n tnisse vo raus . Für die vier in Aufgabe 2 implementierten :x:ML-RPC-Dienste add, reno ve, getA11 und l ookup soll eine w eb-Obe rfläche mit Hilfe von PHP realisiert werden. Die einzelnen PHP-Skripte implem entieren also XML-RPC-Clien ts und gen erieren HTMLSeiten zur Anzeige de r Ergebnisse . Zur Lösung dieser Aufgabe kann beispie lsweise di e XMLRPC-llbrary für PHP unte r der Adresse ht tp: / /sourc eforge. netlpr oj ec t s /phpxm1rpc! heruntergela den werden. Die Include -Darei xmlrpc.inc e nt- hält den Code für die Clieru -Funktionallrät.