Sonntag, 6. Juni 2010


Artikel

Mai 2009 | Artikel

Write once, run everywhere Fortsetzung, Teil 2

Teil 1   Teil 2   

…und die Lösung

Die Anwendung – genannt WebOnDisk – schweißt die verschiedenen Technologien zusammen, die zum Starten der dynamischen Desktop-Webapp benötigt werden. Da das Programm die (Java-) Runtime Environment und die verfügbaren Webserver bzw. Browser zur Laufzeit ermitteln muss, muss sie in einer Form vorliegen, die von der Umgebung möglichst unabhängig ist. Die Wahl fällt in einem solchen Fall meist auf eine C++-Executable, die plattformspezifisch kompiliert eine sehr schnelle Low-level-Anbindung an das Betriebssystem ermöglicht.

In einem ersten Schritt wird die verfügbare Java-Umgebung ermittelt. Ist ein installiertes Java in der benötigten Version vorhanden, so wird diese verwendet, ansonsten kann eine mitgelieferte JRE benutzt werden. Ob Java installiert ist, ist aus der Registrierung ermittelbar, indem der Ast HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\Java Runtime Environment überprüft wird. Mithilfe der Library jvm.dll aus dem Unterverzeichnis bin/client/ kann die JRE geladen werden. Als Nächstes sollen zwei freie Ports ermittelt werden, die von unserem Webserver belegt werden können (Connector Port und Shutdown Port des Servlet-Containers). In einer Schleife werden dazu freie Ports gesucht (das Beispiel aus Listing 1 nutzt das Windows-API; für UNIX-basierte Systeme nutzt man spezifische Aufrufe).

Listing 1: Beispiel zum Ermitteln zweier freier Ports
  1. void CleanupSocket(int hSocket)
  2. {
  3. if (hSocket)
  4. closesocket(hSocket);
  5. SACleanup();
  6. }
  7. BOOL IsPortFree(USHORT ushPort, const char* sIP)
  8. {
  9. WSADATA wsaData;
  10. WORD version = MAKEWORD(2, 2);
  11. WSAStartup(version,(LPWSADATA)&wsaData);
  12. SOCKADDR* _pmySockAdress;
  13. sockaddr_in service;
  14. service.sin_family = AF_INET;
  15. service.sin_addr.s_addr = inet_addr(sIP);
  16. service.sin_port = htons(ushPort);
  17. _pmySockAdress = (SOCKADDR*) &service;
  18. SOCKET hSocket = socket(AF_INET, SOCK_STREAM, 0);
  19. int _iReturn = bind(hSocket, _pmySockAdress, sizeof(service));
  20. if (_iReturn == SOCKET_ERROR)
  21. {
  22. if (WSAGetLastError() == WSAEINVAL)
  23. {
  24. CleanupSocket(hSocket);
  25. return FALSE; //port in use
  26. }
  27. CleanupSocket(hSocket);
  28. return FALSE; //other error
  29. }
  30. CleanupSocket(hSocket);
  31. return TRUE ;
  32. }
  33. USHORT GetFreePort()
  34. {
  35. USHORT ushPort = 49152;
  36. while (ushPort <= USHRT_MAX)
  37. {
  38. if(IsPortFree(ushPort, "127.0.0.1"))
  39. break;
  40. ushPort++;
  41. }
  42. return ushPort;
  43. }

Jetzt kann ein Servlet-Container (in unserem Beispiel Jetty) mithilfe der procrun- Library problemlos mit Angabe der ermittelten Ports gestartet werden (Listing 2). Die procrun-Library kapselt Zugriffe auf das Java-Framework, mit dessen Hilfe der Webserver hochgefahren werden kann (libprocrun ist eine Library der Apache Software Foundation mit der Bezeichnung "Apache Commons Daemon" unter der Apache License Version 2).

Listing 2: Unser Servlet-Container
  1. BOOL bUseTomcat = FALSE;
  2. CString sJvmDll(_T(sDirOfJRE+"\\bin\\client\\jvm.dll"));
  3. APXHANDLE hPool = apxPoolCreate(NULL, 0);
  4. _towchar _swJvmDll(sJvmDll);
  5. APXHANDLE hWorker = apxCreateJava(hPool,_swJvmDll);
  6. if (IS_INVALID_HANDLE(hWorker))
  7. return FALSE;
  8. CString sStartupJar = bUseTomcat ? "bin\\bootstrap.jar" : "start.jar";
  9. CString sStartupClass = bUseTomcat ? "org/apache/catalina/startup/Bootstrap" :
  10. "org/mortbay/start/Main";
  11. LPSTR _jni_jvmoptions = "-XX:+CMSPermGenSweepingEnabled";
  12. //eine temporär geschriebene Konfigurationsdatei des Servers
  13. //mit Angabe der freien Ports
  14. LPSTR _jni_rparam = "c:\\temp\\etc\\jetty.xml";
  15. apxJavaInitialize(hWorker, sStartupJar, _jni_jvmoptions,
  16. iJavaXms, iJavaXmx, dwThreadStackSize);
  17. apxJavaLoadMainClass(hWorker, sStartupClass, NULL, _jni_rparam);
  18. apxJavaSetOut(hWorker, TRUE, _swSCErrFilePathName);
  19. apxJavaSetOut(hWorker, FALSE, _swSCOutFilePathName);
  20. //Run worker
  21. return apxJavaStart(hWorker);

