Start - Publikationen - Wissen - TOGAF - Impressum -

Content Length und Chunked Encoding


HTTP-Nachrichten bestehen aus einem Header und, optional, einem Body. Der Header wird Zeile für Zeile ausgewertet und gilt als abgearbeitet, wenn die erste Leerzeile gelesen wird. Der Body ist dafür designed, jede Art von Informationen aufnehmen zu können. Hier müssen sich Sender und Empfänger darüber einigen, wie lang der Body ist. Das geschieht mit entsprechenden Angaben im Header der HTTP-Nachricht:

  1. Der Content-Length Header ist gesetzt und teilt dem Empfänger mit, nach wieviel Bytes die Nachricht zu Ende ist. Für HTTP1.0 Clients ist die Angabe der Content-Length sogar Pflicht. Das Problem: bei dynamischen Inhalten ist im Allgemeinen die Content-Length erst nach der Erstellung der Inhalte bekannt. Einige Responsetypen (zum Beispiel 304) haben per Spezifikation keinen Inhalt, hier ist die Content-Length implizit mit '0' gegeben und muss normalerweise nicht manipuliert werden.
  2. Seit HTTP1.1 kann der Server dem Client Inhalte unterteilt in Pakete (Chunks) gegebener Größe zuschicken. Dieses Verfahren wird als chunked Encoding bezeichnet und eine solche Übertragung wird mit den Header Transfer-Encoding: chunked eingeleitet. Sind sämtliche Inhalte übertragen, schickt der Server einen Chunk mit dem Header Chunked-Body: last-chunk. Im Vergleich zur Angabe der Content-Length hat dieses Verfahren offensichtlich einen kleinen Overhead, da die Response zerstückelt geschickt wird. Dennoch schont dieses Verfahren die Ressourcen des Servers, da ein Response (zur Ermittlung seiner Content-Length) nicht erst im Speicher komplett erstellt werden muss, bevor er zum Client geschickt wird.
Daneben kann der Server dem Client durch Beenden der TCP-Connection signalisieren, dass die Nachricht zu Ende ist. Da jedoch sehr wahrscheinlich ist, dass derselbe Client sofort weitere Ressourcen vom Server laden wird, Bilder, JavaScript, CSS oder eine andere Seite wegen eines Redirects, gilt dieses Verfahren als unvorteilhaft. Server halten TCP-Connection deshalb gewöhnlich offen. Technisch wird das ganze über den Connection: keep-alive Header geregelt und mir ist kein Szenario bekannt, bei dem dieses Verfahren nicht sinnvoll ist. Übrigens: der Client kann dem Server NICHT durch Schließen der Connection das Ende einer Nachricht signalisieren, denn er bekäme dann keine Antwort mehr.

Servlets


Die Servlet-Spezifikation versucht, die Ermittlung der Content-Length respektive die Angaben beim chunked Encoding zu automatisieren. Eine ServletResponse Implementierung schreibt gewöhnlich die Ausgaben nicht sofort zum Client, sondern zunächst in einen internen Puffer. Sind die Ausgaben abgeschlossen bevor der Puffer voll ist, dann wird die Content-Length einfach am Puffer gelesen und der Header gesetzt bevor der Inhalt des Puffers zum Client geschickt wird. Sind die Ausgaben nicht abgeschlossen, bevor der Puffer zum ersten Mal voll ist, dann kommt automatisch Chunked Encoding zum Einsatz und der Inhalt der Puffers wird sooft zum Client "geflushed", bis die Nachricht vollständig übertragen ist.

Dieses Standardverhalten kann beeinflusst werden:

  • Inhalte werden programmatisch geflushed (writer.flush() wird gerufen). Der Automatismus zur Ermittlung der Content-Length kann nicht mehr zur Anwendung kommen und es wird immer Chunked Encoding eingesetzt, auch für kurze Inhalte.
  • Die Content-Length wird explizit vorgegeben:
    response.setContentLength(500) 
    //oder gleichwertig: 
    response.setHeader("Content-Length", "500");
    
    Damit wird chunked Encoding verhindert. Die Content-Length muss jedoch exakt sein. Wird mehr Inhalt geschickt als angegeben, werden diese vom Client gewöhnlich ignoriert, es fehlt ein Teil der Nachricht. Werden weniger Inhalte zum Client geschickt, dann wartet der Client ewig auf das Ende der Response. Das Ladesymbol am Browser des Nutzers dreht sich dann ewig.
