Aus Linux-Magazin 02/2007

Asynchrone Webanwendungen mit Jetty, Comet und anderen

© sxc.hu, Yarick Mission

Nach der Ajax-Revolution im Browser steht eine Modernisierung der Server-Seite an. Auch dort sollen asynchrone Requests zu schnelleren und besser skalierenden Webanwendungen führen. Der Jetty-Server hat dazu Continuations implementiert, Cometd bietet einen Standard.

Noch haben sich nicht alle Webentwickler auf das Ajax-Modell eingestellt, da steht die nächste Aufgabe schon vor der Tür: Soll der Browser eine geöffnete Webseite möglichst aktuell halten, muss er sich regelmäßig über Veränderungen am Datenbestand auf dem Server informieren. Dazu läuft meist im Hintergrund eine Javascript-Funktion, die in einem festgelegten, möglichst kleinen Intervall den Server nach Änderungen befragt (so genanntes Polling).

Dumm nur, wenn sich eine Zeit lang überhaupt nichts ändert, dann waren nämlich alle Anfragen vergebens. Angenommen das Polling-Intervall liegt bei 5 Sekunden, die Daten ändern sich aber 2 Minuten lang nicht, dann sind das 24 überflüssige Requests – wohlgemerkt pro Client und Browser.

Effektiver wäre es, wenn der Server von sich aus dem Client mitteilen könnte, dass sich die interessierenden Daten geändert haben. Leider geht das nicht so einfach: Der HTTP-Verkehr gehorcht dem Request-Response-Modell, bei dem der Request vom Client ausgeht. Ein möglicher Ausweg wäre, die Verbindung zwischen Client und Server bestehen zu lassen, sodass der Server seine Antwort erst dann schickt, wenn es auch etwas Sinnvolles zu antworten gibt.

Das ist tatsächlich möglich, da die meisten Browser und Server über HTTP 1.1 kommunizieren, bei dem so genannte Keep-Alive-Connections Standard sind. So wird aus dem bislang synchronen (Server antwortet sofort auf Request) ein asynchroner Ablauf. Die Threads, Prozesse und so fort, die auf Server-Seite eine Anfrage bedienen, müssen dann entsprechend länger laufen.

Damit das nicht zu einem neuen Ressourcenengpass führt, sind weitere kluge Ideen gefragt. Eine hat Greg Wilkins in dem in Java geschriebenen Jetty-Server implementiert. Jetty [1] ist ein Webserver und Servlet-Container, der sich durch seine geringe Größe auszeichnet. Deshalb setzen ihn viele bekannte J2EE-Server wie Jonas, Geronimo und Jboss als Servlet-Container ein.

Jetty-Continuations

Greg Wilkins, der in seinem Blog [2] fleißig über neue asynchrone Webtechniken berichtet, verwendet für asynchrone Anfragen mit Jetty so genannte Continuations. Bekannt sind diese bisher vor allem von experimentellen Servern aus der Ruby- und Smalltalk-Ecke, etwa Borges [3]. Die Motivation für die Entwicklung einer eigenen Lösung war, dass Java-Webserver für jede neue Verbindung einen neuen Thread erzeugen.

Obwohl die JVM in den letzten Jahren in diesem Bereich stark optimiert wurde, bedeuten viele Threads immer noch einen relativ großen Verbrauch an Ressourcen wie Speicher – auch wenn ein Thread mit »wait()« schlafen gelegt wird. Zudem begrenzen die meisten Server die maximale Anzahl an Threads mit einem harten Limit, sodass bei dessen Erreichen er einfach keine neuen Verbindungen mehr annimmt.

Extra API

Die von Wilkins implementierten Jetty-Continuations [4] erlauben es, die Bearbeitung eines Requests auszusetzen oder auf später zu verschieben, den zugehörigen Thread aber für einen neuen Request zu verwenden. Um davon Gebrauch zu machen, müssen Webprogrammierer explizit die Continuations-Klassen verwenden und ihre Anwendungen entsprechend umstrukturieren.

So liefert die Helferklasse »ContinuationSupport.getContinuation()« eine neue Continuation, die man mit »suspend()« aussetzt und mit »resume()« wieder aufnimmt. Listing 1 zeigt die Implementation der entsprechenden Methode des Chat-Beispiels von Jetty. Daran ist zu sehen, dass es sich bei der Jetty-Implementation gar nicht um echte Continuations handelt, wie sie von anderen Programmiersprachen bekannt sind.

Listing 1: »Chat.java«