In einem weiteren Schritt muss festgestellt werden, wann die Servlet Engine ansprechbar ist. Hierzu werden in einer Schleife in definierten Zeitabständen HTTP-Requests an den ermittelten freien Connector Port abgesetzt, bis eine Antwort ermittelt werden kann. Ist eine Antwort verfügbar, kann der Standardbrowser des Benutzers mit dem URL der Webapp als Command-Line-Parameter gestartet werden, der zu der lokal eingerichteten Webseite navigiert.

Procrun: Der Apache commons daemon

Die Library procrun (libprocrun) der Apache Software Foundation ist eine Sammlung von APIs und Anwendungen, die den Zugriff auf das Java-API aus C++-Anwendungen heraus erleichtern; Projektdateien für verschiedene Versionen von Microsoft Visual Studio sind verfügbar. Die Software steht unter der Apache Software License, Version 2.0. Mithilfe dieser Library wird unter anderem der Servlet-Container Tomcat für Windows (tomcat.exe, tomcatw.exe) erstellt. Sie erlaubt es unter anderem, eine JRE zu laden, Java-Anwendungen über deren public static void main(args[])-Funktion zu starten und beim Herunterfahren der Runtime-Umgebung benutzerdefinierte Aktionen auszuführen. Die Installation der Anwendung als Service wird unterstützt und kann mittels Startparameter gesteuert werden. Die Library unterstützt auch den Zugriff aus Java-Anwendungen; so ist es möglich, aus Java den Native Daemon zu steuern.

Eine neue Fragestellung taucht auf, wenn der Benutzer die Arbeiten auf unserer lokalen Anwendung beendet. Sollte der Servlet-Container heruntergefahren werden? Dagegen spricht, dass der Start des Servers beim Neustart der Anwendung länger dauert als der Start des Browsers im Falle eines bereits laufenden Webservers. Andererseits belegen der Server und die JRE Ressourcen, die nach dem Ende der Benutzer-Sessions freigegeben werden können. Sollen die Ressourcen des Client-Rechners geschont werden, wird empfohlen, den Server zu beenden, sobald alle Sessions abgelaufen sind. Um die Anzahl der offenen Sessions zu ermitteln, kann die Serveranwendung im Fall von Tomcat die Manager-Applikation periodisch befragen (unter dem URL manager/list). Unter Jetty steht die Manager-Applikation nicht zur Verfügung, hier muss die Webapp unter einer URL die Anzahl der offenen Sessions bereitstellen.

Warum reicht es nicht, die gestartete Browserinstanz bis zum Beenden zu überwachen? Der Grund liegt im verschiedenen Verhalten der Browser: Manche öffnen beim Aufruf eine neue Anwendungsinstanz (IE 6.x), manche verwenden eine bereits gestartete und öffnen dort einen neuen Tab (Firefox) oder ein neues Browserfenster. Natürlich kann es vorkommen, dass der Anwender seine Session ablaufen lässt und dann einen Link in der Webseite klickt. Der Klick läuft ins Leere, da mit der Session auch der Webserver heruntergefahren wird. Mit einem Workaround lässt sich die Session-Timeout überlisten: Ein in die angezeigte Webseite eingebettetes unsichtbares IFRAME setzt periodisch (mittels Meta-Tag refresh oder einer JavaScript-Funktion) Requests ab, die die Session am Leben erhalten. Statt Aufruf des Standardbrowsers ist es auch denkbar, noch ein Stück autonomer vorzugehen und sogar den benötigten, bzw. empfohlenen Browser mitzuliefern! Die portablen Versionen von Firefox und Opera arbeiten auch ohne Installation fehlerfrei, sogar eine auf Adobe AIR basierende Lösung kann umgesetzt werden.

Für den Einsatz von Adobe AIR spricht, dass der eingebettete, auf Webkit basierende Browser schlank, schnell und ohne Toolbar/Buttonbar verwendbar ist. Die Binaries von Adobe AIR funktionieren auch ohne Installation erwartungsgemäß und sind über das Adobe AIR SDK verfügbar. Die WebOnDisk-Anwendung kann Adobe AIR mit einer XML-Steuerdatei lenken, der Speicherort der temporär erstellten XML-Datei wird dabei als Command-Line-Parameter an AIR übergeben. Die einfachste Form einer solchen Steuerdatei sehen Sie z. B. in Listing 3.