Mit dieser Analyse lassen folgende Regeln formulieren:
  1. Kein programmatisches Flushen der Inhalte! writer.flush() bedingt chunked Encoding, auch wenn die Inhalte der Nachricht kurz sind. Es sollte normalerweise der HttpServlet-Implementierung von der jedes Servlet erbt überlassen werden. Sie versucht standardmäßig für Inhalte die in den ServletResponse-Puffer passen, die Content-Length zu ermitteln. Das gleiche bewirkt writer.close() und sollte daher auch unterlassen werden.
  2. Wenn die Content-Length der Antwort exakt und ressourcenschonend ermittelt werden kann wird sie gesetzt. Eine Bestimmung der Content-Length über eigene, in memory-buffer ist dabei gewöhnlich kontraproduktiv: im Vergleich zum chunked Encoding viel zu aufwendig.
  3. Wenn die exakte Content-Length der Antwort nicht ohne Aufwand ermittelbar ist, jedoch eine verlässliche Obergrenze für die Länge der Antwort bekannt ist, dann kann das Setzen der Größe des ServletResponse-Puffers
    response.setBufferSize(...);
    
    eine Optimierung bedeuten. Auch für lange Antworten wird dann die Content-Length automatisch gesetzt.
  4. Regel 1 sollten Sie immer befolgen - auch wenn es ungewöhnlich scheint, eine Stream-Ressource zu akquirieren, ohne sie explizit zu schließen. Hier muss man sich einfach klar machen, dass nach Verlassen der service Methode solche Dinge automatisiert durchgeführt werden. Die Regeln 2 und 3 stellen Mikrooptimierungen dar, die in den meisten Fällen den Aufwand nicht lohnen und mehr Schaden als Nutzen anrichten können.

JSP


Aus JSP Seiten werden Servlets generiert, auf die das gerade Beschriebene (mit einiger Sicherheit) zutrifft. Dabei fordert die Spezifikation, dass nicht direkt auf den ServletResponse geschrieben wird, sondern in eine JspWriter Instanz, die mit dem PrintWriter des ServletResponse assoziiert ist. Der JspWriter arbeitet intern mit einem Puffer, der 8kb groß ist und mit der Page-Directive

<%@ page buffer="..kb" %>
exlipzit gesetzt werden kann. Die Directive
<%@ page autoFlush="true|false" %>
steuert dabei was passiert, wenn der Puffer voll ist. Bei true, dem Standardwert, wird der Inhalt des Puffers in den PrintWriter des ServletResponse geschrieben und der Puffer kann wieder befüllt werden. Bei false muss das Flushen programmatisch geschehen, bevor der Puffer voll ist - es kommt andernfalls zu einem Überlauf und damit zu einem Laufzeitfehler.
<%@ page buffer="none" %> 
schaltet den Puffer aus und jedes geschriebene Byte wird direkt an den PrintWriter geschickt.

Daraus folgen die Regeln für Optimierungsmaßnahmen mit JSP:

  1. Programmatisches Flushen out.flush bedingt auch hier chunked Encoding und sollte vermieden werden. Das heißt auch, dass autoFlush='false' nicht ohne besonderen Grund gesetzt werden darf.
  2. Kann eine obere Grenze für die Länge der Inhalte einer JSP ermittelt werden, so kann das Setzen der JspWriter Puffergröße eine Optimierung bedeuten. Das generierte Servlet wird diese Puffergröße übernehmen und hat damit die Möglichkeit die Content-Length der potenziell langen Antwort zu ermitteln.
Regel 1 befolgen Sie einfach immer. Regel 2 ist eine Mikrooptimierung, der Aufwand wird sich selten lohnen.

Fazit


Das Ergebnis dieser Analyse ist ernüchternd:

  • Überlassen Sie das Flushen oder das Schließen der Writerinstanzen der Implementierung in GenericServlet.
  • Mit HTTP1.1 setzen Sie explizit niemals die Content-Length: es kommt automatisch chunked Encoding zum Einsatz wenn der Responsepuffer überläuft. Sparen Sie sich die Zeit, mit der Puffergröße zu experimentieren - in den meisten Fällen lohnt der Aufwand nicht!
  • Mit HTTP1.0 setzen Sie, wenn einfach ermittelbar, die Content-Length explizit, oder aber die Puffergröße derart groß, dass die gesamte Response reinpasst - die Content-Length wird dann automatisch gesetzt.

Literatur


Hypertext Transfer Protocol -- HTTP/1.0
Hypertext Transfer Protocol -- HTTP/1.1

copyright © 2003-2021 | Dr. Christian Dürr | prozesse-und-systeme.de | all rights reserved