01 private void doPoll(HttpServletRequest request, AjaxResponse response)
02     {
03         HttpSession session = request.getSession(true);
04         String id = session.getId();
05         long timeoutMS = 10000L;
06         if (request.getParameter("timeout")!=null)
07             timeoutMS=Long.parseLong(request.getParameter("timeout"));
08 
09         Member member=null;
10         synchronized (mutex)
11         {
12             member = (Member)chatroom.get(id);
13             if (member==null)
14             {
15                 member = new Member(session,null);
16                 chatroom.put(session.getId(),member);
17             }
18 
19             // Get an existing Continuation or create a new one if there are no events.
20             if (!member.hasMessages())
21             {
22                 Continuation continuation = ContinuationSupport.getContinuation(request, mutex);
23                 member.setContinuation(continuation);
24                 continuation.suspend(timeoutMS);
25             }
26             member.setContinuation(null);
27 
28             if (member.sendMessages(response))
29                 sendMembers(response);
30         }
31 
32     }

Die merken sich nämlich die Stelle, an der der Programmfluss die Continuation-Subroutine verlässt, und springen bei einem erneuten Aufruf genau dorthin. Weil das mit den Jetty-Continuations nicht funktioniert, muss der Entwickler selbst dafür sorgen, dass sich die aufgerufene Funktion bei jedem Aufruf gleich verhält, im Jargon “idempotent” ist.

Wilkins verwendet in seinem Beispiel dazu das Chatroom-Objekt, in dem er beim ersten Aufruf die Session-ID speichert. Wenn keine Nachrichten anstehen, setzt die Methode die Verarbeitung aus. Bei einem weiteren Aufruf steht die ID wieder über das Chatroom-Objekt zur Verfügung (Zeile 12).

Comet

Ähnlich wie bei Ajax gibt es auch zu der neuen oder neu entdeckten Server-Push-Technologie einen Aufsatz, der dem Kind einen Namen gibt. Analog zum Event-basierten Update von Teilen einer HTML-Seite bei Ajax siedelt Alex Russell in seinem Aufsatz “Comet: Low Latency Data for the Browser” einen Event-basierten Layer auf Server-Seite an [5]. Speziell bei interaktiven Multiuser-Anwendungen im Web soll der Comet-Ansatz – ähnlich wie oben beschrieben – die Antwortzeiten des Gesamtsystems verkürzen.

Um die Vorgehensweise zu vereinheitlichen haben Russell, Wilkins und andere einen Server namens Cometd entwickelt, der als Event-basierter Message-Bus die asynchrone Kommunikation am Webserver verbessern soll [6]. Er implementiert das eigens entwickelte Protokoll Bayeux, über das Webserver und Backend miteinander sprechen. Implementationen gibt es unter anderem in Python und Perl.

Leider ist die Dokumentation zurzeit noch recht dürftig. Im ersten Release-Kandidaten der 6.1-Release von Jetty hat Greg Wilkins aber ein Chat-Beispiel mit Comet und Bayeux implementiert, das deren Verwendung illustriert. Abbildung 1 zeigt den Chat im Browser mit den Requests und den Jetty-Debug-Ausgaben.

Abbildung 1: Ein Webchat mit Hilfe von Comet und Jetty. Im Debug-Fenster ist ein noch offener Request zu sehen, auf dessen Beantwortung der Browser wartet.

Abbildung 1: Ein Webchat mit Hilfe von Comet und Jetty. Im Debug-Fenster ist ein noch offener Request zu sehen, auf dessen Beantwortung der Browser wartet.

Alternativen: Glassfish & Co.

Greg Wilkins und Alex Russell sind nicht die Einzigen, die sich mit asynchroner Verarbeitung bei Servern beschäftigen. Viel versprechend ist auch das Glassfish-Projekt, bei dem Sun einen freien J2EE-Server implementiert [7]. Sun hat in Glassfish einen HTTP-Server namens Grizzly eingebaut, der die asynchrone Java-I/O-Lösung NIO verwendet.

Grizzly lässt sich für die Verarbeitung von asynchronen Anfragen (Asynchronous Request Processing, ARP) konfigurieren. Mittlerweile haben die Entwickler sogar Comet-Support implementiert. Der Glassfish-Entwickler Jean-François Arcand erklärt in seinem Blog, wie man mit Glassfish eine Comet-Anwendung schreiben kann [8].

Das Java-Framework RIFE verfügt zwar bisher nicht über Comet-Support, implementiert aber selbst Continuations, mit denen sich asynchrone Anwendungen schreiben lassen. Auch Tomcat stellt mit den NIO- und APR-Konnektoren die Basis für solche Anwendungen zur Verfügung [9]. Bleiben am Ende noch Active-MQ [10] vom Apache-Projekt und DWR zu erwähnen, die ähnliche Funktionalität bieten, aber in Sachen Protokoll andere Wege gehen als Comet, DWR unter dem Namen Reverse Ajax [11].

