Java Magazin 10/09

AJAX, JSON, XHR

Autor:

Diesmal verlassen wir unseren „heimischen“ Java- Boden und begeben uns in die Welt von Ajax-Anwendungen. Im letzten Artikel haben wir uns mit generellen serverseitgen Performance- und Skalierbarkeitsproblemen beschäftigt. Die Performance von JavaScript, dessen Konzepte und Ausführungsumgebungen stehen dieses Mal im Vordergrund der Betrachtungen.

Bis vor wenigen Jahren galt JavaScript noch als „Spielerei“ und vor allem durch einige Kompatibilitätsprobleme als wenig wartbar. Unter dem Begriff „Ajax“ gelang dann in den letzten Jahren der Durchbruch für JavaScript. Ein Grund hierfür ist sicherlich der Fortschritt im Umfeld von verfügbaren Bibliotheken und die bessere Unterstützung in allen gängigen Browsern. Zu den bekannteren Frameworks zählen hier z. B. Prototype oder JQuery, die die Browserkompatibilitätsprobleme zentral lösen. In diesem Artikel werden wir auf drei Hauptbereiche von JavaScript-Performance eingehen. Verteilte Kommunikation – ein Hauptbestandteil von Ajax – kann helfen, übertragene Datenmengen zu reduzieren. Gleichzeitig ist Kommunikation in verteilten Systemen auch eine der größten Quellen für Performanceprobleme. Ein zweiter Punkt sind JavaScript-basierte Erweiterungen, die den Browser im Hinblick auf Usability zu den klassischen Rich Clients aufholen lassen. Verschiedene JavaScript-Frameworks bieten mit teilweise vielen Tricks, die Timer, viele DOM-Objekte und viel JavaScript-Code benötigen, schöne visuelle Effekte. So kann es aber sein, dass ein Aufklappeffekt, der die Seiten nur verschönern soll, zu schlechterer Benutzbarkeit führen kann, da er den Arbeitsablauf um Sekunden verzögert. Last but not least werden wir uns dem Thema „Memory Leaks“ zuwenden. Grundsätzlich gilt, dass die Konzepte im JavaScript-Umfeld dem Java-Bereich sehr ähnlich sind. Einige spezielle Probleme ergeben sich hier im Laufzeitverhalten von JavaScript. Hinzu kommt, dass die Garbage Collectors nicht so ausgereift sind wie in Java-Umgebungen. Viele Probleme sind hier auch vom verwendeten Browser abhängig. Glücklicherweise entschärft die korrekte Verwendung eines JavaScript-Frameworks viele dieser Unannehmlichkeiten. Spätestens aber, wenn man selbst beginnt, JavaScript-Code zu schreiben, sollte man sich der möglichen Probleme bewusst sein.

Ajax-Anwendung Bildschirmfoto 2016-02-04 um 16.39.02

Ajax = DOM + JavaScript + XHR

Schauen wir etwas hinter die Kulissen einer modernen Ajax-Webanwendung. Das Herz einer jeden Webseite ist HTML, aber im Ajax-Umfeld spricht man vorrangig vom DOM (Document Object Model). Dieses enthält die hierarchische Struktur der angezeigten Website und ist mit dem Aufbau eines XML vergleichbar. Das lesende und schreibende Bearbeiten des DOM ist ein zentrales Konzept in Ajax. Genau wie bei herkömmlichen Benutzerschnittstellen können hier Controls hinzugefügt oder Event Listener registriert werden. Wie viel vom DOM durch HTML deklarativ und wie viel zur Laufzeit erzeugt wird, hängt stark vom verwendeten Framework ab. Natürlich sind diese Aspekte für die Anwendungsperformance wesentlich.

