FachartikelAnatomie einer "unmöglichen" Architektur -
Aufrufen von Web-Services direkt aus einem Standard-Browser heraus
|
| POST /examples/test1/TarifRechnerWebService/Service1.asmx HTTP/1.1 Host: localhost Content-Type: text/xml; charset=utf-8 Content-Length: length SOAPAction: http://tempuri.org/rechneRueckkauf
<?xml version="1.0" encoding="utf-8"?> |
Im Vergleich dazu sieht der Datenstrom, der bei einem "normalen" HTML-Formular übertragen wird, wie folgt aus:
| POST /formresult.html HTTP/1.1 Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/msword, application/vnd.ms-excel, application/vnd.ms-powerpoint, */* Host: localhost:8080 Content-Length: 30 Connection: Keep-Alive Cache-Control: no-cache
x=3 |
Man erkennt, dass das SOAP-Protokoll auf XML und nicht auf den einfachen Key-Value-Paaren wie bei HTML-Formularen beruht. Leider ist dieses Key-Value-Format die einzige Möglichkeit, wie ein Standard-Browser ohne PlugIn und Java-Applets Daten an den Web-Server zurücksenden kann. Web-Services können zwar auch über HTTP-POST oder GET gerufen werden, die Parameter, die dem Web-Service übermittelt werden, müssen jedoch dem einfachen Key-Value-Format genügen, welches der Browser versenden kann. Diese Verfahren eignen sich nur für die Übertragung einfacher Datenstrukturen.
Der Web-Service des obigen Beispiels antwortet mit Daten, die ebenfalls im XML-Format codiert werden. Damit können nur XML-fähige Browser diese Antwort interpretieren. Der (noch) weit verbreitete Net-scape 4.x kann mit einem XML-Datenstrom gar nichts anfangen. Standard-HTML-Formulare liefern immer eine weitere HTML-Seite zurück, schlimmer noch: Sie können kein Formular über den Browser versenden, ohne eine neue Seite zu laden. Das bedeutet, die ursprüngliche Seite wird durch das Versenden der Daten quasi "vernichtet".
Es gibt zwar Möglichkeiten, dies zu verhindern, jedoch verbirgt sich dahinter ein hoher Aufwand oder Kompatibilitätsprobleme. Diese Möglichkeiten werden in [2] beschrieben. Es gibt daher - selbst wenn ein XML-fähiger Browser verwendet wird - kaum Möglichkeiten, das Ergebnis eines SOAP-Kommandos sinnvoll in die Applikation einzubinden, da das Ergebnis in irgendeiner Form im Browser in eine HTML-Seite umgeformt werden muss.
Fazit: Web-Services lassen sich aus den Browsern heraus nicht ohne weiteres aufrufen, da die Browser über keine oder unzureichende XML-Unterstützung verfügen. Aus diesem Grund werden Web-Services heutzutage vor allem im Backend-Bereich eingesetzt, um heterogene Systeme miteinander zu verbinden.
...und es geht doch und bringt Vorteile
Web-Services lassen sich aus den aktuellen Browsern heraus nicht ohne Weiteres aufrufen. Diese Behauptung greift zu kurz. Denn mit ein wenig Überlegung und architektonischem Unterbau lassen sich Web-Services sehr wohl aus einem Browser heraus aufrufen. Ziel ist es, aus Standard -HTML-Seiten direkt beliebige Web-Services aufzurufen die z.B. mit .NET oder dem Apache SOAP-Toolkit implementiert werden.
Welchen Vorteil hat der Ansatz gegenüber der Verwendung von Web-Services auf dem Server? Der Hauptvorteil liegt in dem verringerten Umfang und Komplexität des Server-Codes. Wird eine klassische MVC-Architektur verwendet - um das Beispiel aus dem Artikel umzusetzen - wird relativ viel Code benötigt, um die Eingaben entgegenzunehmen, nach SOAP zu konvertieren und das Ergebnis wieder in HTML umzuformen. (Bei STRUTS z.B. Action, ActionForm-Klassen, generierte Proxies, JSP's usw.). Diese Dateien sind Bestandteil des "dynamischen Teils" der Applikation, d.h. sie müssen übersetzt werden, stehen unter Kontrolle des Applikation-Servers und müssen gemäß J2EE-Spezifikation in ein EAR-File "assembliert" und für den Application-Server "deployed" werden. Ein Änderungszyklus kann in einer komplexen Umgebung mehrere Stunden dauern. Eine komplexe Umgebung entsteht z.B. durch NT-Rechner als Entwicklungsmaschine und Teststufe1, SUN-Rechner für Test-stufe2 und Produktion, kein Zugriff der Entwickler auf den Produktionsrechner, die Rollen des "Application Assembler" und "Deployers" organisatorisch getrennt usw.
Die vorgestellte Architektur verwendet generische Server-Komponenten, die sich nicht ändern, wenn z.B. ein neuer Web-Service gerufen werden soll. Änderungen werden nur noch im "statischen Teil" der Applikation vorgenommen, die unter Kontrolle des Web-Servers liegen. In vielen Fällen genügt ein einfaches Kopieren der geänderten Dateien: Microsoft würde in diesem Zusammenhang von XCOPY-Deployment' sprechen: Einfach auf die Platte kopieren und fertig.
In der Praxis können Anwendungen auf Basis dieser Architektur zu fast 90% auf Basis von Web-Services entwickelt werden. Nur noch bei komplexen Vorgängen wird auf die klassische MVC2 -Architektur zurückgegriffen, die ein aufwändiges Deployment zur Folge hat.
Eine Beispielanwendung
Zur Demonstration soll eine kleine Beispielanwendung entwickelt werden, die das "addressbook-sample" des Apache-SOAP-Toolkits verwendet. Das Apache-SOAP-Toolkit steht unter [3] frei zur Verfügung und ist z.B. unter TOMCAT einsetzbar. Eine sehr gute Anleitung zur Installation ist unter [4] zu finden. Das "addressbook-sample" stellt eine einfache Adressverwaltung auf Basis von Web-Services dar, der Service GetAllListings liefert hierbei den gesamten Inhalt des Adressbuchs. Dabei werden folgende Informationen zwischen den Beteiligten ausgetauscht: Der Request auf Basis von HTTP:
| POST /soap/servlet/rpcrouter HTTP/1.0 Host: localhost:8070 Content-Type: text/xml; charset=utf-8 Content-Length: 417 SOAPAction: ""
<?xml version='1.0' encoding='UTF-8'?> |
| SOAP-ENV:encodingStyle="http://xml.apache.org/xml-soap
/literalxml"> </ns1:getAllListings> </SOAP-ENV:Body> </SOAP-ENV:Envelope> |
Der Server antwortet mit der Liste aller Adressen:
| <?xml version='1.0' encoding='UTF-8'?> <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/ soap/envelope/" xm lns:xsi="http://www.w3.org/1999/XMLSchema-instance" xmlns:xsd="http://www.w3.org /1999/XMLSchema"> <SOAP-ENV:Body> <ns1:getAddressFromNameResponse xmlns:ns1="urn:AddressFetcher" SOAP-ENV:encoding Style="http://schemas.xmlsoap.org/soap/encoding/"> <return xmlns:ns2="urn:xml-soap-address-demo" xsi:type="ns2:address"> <phoneNumber xsi:type="ns2:phone"> <exchange xsi:type="xsd:string">456</exchange> <areaCode xsi:type="xsd:int">123</areaCode> <number xsi:type="xsd:string">7890</number> </phoneNumber> <zip xsi:type="xsd:int">12345</zip> <streetNum xsi:type="xsd:int">123</streetNum> <streetName xsi:type="xsd:string">Main Street </streetName> <state xsi:type="xsd:string">NY</state> <city xsi:type="xsd:string">Anytown</city> </return> </ns1:getAddressFromNameResponse> </SOAP-ENV:Body> </SOAP-ENV:Envelope> |
Abb 1: Die Beispielanwendung nach Aufruf des Web-Services
Schritt 1: Wie man aus dem Browser einen Web-Service aufruft...
Der Browser kann zwar keinen SOAP-Request direkt absetzen, es ist jedoch einfach, ein Formular zu entwickeln, in welches der komplette SOAP-Request eingetragen und versendet wird. Der Aufbau des SOAP-Kommandos wird über Java Script am Client durchgeführt, der Datenstrom wird an ein spezielles Servlet gesendet, welches die Daten analysiert und den eigentlichen Aufruf über das Apache-SOAP-Toolkit ausführt. Es soll mit dem Formular auf der Clientseite begonnen werden: Das Formular hat folgenden Aufbau:
| <form name="send_xml_data" action="http://localhost:8080/soap/servlet/ConvertToApacheServlet" method=POST> <textarea rows=10 cols=80 name=data_xml> </textarea> <input name=data_submit type=submit> </HTML> |
Das folgende Beispiel zeigt die zweite Möglichkeit: Ein FrameSet beinhaltet den sichtbaren Bereich und einen unsichtbaren Teil, der das versteckte Formular zum Senden des SOAP-Kommandos enthält. Die Datei start.html enthält den sichtbaren Teil, die Datei hidden_form.html enthält den unsichtbaren Teil der Anwendung. Durch die Angabe rows=100%,* wird nur der sichtbare Teil im Browser angezeigt.
| <html><head><title>Web-Service-Demo</title>
</head> <FRAMESET border=0 frameborder=0 framespacing=0 rows="100%, *" target=_top>
<FRAME src="./hidden_form.html" name="hidden_form" id="hidden_form" scrolling="no"> </FRAMESET> |
| <body bgColor=#CCFFCC> <HR> <H3>Suchen von Adressdaten mit Web-Services </h3><BR>
<form id=add action="javascript:sendxmldata();"> |
Zu beachten ist, dass beim Versenden des Formulars eine Java Script-Funktion gerufen wird. Der <div style="position:relative;" id=result></div> HTML-Code dient zur späteren Anzeige des Ergebnisses. Die folgende Funktion füllt das versteckte Formular mit dem entsprechenden SOAP-Daten für den Aufruf des Web-Services und sendet das Formular über die submit ()-Funktion an ein Servlet:
| // Variable mit dem Inhalt des SOAP Requests var data_getall_xml="<?xml version='1.0' encoding='UTF-8'?> <SOAP-ENV:Envelope xmlns:SOAP-ENV='http://schemas.xmlsoap.org/soap/envelope/' xmlns:xsi='http://www.w3.org/1999/XMLSchema-instance' xmlns:xsd='http://www.w3.org/1999/XMLSchema'><SOAP-ENV:Body> <ns1:getAllListings xmlns:ns1='urn:AddressFetcher' SOAP-ENV:encodingStyle='http://xml.apache.org/xml -soap/literalxml'></ns1:getAllListings></SOAP-ENV:Body> </SOAP-ENV:Envelope>"; function sendxmldata(form) { var hiddenForm=top.frames[1].document.forms["send_xml_data"]; hiddenForm.data_xml.value=data_getall_xml; hiddenForm.submit(); return false; }; |
Das "unsichtbare" Formular wird verschickt und der Web-Server erhält folgenden HTTP-Strom, den es nun weiterzuverarbeiten gilt:
| POST /soap/servlet/ConvertToApacheServlet HTTP/1.1 Host: localhost:8081 User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.0; de-DE; rv:0.9.4) Gecko/2001 1019 Netscape6/6.2 Accept: text/xml, application/xml, application/xhtml+xml, text/html;q=0.9, image /png, image/jpeg, image/gif;q=0.2, text/plain;q=0.8, text/css, */*;q=0.1 Accept-Language: de-DE Accept-Encoding: gzip, deflate, compress;q=0.9 Accept-Charset: ISO-8859-1, utf-8;q=0.66, *;q=0.66 Keep-Alive: 300 Connection: keep-alive Referer: http://localhost:8080/examples/xandra/simple/hidden_form.html Content-type: application/x-www-form-urlencoded Content-Length: 560
data_xml=%3C%3Fxml+version%3D%271.0%27+encoding%3D%27UTF- |
Wie zu erkennen ist, wird der komplette SOAP-Request in einem Datenfeld mit dem Namen data_xml übertragen. Diese Daten werden an ein Servlet gesendet, welches die Daten auswerten kann.
Was bisher erreicht wurde
Dadurch, dass SOAP über ein Feld in einem versteckten Formular versendet wird, haben wir in einem Browser die Möglichkeit,
Dadurch werden die Beschränkungen von Standard-Formularen aufgehoben, bei denen nur Key-Value-Paare und keine strukturierten Daten versendet werden können. In den nächsten Schritten gilt es nun
Gerade der letzte Schritt bedarf einiger "Tricks", da nicht alle Browser in der Lage sind, XML-Dateien anzuzeigen.
Schritt 2: Weiterleiten des Requests an das Apache-SOAP-Toolkit
Die Daten, die vom Browser geladen werden, werden in folgenden Schritten weiterverarbeitet:
| public class ConvertToApacheServlet extends HttpServlet { public void doPost (HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { // Schritt 1, Request erzeugen String data_xml = req.getParameter("data_xml");
int length=data_xml.length();
// Schritt 2, Request an SOAP-Toolkit weiterleiten
// Schritt 3, Ergebnis abholen... |
Schritt 3: Zurücksenden der Daten an den Browser
Nach dem Abarbeiten des Requests sendet das SOAP-Toolkit die Antwort als XML-Daten an den Rufer. Diese "rohen" XML-Daten müssen nun so "verpackt" werden, dass der Browser die Daten interpretieren kann. Hierzu wird der XML-Datenstrom
| // Schritt 4 extrahiere XML-Nettodaten und füge JavaScript-Header
hinzu int startPosition = sResult.indexOf ("<SOAP-ENV:Envelope"); if ( startPosition > -1) { sResult=sResult.substring(startPosition); sResult="<?xml version='1.0' encoding='UTF-8'?>"+sResult; }
sResult=sResult.replace('\n',' '); |
Über die Zeile onload=\"javascript:top.frames[0].onXML DataReceived(data_xml) wird eine Java Script-Funktion im "sichtbaren" Bereich gerufen, die die XML-Antwort als Parameter erhält. Die "onLoad"-Funktion wird von allen gängigen Browsern gerufen, nachdem die HTML-Seite komplett geladen wurde. Dieser Funktion werden die SOAP-Daten übergeben, die zuvor in einer Variablen abgespeichert wurden.
Schritt 4: Analysieren und Anzeigen des Ergebnisses im Browser
Der Funktion onXMLDataReceived() fällt nun die Aufgabe zu, den XML Datenstrom zu zerlegen und das Ergebnis anzuzeigen. Hierzu wird in der Beispiel-Applikation ein unter [5] verfügbarer XML-Parser verwendet, der den Datenstrom zerlegt und als Datenstrukturen ähnlich dem Document Object Model (DOM) zur Verfügung gestellt. Die Funktion formt den Ergebnis-Daten-strom in HTML-Code um, der dann über Dynamic HTML (DHTML) in der Seite angezeigt wird:
| function onXMLDataReceived(xmlstring ) { // Parsen der XML-Daten var root= new Xparse(xmlstring); // Suche den Knoten mit dem Namen AddressBook var adrBook=XMLGetElementTag("AddressBook",root); // Aufbau der HTML-Ausgabe
var result="<TABLE border=1 width=100% bgColor=lightblue>"; |
ToDo's
Mit dieser zugegebenermaßen einfachen Architektur können fertige und in ihrer Struktur bekannte Web-Services aufgerufen werden. Web-Services sind jedoch noch durch einige Punkte gekennzeichnet, die nicht berücksichtigt wurden. Das sind:
Ein weiterer wichtiger Punkt ist auch das Session Management, da die vorgestellte Architektur bisher vollkommen ohne serverseitigen Code zur Sessionverwaltung vorgestellt wurde. Dieser Punkt soll Thema weiterer Artikel sein...
Fazit
Die skizzierte Architektur ist sehr schlank und extrem skalierbar. Da die Servlets für alle Web-Services gleich sind, wird keinerlei "Applicationserver-Code" benötigt, um beliebige Web-Services zu einer neuen Applikation zu verknüpfen. Der Code befindet sich im statischen Teil der Applikation, der wesentlich einfacher zu verwalten ist: Keine aufwändige Assemblierung und kein Deployment spart Zeit und Kosten.
Die eigentlichen Web-Services können mit leistungsfähigen Umgebungen wie .NET oder dem WebSphere Application Developer erstellt und aus jeder Standard-HTML-Seite heraus aufgerufen werden. In einigen Projekten hat sich gezeigt, dass fast 100% der Anwendungen über die Kombination von lose gekoppelten Web-Services umgesetzt werden konnten. Ein Beispiel finden Sie im Web unter [6]. Diese Anwendung enthält überhaupt keinen serverseitigen Code, der innerhalb eines Applikations-Servers verwaltet werden müsste. Da die Web-Services vollkommen vom Ausgabemedium getrennt sind, können diese aus den verschiedensten Umgebungen (Browser, Java, C++ -Anwendungen u.a.) verwendet werden und stellen somit echte, sprach- und architekturunabhängige Komponenten dar.
Über den Autor:
Jürgen Nicolai ist Geschäftsführer der main {GRUPPE} in Stuttgart. Auf Basis der in dem Artikel beschriebenen Grundlagen wurde XANDRA® Technology entwickelt und patentiert. XANDRA® Technology ist eine Technologie, die vollkommen neue Konzepte zur Erstellung von anspruchsvollen Web-Anwendungen auf der Basis von J2EE verwendet. Jürgen Nicolai ist unter j.nicolai@main-gruppe.de erreichbar.
Links & Literatur