Listing 3: Beispiel für eine XML-Steuerdatei für Adobe AIR
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <application xmlns="http://ns.adobe.com/air/application/1.0">
  3. <id>mywebapp</id>
  4. <version>0.1</version>
  5. <filename>mywebapp</filename>
  6. <initialWindow>
  7. <content>mywebapp.htm</content>
  8. <visible>true</visible>
  9. </initialWindow>
  10. </application>

Das angegebene, einfache Beispiel steuert Adobe AIR zur Anzeige der HTML-Datei mywebapp.htm (weitere Einstellungen, wie die Größe und Position des zu öffnenden Fensters bzw. anzuzeigendes Icon sind möglich). Die Angabe eines URL in der XML-Steuerdatei wird von AIR nicht unterstützt. Deshalb muss diese temporär erstellte HTML-Datei mywebapp.htm mittels JavaScript auf den URL der unter localhost geladenen Webapp weiterleiten.

Grenzen der Technologie

Schöner Artikel?

Hat Ihnen der Artikel gefallen? Dies und mehr ist alles Teil der Ausgabe 3.09 unseres Entwickler Magazins. Alle zwei Monate frisch am Kiosk! Zur jeweils aktuellen Ausgabe geht es hier.

Die gezeigte Lösung erlaubt es, auch komplexe Webanwendungen auf einem Desktoprechner auszuführen, indem diese mithilfe einer JRE in einen leichtgewichtigen Servlet-Container eingebunden werden. Dabei ist diese Technologie trotzdem einem dedizierten Server nicht gleichzusetzen. Folgendes ist beim Einsatz der WebOnDisk-Lösung zu bedenken:

  • Ein Webserver muss für seine Webanwendungen mehr bieten als nur eine Runtime Environment. Datenbankverbindungen und andere zu installierende Softwarekomponenten bzw. Frameworks sind heute Standard bei komplexen Enterprise-Webanwendungen.
  • Die mitgelieferten Softwarekomponenten, die ein autonomes Starten ohne Voraussetzungen an den Clientrechner erlauben, benötigen erheblichen Speicherplatz. Die JRE schlägt mit bis zu 70-80 MB zu Buche, der Servlet-Container Jetty ist wiederum mit ca. 6 MB relativ schlank.
  • Die entwickelte Lösung kann von jedem beliebigen Laufwerk gestartet werden, auch von CD/DVD bzw. UNC-Pfad. Allerdings sind die Startzeiten des Servers von einem langsamen Medium entsprechend zeitraubend. Die Startzeit kann mit Anzeigen eines Splash-Screens überbrückt werden.
  • Die Implementierung der Lösung für Nicht-Windows-Systeme ist möglich. Statt Verwendung von MFC (Microsoft Foundation Class Library) muss man unter Linux bzw. MAC OS auf die betriebssystemspezifischen Libraries zurückgreifen. Für UNIX-basierte Systeme stellt die Apache Software Foundation statt libprocrun das jsvc-Framework zur Verfügung.
  • Die WebOnDisk-Lösung startet auch unter aktuellen Wine-Implementierungen schnell.
Erfahrungen

Die WebOnDisk-Technologie wird bereits im professionellen Umfeld in Verbindung mit Enterprise-Webanwendungen, basierend auf JSF-Technik erfolgreich eingesetzt. Die Auftraggeber profitieren von den einmalig anfallenden Entwicklungskosten und bieten die Anwendung für die Nutzer über das Internet/Intranet und auf CD-ROM bzw. USB-Stick an. Die Robustheit der verwendeten Open-Source-Frameworks und Libraries wird durch das geringe Hotline-Aufkommen bestätigt. Ein weiterer Vorteil ist das einfache und kostengünstige Customizing der Anwendung durch Verwendung des JSF-Framework, von HTML sowie CSS, ohne dass die Codebasis der Anwendung angepasst werden muss.

Fazit

Die Anforderung, die vor ein paar Jahren noch als Vision galt – nämlich Webanwendungen auf dem Desktop anzubieten – ist heute mit vergleichsweise wenig Aufwand umzusetzen. Freie Standards und Open-Source-Software machen es möglich, das Web ins Wohnzimmer zu holen. Die kostengünstige und flexible WebOnDisk-Technologie überzeugt die Hersteller; die modische Oberfläche bietet dem webgewöhnten Nutzer die ihm vertraute Umgebung. Und durch das Mitbringen aller benötigten Softwarekomponenten wird die größtmögliche Unabhängigkeit vom Clientsystem und dessen Einstellungen erreicht; die automatische Ermittlung aller Konfigurationsparameter vermeidet manuelle Konfigurationsfehler.

George Herczeg, Softwarearchitekt bei der SHI Elektronische Medien GmbH in Augsburg. Mit mehr als 10 Jahren Erfahrung als C++-, Java-, Perl-, ASP- und PHP-Entwickler unterstützt er die Integration von Softwarekomponenten in unterschiedlichen Sprachen. Kontakt: george.herczeg@shi-gmbh.com.

Teil 1   Teil 2   

Kommentare