Neben dem DOM existiert die JavaScript Engine. Diese ist für das Ausführen des Codes verantwortlich und im Unterschied zu einer JVM Single-threaded implementiert. Das bedeutet, dass sich alle Aktionen in der Anwendung einen Thread teilen. Speziell auf die Responsiveness der Anwendung kann das große Auswirkungen haben. Sehr häufig wird mittels der Timer versucht, PseudoparalAjax lelisierung zu erreichen. Bei der Verwendung von Frameworks muss man sich um diese Details oft nicht oder nur am Rande kümmern. Im Interaktionsdesign der Anwendung sollte man aber auf jeden Fall darauf Rücksicht nehmen und zu lange Verarbeitungsroutinen vermeiden. Speziell JavaScript-Anwendungsframeworks wie Google Web Toolkit (GWT) erlauben es, sehr komplexen und langlaufenden Code in JavaScript zu implementieren.

Das XMLHTTPRequest-(XHR-)Objekt ist das Kernstück jeder Ajax-Anwendung. Es erlaubt uns, mit dem Server zu kommunizieren. Die Kommunikation ist asynchron implementiert, beim Absetzen einer Abfrage wird ein Callback registriert, um auf Events wie die verfügbare Antwort reagieren zu können. Bei der Kommunikation im Browser gilt es, eine wichtige Limitierung zu beachten: die Anzahl der verfügbaren Netzwerkverbindungen. Die HTTP/1.1-Spezifikation [1] definiert, dass ein Client nicht mehr als zwei persistente Verbindungen zu einem Server halten soll. Glücklicherweise erlauben fast alle Browser mindestens vier. Diese werden verwendet, um alle Netzwerkanfragen abzuarbeiten, also auch JavaScript-Dateien, Bilder usw. Hier haben wir es mit einem klassischen Bottleneck zu tun. Obwohl uns Frameworks ebenfalls helfen, ist doch Vorsicht geboten. Laden wir z. B. sehr viele große Bilder zur Laufzeit und wollen gleichzeitig Ajax-Abfragen absetzen, wird sich das massiv auf die Performance unserer Anwendung auswirken. Welche Limitierungen in speziellen Browsern bestehen, lässt sich sehr einfach mittels des UA-Profilers [2] testen. Da die Verbindungen pro Host begrenzt sind, sollte man daher erwägen, Bilder auf einen zweiten Host auszulagern.

Der nächste Teil einer Ajax-Anwendung sind CSS-Stylesheets. In vielen Fällen sind diese nicht performancerelevant, speziell aber, wenn diese komplexe, relative Positionierungsanweisungen enthalten, kann das zu langen Renderzeiten führen. Andere Ressourcen und Bilder sind in Bezug auf Größe und Downloadhäufigkeit performancerelevant.

Ajax – Nomen est omen?

