Mit Java auf dem HTTP/2-Zug

HTTP/2 löst nun auch in Java den veralteten Vorgänger ab, der aktuellen Anwendungen nicht mehr gerecht wird.

Know-how  –  5 Kommentare
Mit Java auf dem HTTP/2-Zug

Kurz nachdem im September 2017 das lang ersehnte Java Development Kit 9 (JDK9) erschien, hat Oracle die Enterprise-Variante in der aktuellen Version 8 veröffentlicht. Einer der zentralen Punkte des neuen Java EE 8 ist die Servlet-API, die nun erstmals HTTP/2 unterstützt. Version 2 des Hypertext Transfer Protokolls wurde im Mai 2015 als Standard verabschiedet. Das Ziel des neuen HTTP ist, die Bandbreite im World Wide Web besser auszunutzen und die Netzwerk-Latenz zu verringern. Webserver und -browser setzen HTTP/2 seit langem um. Zwei Jahre später springt endlich auch Java EE auf diesen Zug auf.

Java hat im Enterprise-Umfeld eine weite Verbreitung, und viele umfangreiche Applikationen basieren auf HTTP, seien es REST-basierte Microservices, SOAP-Webservices oder Webanwendungen auf der Basis von Servlets, Java Server Pages oder JavaServer Faces.

Somit lohnt sich ein Blick auf die Vorteile von HTTP/2. Der direkte Vorgänger HTTP/1.1 stammt noch aus dem Jahr 1999, und seitdem hat sich das Web deutlich weiterentwickelt. Wer dieser Entwicklung von fast zwei Jahrzehnten Rechnung tragen will, muss auf HTTP/2 umsteigen.

Nur zur Erinnerung: im Jahre 1999 war der bedeutendste Hersteller für Mobiltelefone ein Unternehmen aus Finnland mit dem Namen Nokia. Der Präsident der USA hieß Bill Clinton. Apple lieferte seine Rechner mit fest verbauten Bildröhren aus, und deutsche Kunden bezahlten dafür damals nicht in Euro, sondern in D-Mark. Ein Blick in das Internet-Archiv archive.org bringt die Ausgabe von heise.de aus Abbildung 1 zutage. Die Erinnerungen verdeutlichen das Alter von HTTP/1.1. Das Protokoll entstand für die Anforderungen seiner Zeit – also für Webseiten wie die zu den Anfangszeiten von Heise Online. Für Webseiten wie heute üblich oder für serviceorientierte Anwendungsarchitekturen war es nie ausgelegt.

So sah heise.de am 25. Februar 1999 aus (Abb. 1).

Dieser Artikel stellt deswegen zunächst die einzelnen Neuerungen von HTTP/2 vor und zeigt im Anschluss mit Codebeispielen, wie sich einfache Java-Anwendungen auf Basis von Servlet 4 implementieren lassen. Dabei kommt das Framework Spring Boot in der Version 2.0.0.M6 zum Einsatz, um einen einfachen REST-Webservice und -Client zu bauen. Als Beispiel dient ein Servlet, das eine dynamische Webseite ausliefert. Die Basis bildet der Applikationsserver Glassfish in der Version 5.0.

HTTP/2 nutzt die bestehende Bandbreite bei der Datenübertragung besser aus als seine Vorgänger. Das erreicht der neue Standard durch drei Neuerungen. Zunächst ist das Request-Multiplexing zu nennen. Es sorgt dafür, dass mehrere Request-/Response-Konversationen gleichzeitig über ein und dieselbe TCP-Verbindung erfolgen können. Das Konzept des Server-Push ermöglicht, dass ein HTTP-Client einen HTTP Request in Richtung Server absetzen und Letzterer darauf mit mehr als einer Response antworten kann. Schließlich verwendet HTTP/2 eine verbesserte Implementierung zur Datenkompression speziell für die Protokoll-Metadaten – den HTTP Header.