Entsprechende Javascript-Klassen vereinfachen bei Active-MQ die asynchrone Kommunikation mit dem Server: Auf Client-Seite kann sich der Webentwickler darauf beschränken, dem Server eine Nachricht zu schicken und einen Event-Handler bereitzustellen.

Comet-Tricks

Es wäre überaschend, wenn die Comet-Methode nicht wieder zu neuen Schwierigkeiten führte. Eine liegt darin, dass die meisten Server nur zwei offene Verbindungen zu einem Client zulassen. Bleibt ein Browser-Request lange offen, kann der Browser keine neuen Verbindungen mehr aufbauen. Ein möglicher Workaround besteht darin, künstliche Subdomains oder Hosts zu verwenden, die zum Beispiel lange andauernde Ajax-Polls bearbeiten. Auch der Internet Explorer macht Schwierigkeiten, indem er die geöffneten Connections mit einem ewig drehenden Icon quittiert, das Benutzer irritiert. Dieses Problem umgehen die Comet-Experten mit eingebetteten unsichtbaren Iframes.

Asynchrone Kommunikation auf der Server-Seite steht trotzdem allerorten auf der Tagesordnung. Von Jetty über Apache und Tomcat bis zu Suns Glassfish implementieren alle in ihren Web- oder J2EE-Servern asynchrone Requests. Server-seitig könnte Comet zum Standard werden, andere Ansätze, die zum Beispiel mit dem Java Messaging Service zusammenarbeiten, sind auch durchaus viel versprechend.

Für die Programmierung am Webserver selbst gibt es noch wenige Vorbilder. Jettys Continuations sind zumindest vom Design her nicht so sauber, wie sein sollten. Dem Programmierer bleibt es weitgehend selbst überlassen, seine Request-Handler den Continuations entsprechend zu schreiben. Jetty-Programmierer Greg Wilkins setzt große Hoffnungen in die kommende Servlet-API-Spezifikation 3.0, wie er in seinem Blog regelmäßig dokumentiert [12]. Eine einheitliche Lösung ist aber bisher nicht in Sicht.

Noch ein Stück

Der paradoxe Kampf der Webentwickler gegen ihre eigenen Standards setzt sich fort. Von neuen Webanwendungen erwarten die Nutzer immer mehr das Verhalten einer Desktop-Applikation, aber den Entwicklern steht das zustandslose, für kurze Anfragen optimierte HTTP-Protokoll im Weg. Asynchrone Verarbeitung auf Client (Ajax) und Server (Comet) könnte dazu beitragen, dem hoch gesteckten Ziel näher zu kommen.

Der asynchrone Comet-Ansatz eignet sich vor allem für Webanwendungen mit vielen Usern, die so gut wie möglich gleichzeitig informiert werden sollen, auch wenn er wie gezeigt eigene Probleme mitbringt. Trotzdem stellen er und verwandte Lösungen einen weiteren Schritt in der Web-Evolution dar.

Infos

[1] Jetty: [http://jetty.mortbay.org]

[2] Greg Wilkins: [http://blogs.webtide.com]

[3] Borges: [http://borges.rubyforge.org]

[4] Jetty-Continuations: [http://docs.codehaus.org/display/JETTY/Continuations]

[5] “Comet: Low Latency Data for the Browser”: [http://alex.dojotoolkit.org/?p=545]

[6] Cometd: [http://cometd.com]

[7] Glassfish: [http://glassfish.dev.java.net]

[8] Comet mit Glassfish: [http://weblogs.java.net/blog/jfarcand/archive/2006/07/the_grizzly_com.html]

[9] Tomcat AIO: [http://tomcat.apache.org/tomcat-6.0-doc/aio.html]

[10] Active-MQ asynchron: [http://incubator.apache.org/activemq/ajax.html]

[11] Reverse Ajax in DWR: [http://getahead.ltd.uk/dwr/changelog/dwr20m1]

[12] Gedanken zur Servlet-API 3.0: [http://blogs.webtide.com/gregw/2006/05/18/1147978560000.html]

LINUX-MAGAZIN KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Readly Logo
E-Mail Benachrichtigung
Benachrichtige mich zu:
0 Kommentare
Älteste
Neuste Beste Bewertung
Inline Feedbacks
Alle Kommentare anzeigen
Nach oben