Vertraut man dem Namen, so handelt es sich bei Ajax – Asynchronous JavaScript and XML – um ein Protokoll zur asynchronen, verteilten Kommunikation mittels XML. Ajax ist allerdings kein Protokoll, sondern beschreibt das Konzept der Kommunikation. Als Transportmedium wird fast ausschließlich HTTP verwendet. Alternativen wie WebSockets sind aber schon am Horizont zu sehen. Zum Beispiel bietet Kaazing [3] schon eine WebSockets-Implementierung an. Das X in „Ajax“ steht entgegen vieler Erwartungen sehr oft nicht für XML. JSON (JavaScript Object Notation) ist bequemer zu nutzen als XML und wird deshalb auch häufiger verwendet. Da JSON bereits gültigen JavaScript-Code darstellt, kann er einfach mittels eines eval-Aufrufs ausgeführt werden. Vereinfacht handelt es sich dabei um in JavaScript definierte Objekt-Arrays oder -Maps aus Name und Wert. Durch den Einsatz einer JSON-fähigen Bibliothek wie Jettison [4] oder JSONTools [5] können JavaScript-Objekte einfach in Java geschrieben und gelesen werden. Damit sind wir aber noch nicht am Ende der Möglichkeiten. Die Antwort eines Ajax-Aufrufs kann beliebiger Text sein (letztendlich ist XML und JSON ja auch genau das). Einige Frameworks schicken auch direkt HTML-Code als Antwort zurück. Dieser wird dann über das DOM-API mittels innerHtml direkt gesetzt. Das macht dann Sinn, wenn XML sowieso wieder in HTML umgewandelt werden soll. Ein Beispiel wären Kontendaten in XML, die dann in einer HTML-Tabelle angezeigt werden. Hier kann serverseitig sofort HTML generiert werden. Einige mögen jetzt aufschreien, dass damit eine Trennung à la Model View Controller verletzt wird, das ist dann eben Geschmackssache. Für manche JavaScript-Entwickler ist das ein sehr pragmatischer Weg, HTML auszutauschen, und abhängig vom verwendeten Framework ist die Einflussnahme des Programmierers nur bedingt möglich.
Zuletzt kann man die Antwort als ausführbaren JavaScript-Code senden. Warum das Sinn macht? Nehmen wir als Beispiel eine Wettanwendung. Während aktuelle Spiele konstant bleiben, werden sich die Quoten laufend ändern. Egal, ob wir XML oder HTML verwenden, um Quoten zu aktualisieren, wir senden viel zu viele Daten. Pro Spiel ändert sich nur die Quote, die anderen Werte bleiben gleich. Mittels JavaScript kann man hier Code zurückschicken, der genau nur diese Werte ändert. Man kann das Ganze natürlich auch mit JSON und JavaScript realisieren, der Unterschied liegt lediglich in der Verantwortlichkeit für die Daten. In dem einen Fall wird die Anweisung, was mit den Daten zu tun ist, gleich mit gesendet, im anderen Fall enthält die Ursprungsseite Code, der mit den Rohdaten umgehen kann. Um herauszufinden, welche Daten verschickt werden, kann man einerseits auf Diagnosewerkzeuge zurückgreifen oder einen einfachen HTTP Proxy wie Fiddler [6] verwenden.

Who Bayeux … so you are french?

Webanwendungen basieren auf dem Request/Response Pattern von HTTP. Das gibt dem Server keine Möglichkeit, Daten an den Client zu schicken, wenn dieser sie nicht zuvor angefordert hat. Das Bayeux-Protokoll [7] bietet hierfür eine Antwort, die auch als Server-Push-Kommunikation bekannt ist. Das Bayeux-Protokoll schreibt die Implementierung mittels so genanntem „Long-Polling“ vor. Das bedeutet ein HTTP-Request wird vom Client abgesetzt, der Server wartet dann so lange, bis er diese Anfrage beantworten kann und sendet dann die Antwort. Auf diese Weise können sehr einfach Chat- oder Ticker-Anwendungen realisiert werden. Mit einem Konzept ähnlich zu JMS Topics kann zusätzlich ein Publish-/Subscribe-Protokoll implementiert werden. Serverimplementierungen müssen, um eine effiziente Implementierung zu gewährleisten, mittels Non-blocking IO (NIO) realisiert werden. Dabei wird die Anwendung mittels Events benachrichtigt, wenn neue Daten bereitstehen. Hierfür existieren Implementierungen in Tomcat 6, Jetty sowie kommerziellen Server von Oracle und IBM. Ein Beispiel der serverseitigen Implementierung findet sich in der Tomcat-Dokumentation [8]. Wie schon Eingangs angesprochen, ist die Anzahl der maximalen Verbindungen pro Host in Browsern beschränkt. Wie viele Verbindungen tatsächlich gehalten werden können, hängt aber nicht nur vom Server, sondern auch von eventuellen Proxies ab. Es empfiehlt sich also, von maximal zwei Verbindungen auszugehen. Das bedeutet, dass mit Verbindungen sehr sparsam umgegangen werden muss, da ansonsten keine weiteren Daten mehr geladen werden können. Als generelles Designkonzept sollte hier „Open late – close early“ verwendet werden.

Ich sehe was, was du nicht siehst

