Eine Verbindung, viele Anfragen
Drei Arten, wie ein Body endet
In Lektion 1 blieb ein abgelehnter Body auf der Verbindung liegen, und der Server las ihn als nächste Anfrage. Ein Client hat dasselbe Problem von der anderen Seite. Auf einer offen gehaltenen Verbindung beginnt die nächste Antwort direkt nach dem letzten Byte dieser Antwort, also muss der Client genau an diesem Byte aufhören, diesen Body zu lesen, bevor er die nächste Statuszeile finden kann. Das hast du schon einmal gelöst: In Networks with Python, Kapitel 8, begann jeder Frame mit seiner Länge, und der Reader nahm genau so viele Bytes und kein einziges mehr.
Die Reihenfolge der Regel
Ein Client, der eine Antwort liest, geht diese
Gar kein Body. Eine Antwort auf
HEADund jede Antwort mit dem Status 1xx, 204 oder 304 endet an der Leerzeile nach ihren Headern, egal was diese Header sagen. Eine 304 darf sogar diecontent-lengthder 200 tragen, für die sie steht. Diese Zahl beschreibt den gespeicherten Body, nicht Bytes auf dieser Verbindung.transfer-encoding: chunked. Der Body kommt in Chunks an, jeder mit seiner eigenen Größe. Ein Chunk der Größe null, gefolgt von einer leeren Zeile, beendet ihn.content-length. Der Body ist genau so viele Bytes lang.Keins von beiden. Der Body besteht aus jedem Byte, bis der Server die Verbindung schließt.
Eine Antwort kann sowohl transfer-encoding als auch content-length enthalten. Der RFC entscheidet das zugunsten von transfer-encoding und fügt hinzu, dass eine solche Nachricht “ought to be handled as an error”, also als Fehler behandelt werden sollte: Ein Client und ein Proxy, die sich für verschiedene Header entscheiden, würden dieselben Bytes in verschiedene Nachrichten zerschneiden. Dein Client wird so eine Antwort ablehnen.
Das fetch in deiner wire.py behandelt zwei dieser Fälle: keinen Body nach HEAD und content-length. Alles andere lehnt es ab. Lektion 4 bringt ihm den Rest bei.
Ein Antwortkopf enthält transfer-encoding: chunked und keine content-length. Wo endet der Body?
Chunks
Das Chunked-Framing schreibt den Body als eine Folge von Chunks. Jeder Chunk ist eine Zeile mit der Größe des Chunks in Hexadezimalschreibweise, dann genau so viele Bytes Daten, dann CRLF. Auf eine Größe können ; und eine Erweiterung folgen, die ein Leser ignorieren darf. Ein Chunk der Größe 0 sagt, dass die Daten zu Ende sind. Danach kommen Trailer-Zeilen, also Header-Zeilen, die nach dem Body gesendet werden, meistens keine, und dann eine leere Zeile, die die Nachricht beendet.
Hier ist der Body mit Chunked Encoding, den ein kleiner Server in pl_http.py in den Lektionen 3 und 4 sendet, mit einer Zeile pro Reihe:
7 size: 7 bytes
{"books 7 bytes of data
18;note=x size: hex 18 is 24 bytes; ;note=x is an extension
": ["Kindred", "Café"]} 24 bytes of data (é is two bytes in UTF-8)
0 the zero chunk: no more data
X-Checksum: abc a trailer line
the empty line: the message ends here
Jede Reihe endet mit CRLF. Führe dieselben Bytes aus, an jedem CRLF geteilt:
Die beiden leeren b''-Zeilen am Ende kommen vom abschließenden CRLF CRLF. Das Teilen an CRLF funktioniert hier nur, weil die Daten keines Chunks CRLF enthalten. Daten dürfen beliebige Bytes enthalten, deshalb liest ein echter Decoder eine Größenzeile, dann genau so viele Bytes, und bestimmt das Ende der Daten nie durch Suchen.
Die Größen sind hexadezimal, und int liest sie, wenn du ihm die Basis 16 gibst:
Was gibt das aus?
print(int("18", 16), int("18"))
Warum würde ein Server Chunks statt einer Länge wählen? Eine Länge muss bekannt sein, bevor das erste Byte des Bodys gesendet wird. Ein Server, der den Body nach und nach erzeugt, aus einer langsamen Abfrage oder während er eine große Datei komprimiert, kennt die Gesamtlänge noch nicht. Mit Chunks kann er sofort mit dem Senden beginnen und trotzdem das Ende markieren, sodass die Verbindung nutzbar bleibt.
Das Schließen
Der letzte Punkt der Regel braucht vom Server nichts als ein Schließen. Er ist gültig, und HTTP/1.0-Server haben sich oft darauf verlassen. Er kostet die Verbindung: Das Schließen ist die einzige Markierung, also kann auf dieser Verbindung nichts mehr nach dem Body kommen. Wenn pl_http auf so eine Antwort trifft, sagt es (the reply has no content-length, so pl_http read until the server closed), und auf einer offen gehaltenen Verbindung dauert dieses Lesen bis zum Schließen des Servers nach 2 Sekunden Leerlauf. Ein Server, der Verbindungen offen hält, muss also jedem Body eine content-length oder Chunked-Framing geben. Deshalb brauchte dein Server aus Lektion 1 keine Änderung an seinen Antworten.
Eine HTTP/1.1-Antwort mit Body hat weder content-length noch Chunked-Framing. Was folgt daraus?
Auch Anfragen werden mit Chunks oder mit content-length gerahmt, mit einem Unterschied: Eine Anfrage ohne einen der beiden Header hat gar keinen Body (RFC 9112, Abschnitt 6.3). Ein Anfrage-Body kann nicht mit dem Schließen enden, weil der Client die Verbindung noch braucht, um die Antwort zu lesen. Deshalb konnte dein Server aus Kapitel 5 bei POST und PUT auf content-length bestehen und jede andere Anfrage ohne diesen Header so behandeln, als hätte sie keinen Body.
Eine 304 enthält etag und keine content-length. Wie viele Body-Bytes folgen auf ihren Kopf?
Zwei verwandte Techniken bleiben außerhalb dieses Kurses: mehrere Anfragen zu senden, bevor man eine Antwort liest, das sogenannte Pipelining, und selbst Bodys mit Chunked Encoding zu senden. Dein Client wird Chunks decodieren und nie welche senden. Lektion 3 schreibt diesen Decoder.