HTTP/1.1 bietet keine direkte Möglichkeit, Requests zu parallelisieren. Über eine TCP-Verbindung kann jeweils nur ein HTTP-Request und daraufhin nur eine HTTP-Response fließen. Das ist für zeitgemäße Webseiten problematisch. Die Startseiten großer Online-Portale erfordern nicht selten den Transfer mehrerer Megabyte an Daten vom Server an den Client. Um diesen Download in Gang zu setzen und abzuschließen, sind mitunter hunderte von HTTP-Requests notwendig.

Die eingangs genannte Einschränkung durch HTTP/1.1 von genau einer Request-Response-Konversation pro TCP-Verbindung wird zum Nadelöhr. Typische Workarounds waren bisher, dass Webbrowser mehrere TCP-Verbindungen zum Server aufbauen, über die sich parallel mehrere HTTP-Verbindungen abwickeln lassen. Der dadurch entstehende Protokoll-Overhead fällt jedoch vergleichsweise stark ins Gewicht, da jede TCP-Verbindung einen initialen Aufbau erfordert. Abbildung 2 zeigt den notwendigen Nachrichtenaustausch zwischen Client, DNS-Service und Server für einen solchen Verbindungsaufbau.

Austausch von Nachrichten zum Aufbau einer TCP-Verbindung (Abb. 2)

Ein weiterer Workaround war das Verringern von HTTP-Requests durch das Zusammenfassen von Ressourcen. Entwickler kombinierten viele Bilder zu einer großen Grafik und zeigten über CSS-Spriting jeweils einzelne Ausschnitte an. Einzelne CSS- und Javascript-Dateien fassten sie ebenfalls zu größeren Dateien zusammen. Der Nachteil dieser Verfahren ist, dass die einzelnen Ressourcen, also die Bilder, CSS- und Javascript-Dateien, sich nicht mehr separat im Cache des Clients verwahren lassen.

Eine Änderung an einer einzelnen kleinen Bilddatei erfordert einen kompletten Neubau eines Sprites auf der Serverseite. Der Server muss das Sprite anschließend komplett neu an den Client übertragen, obwohl sich nur ein kleiner Teil verändert hat. Dasselbe gilt für zusammengefasste CSS- und JavaScript-Dateien.

Webbrowser-Implementierungen erlauben nur eine begrenzte Anzahl paralleler TCP-Verbindungen zum selben Hostnamen. Je nach Browser ist diese Höchstzahl unterschiedlich, aber meist sind es weniger als zehn. Ist also der Download einer HTML-Datei, mehrere Bilder und weiterer Ressourcen notwendig, umging man die Begrenzung durch das Verteilen der Ressourcen auf mehrere Hostnamen:

  • Die HTML-Datei liegt auf www.site.de. Das ist der Hostname, den der Besucher in der Adresszeile des Browsers eingibt.
  • Bilder bietet der Server img.site.de an. Bei einer großen Zahl an Bildern ist es üblich, weitere Hosts wie img2.site.de und img3.site.de einzusetzen.
  • CSS- und Javascript-Dateien liegen auf static.site.de.

Entwickler und Administratoren betreiben damit einen gewaltigen Aufwand, um Einschränkungen eines Protokolls zu umgehen, das aus dem Jahre 1999 stammt.

Hinzu kommt ein weiteres Problem, das man durchaus als Konstruktionsfehler bezeichnen kann: In HTTP/1.1 lassen sich zwar mehrere Request-Response-Konversation über eine TCP-Verbindung abwickeln, aber nur sequenziell. Die Bezeichnung für dieses Vorgehen lautet HTTP-Pipelining. Es erzwingt, dass der Server über eine TCP-Verbindung zu einem Zeitpunkt nur eine Response an einen Client übertragen kann.

Sendet der Client mehrere GET-Requests über die Pipeline zum Server, kann er die zugehörigen Responses nur sequenziell in der Reihenfolge der ursprünglichen GET-Requests empfangen und verarbeiten. Wenn die Response auf den ersten GET-Request viel Zeit benötigt, blockiert sie alle nachfolgenden Responses, die nicht vor der ersten Response beim Client ankommen können. Das Problem heißt auch "Head-of-Line-Blocking".