Was sieht man als Anwender einer Webanwendung? Eine grafische Darstellung der Objekte im DOM, die anhand Layoutanweisung von der Rendering Engine erstellt wird. Doch der DOM enthält bei JavaScript-Anwendungen wesentlich mehr, als man sieht. Neben den JavaScript-Knoten, die durch die JavaScript Engine gelesen werden müssen, finden sich viele versteckte Elemente, die z. B. für Effekte genutzt werden. Dabei ist es prinzipiell egal, ob der DOM-Baum durch die initial geladenen Daten komplex ist oder erst durch das Einfügen mittels Ajax nachgeladener Daten komplex gemacht wird. In JavaScript verwendet man CSS-Selektoren, um das Element im Baum zu finden, mit dem man arbeiten möchte. Bei großen DOM-Bäumen benötigen diese aber für komplexere Operationen entsprechend mehr Zeit. Zwar versuchen alle JavaScript-Frameworks diese Selektoren zu optimieren, jedoch bergen Selektoren wie Finde alle Absätze in Tabellenzellen im Content-Bereich, die auf die erste rote Überschrift folgen (#content td:fist-child h1[class~=“red“]+p) potenziell Performancegefahren und sind möglichst zu vereinfachen. Mehr Informationen über die möglichen CSS-Selektoren finden sich in der Dokumentation des jeweiligen Frameworks oder in der Spezifikation des W3C [9]. Doch noch mehr Fallen lauern in großen DOM-Bäumen. So führt jede Manipulation zu Veränderungen, die neu gerendert werden müssen. Je nach verwendetem CSS kann es sogar erforderlich sein, den Inhalt des gesamten DOM-Baums neu zu rendern, wenn z. B. die Position und Größe eines Elements stark von den umgebenden Elementen abhängt. Werden nicht nur Elemente hinzugefügt oder gelöscht, sondern Inhalte mittels innerHtml geschrieben, so ist sogar ein erneutes Parsing des resultierenden HTMLs notwendig.

Memory Leaks – auch im Browser

Früher, als JavaScript wenig genutzt wurde, waren die Engines gut genug. Durch die intensivere Nutzung ergeben sich neue Anforderungen. Das zeigt auch die Entwicklung der V8 Engine in Google Chrome, die neben Performance viele andere Aspekte behandelt. Ein gutes Beispiel hierfür ist der Garbage Collector. JavaScript Garbage Collectors sind, wie bereits erwähnt, ihrem Äquivalent in Java technisch noch unterlegen. Das führt dazu, dass teilweise auch auf Grund der Implementierung Memory Leaks entstehen können. Das wohl bekannteste Beispiel ist ein Memory Leak im Internet Explorer, älter als Version 8. Hier wird Reference Counting als Garbage-Collection-Strategie verwendet. Dieser Ansatz hat das Problem, zirkuläre Referenzen – also Referenzen, die letztendlich wieder zum Ausgangsobjekt zurückführen – nicht auflösen zu können. Ein spezielles Problem waren hier Referenzen aus der JavaScript-Welt in die DOM-Welt und zurück. Diese können sehr einfach entstehen, wenn eine JavaScript-Funktion als EventHandler auf einen DOM-Knoten registriert wird und selbst eine Referenz auf diesen hält. Dieses Problem kann in der Praxis oft einfach umgangen werden [10].

Wenn man Einfluss auf den verwendeten Browser hat, z. B. bei Intranetanwendungen, sollte man diesen entsprechend dieser Probleme auswählen. Plant man also, eine Client/Serveranwendung durch eine Ajax-Anwendung zu ersetzen, so ist auch die Auswahl des Browsers Teil der Entwicklungsaufgaben. Da mit der zunehmenden Verbreitung von Ajax hier immer potenzielle neue Probleme auftreten können, empfiehlt es sich, Anwendungen immer auf Memory Leaks in allen verwendeten Browsern zu testen.

Don’t Closure your Eyes!

Eine spezielle Form vom Memory Leaks sind Leaks auf Grund falsch verwendeter Closures. Diese sind spezifisch für JavaScript, da dieses Konzept in Java noch nicht existiert – eine Implementierung in Java gibt es voraussichtlich mit Java 7 [11]. Jede Funktion in JavaScript verfügt über einen Execution Scope, dieser beinhaltet alle Variablen der aufrufenden Methoden und den eigenen. Soweit unterscheidet sich das Modell nicht von Java. Sehen wir uns aber das Beispiel an:

function doStuff (AJAX response){
var myElems = // newly created Element in dom
for … // iterate over elements
myElem.onClick= function (){
// do something if someone clicks
}
}

Dieser Code sieht eigentlich ganz in Ordnung aus, das Problem ist aber, dass der Execution Scope unserer anonymen doClick-Funktion eine Referenz auf die Variablen von doStuff hält. Das inkludiert auf die Ajax Response und schlimmer noch das Array aller erzeugten DOM-Elemente. Solange auch nur ein Element noch existiert und der Even Handler definiert bleibt, wird hier kein Speicher mehr freigegeben. Dieses Problem lässt sich aber einfach lösen. Wir definieren die Funktion vorab und nicht mehr inline. Dadurch lösen wir unser erstes Scope-Problem. Zweitens rufen wir explizit ein delete auf myElems auf (oder setzen es auf null). Dadurch wird das Array und die Referenzen freigegeben.

Pseudo-Leaks

Ein Pseudo-Memory-Leak sind falsch eingesetzte CSS Sprites [12]. Das Konzept hinter CSS-Sprites stammt aus den Urzeiten der Spieleentwicklung: Anstelle viele Bilder einzeln zu laden, wird ein großes zusammengesetztes Bild geladen und der relevante Ausschnitt gezeigt. Durch die Bildkomprimierung ist das zusammengesetzte Bild häufig sogar kleiner als die Summe der Einzelbilder. Das Problem ist nur, dass es Sprites gibt, von denen ein Großteil nicht benutzt wird, weil die Fläche frei bleibt oder die dort gespeicherten Bilder nicht genutzt werden. Der Browser muss aber das gesamte Bild zur Anzeige im Speicher halten und zwar in der anzeigbaren, unkomprimierten Form mit 3 + 1 Byte pro Bildpunkt.

Ein weiteres Pseudo-Leak tritt bei einigen Server-side-Push-Frameworks auf. Wie schon beschrieben, senden diese permanent Daten an den Client. Einige Implementierungen verwenden hierzu versteckte Frames und der Server verwendet Partial-Content-Antworten, d. h. er sendet von Zeit zu Zeit neue Daten, oft JavaScript. Die Seite des Frames wächst also permanent weiter, je mehr Daten gesendet werden. Läuft eine Anwendung einen ganzen Arbeitstag lang, kann dies schon zu massiven Memory Leaks führen. Die Anwendung muss dann natürlich neu gestartet werden. Neuere Frameworks bieten hierfür hoffentlich passende Lösungen an. Speziell wenn man am Beginn eines Projekts steht, sollten man sich mit dem Wie von Push-Ansätzen auseinandersetzen.

Antipattern – Zu viele Daten

XHR sollte nur die benötigten Daten transportieren. Auch wenn auf der Java-Seite Serialisierung in XML schnell genug sein sollte, ist die Verarbeitung in JavaScript wesentlich langsamer. Durch die Verwendung von JSON ist die Deserialisierung in JavaScript schneller und einfacher. Zusätzlich spart man bei der Serialisierung Zeit, wenn nur die wirklich verwendeten Attribute von Objekten serialisiert werden. Besonders gut lässt sich XHR betrachten und die mit ihnen transportierten Daten in Firebug. Übrigens gilt das „zu viele Daten“-Antipattern auch auf dem Hinweg. Die HTTP-Spezifikation fordert, dass vorhandene Cookies bei jedem Request mitgesendet werden müssen. Das ist auch bei Ajax Requests der Fall, auch wenn die Cookies für die Bearbeitung gar nicht notwendig sind. Sogar bei Requests für Bilder oder CSS werden Cookies mitgeschickt, weswegen auch die Yahoo Performance Rules empfehlen, für diese Requests eine Domain oder Subdomain zu verwenden, in der keine Cookies verwendet und damit auch nicht übertragen werden.

Antipattern – Alles oder … alles

Ajax ist sehr gut geeignet, um Daten dynamisch nachzuladen, jedoch ist das richtig zu machen. Eine Tabelle mittels eines JavaScript Widgets leer anzuzeigen und 1000 Zeilen dann per Ajax nachzuladen, funktioniert zwar, führt aber mit Sicherheit zu Performanceproblemen. Sinnvoll wäre es „n+2 Lazy Loading“ anzuwenden. Beim initialen Anzeigen der Seite ist der sichtbare Teil der Tabelle (z. B. 10 Einträge) bereits gerendert. Ein Satz weiterer Einträge (in diesem Fall also weitere 10) befindet sich ebenfalls auf der Seite, wird aber durch die Tabelle nicht angezeigt. Blättert oder scrollt der Benutzer nun zum zweiten Satz, kann man mittels Ajax den nächsten bereits nachladen, aber weiter versteckt halten. Für Datensätze, durch die der Benutzer schneller durchblättert, kann entsprechend weiter vorgeladen werden. Dabei ist auch darauf zu achten, alte Sätze wieder aus der Seite zu entfernen. Dieses intelligentere Lazy Loading löst auch das Problem der fachlichen Synchronität, da der Benutzer nicht auf seine Aktion warten muss. Andere Bezeichnungen sind auch „Prefetching“ oder „Preloading“. Yahoo nutzt eine ähnliche Strategie, um z. B. Bilder und CSS der Suchergebnisseite zu laden, nachdem der Benutzer mit dem Eingeben des Suchbegriffs angefangen hat [13].

Antipattern – Interaktivitätsüberdosis

Ein oft angeführter Anwendungsfall für JavaScript und Ajax ist das Laden von Autovervollständigungen. Diese sollten aber nicht bei jedem keyUp-Event vom Server nachgeladen werden, sondern idealerweise erst 200 – 300 ms, nachdem der Benutzer aufgehört hat, zu tippen. Insbesondere problematisch ist, dass die Reihenfolge der Antworten nicht bestimmbar ist. So könnten die Vorschläge für zwei getippte Buchstaben nach denen für drei kommen. Einige Frameworks stellen zur Synchronisation mehrerer Anfragen bereits Lösungen zur Verfügung, so gibt es in RichFaces die Ajax Event Queue oder in JQuery das Ajax Queue Plug-in. Für häufig genutzte Felder ist auch ein Hybrid-Ansatz möglich: Eine Auswahl der häufigsten Autovervollständigungen muss nicht nachgeladen werden, sondern ist direkt vorrätig und kann direkt angezeigt werden. Eine weitere Ursache für starke Auslastung der CPU können viele Timer sein. Da JavaScript in nur einem Thread ausgeführt wird, werden viele Timer eingesetzt, um Nebenläufigkeit zu produzieren. Viele verschiedene Timer erzeugen aber einen großen Overhead. Die JavaScript Engine kann durch die Verwendung weniger einheitlicher Timer, die dann für verschiedene Zwecke genutzt werden, wesentlich entlastet werden.

Antipattern – Unterschätzen der Netzwerkslatenz

Verteilte Kommunikation ist eines der zentralen Gründe für Performance- und Skalierbarkeitsprobleme. Im Ajax-Umfeld muss umso mehr auf effiziente Kommunikation geachtet werden. Der Roundtrip eines Requests startet im Browser des Benutzers, passiert verschiedenste Netzwerke, Proxies und Gateways und landet erst dann beim Server, der die Antwort auf ähnlichem Weg zurücksendet. Zwar werden die Requests – technisch gesehen – asynchron ausgeführt, von der Benutzersicht her ist das aber häufig ein synchroner Vorgang, auf dessen Ergebnis gewartet werden muss. Die Verwendung von Ajax ändert die fachliche Synchronität nicht. Wie im Umfeld von Remoting bereits angesprochen, gibt es einige Dinge, die für effiziente Netzwerkkommunikation beachtet werden müssen. Ein wesentlicher Punkt ist hierbei die übertragene Datenmenge. Diese sollte so gering wie möglich gehalten werden. Hierbei ist auch auf der Serverseite darauf zu achten, dass Datenmengen effizient eingeschränkt werden können. Das kann bei Abfragen z. B. durch die Angabe von Anzahl der Datensätze bzw. Offsets in Daten erreicht werden.

Abhängig vom Inhalt, sollten Ajax-Anfragen, wenn möglich, auch gecached werden – idealerweise natürlich schon im Browser. Das ist aber leider nicht so trivial. Browser cachen häufig zu viel und verhindern identische Anfragen an den Server. Frameworks haben daher ausgereifte Mechanismen, um Caching zu verhindern, damit Anfragen tatsächlich immer bis zum Server durchdringen. Es empfiehlt sich, in der Dokumentation des eingesetzten Frameworks die Caching-Optionen nachzulesen.

Fazit

Performanceprobleme existieren in Ajax-Anwendungen, genauso wie in „herkömmlichen“ Webanwendungen. Die Problemquellen liegen in der Kommunikation und Datenübertragung, komplexen DOM-Interaktionen und Speicherproblemen. Auch die Browser selbst kämpfen oft noch mit Performanceproblemen, jedoch entwickeln sich diese und ihre Engines rasant voran. Deshalb lohnen sich Mikrooptimierungen für Anwendungsentwickler im Browser nicht. Die richtigen Patterns und Konzepte schützen davor, in klassische Rich Client Antipattern zu fallen. Im Fall von Performanceproblemen kann man auch auf eine Reihe von frei verfügbaren Tools wie YSlow oder Firebug zurückgreifen, die zwar nicht erlauben, das Laufzeitverhalten genau zu messen, aber dennoch viele brauchbare Kennwerte liefern. Speziell für Performanceoptimierung und für den Internet Explorer bietet dynaTrace mit Browser Diagnostics ein frei verfügbares Werkzeug an.

Links & Literatur

[1] HTTP/1.1 RFC 2616: http://www.ietf.org/rfc/rfc2616.txt
[2] UA-Profiler: http://stevesouders.com/ua
[3] Kaazing Enterprise Gateway: http://www.kaazing.com
[4] Jettison-JSON-StAX-Implementierung: http://jettison.codehaus.org
[5] JSON-Tools: http://jsontools.berlios.de
[6] Fiddler Proxy: http://www.fiddlertool.com
[7] Das Bayeux-Protokoll: http://svn.cometd.org/trunk/bayeux/bayeux.html
[8] Tomcat 6 Comet Support: http://tomcat.apache.org/tomcat-6.0-doc/aio.html
[9] W3C Selector Specifiication: http://www.w3.org/TR/CSS2/selector.html
[10] Understanding and Solving IE-Leak-Patterns: http://msdn.microsoft.com/en-us/library/bb250448%28VS.85%29.aspx
[11] A Re-Introduction to JavaScript: https://developer.mozilla.org/en/A_re-introduction_to_JavaScript
[12] Erläuterung zu CSS-Sprites: http://www.alistapart.com/articles/sprites
[13] Yahoo Contextual Precaching: http://ajaxian.com/archives/yahoo-search
contextual-precaching
[14] dynaTrace Browser Diagnostics: http://browserdiagnostics.dynatrace.com

Vollständiger Artikel