0%

Eén verbinding, veel verzoeken

Drie manieren waarop een body eindigt

In les 1 bleef een geweigerde body op de verbinding staan, en de server las hem als het volgende verzoek. Een client heeft hetzelfde probleem vanaf de andere kant. Op een open gehouden verbinding begint het volgende antwoord direct na de laatste byte van dit antwoord, dus de client moet precies bij die byte stoppen met het lezen van deze body voordat hij de volgende statusregel kan vinden. Dit heb je al eens opgelost: in Networks with Python, hoofdstuk 8, begon elk frame met zijn lengte, en de lezer nam precies zoveel bytes en geen byte meer. heeft drie manieren om te zeggen waar een body eindigt, en één regel voor welke ervan van toepassing is.

De volgorde van de regel

Een client die een antwoord leest, loopt deze af en stopt bij de eerste regel die past (RFC 9112, sectie 6.3):

  1. Helemaal geen body. Een antwoord op HEAD, en elk antwoord met een 1xx-, 204- of 304-status, eindigt bij de lege regel na de headers, wat die headers ook zeggen. Een 304 kan zelfs de content-length dragen van de 200 waarvoor hij staat; dat getal beschrijft de opgeslagen body, niet bytes op deze verbinding.

  2. transfer-encoding: chunked. De body komt in chunks, elk met een eigen grootte; een chunk met grootte nul, gevolgd door een lege regel, beëindigt hem.

  3. content-length. De body is precies zoveel bytes.

  4. Geen van beide. De body is elke byte totdat de server de verbinding sluit.

Een antwoord kan zowel transfer-encoding als content-length bevatten. De RFC beslist dan in het voordeel van transfer-encoding, en voegt eraan toe dat zo’n bericht “ought to be handled as an error”, als fout behandeld zou moeten worden: een client en een proxy die verschillende headers kozen, zouden dezelfde bytes in verschillende berichten knippen. Je client zal het weigeren.

De fetch in je wire.py handelt twee van deze gevallen af: geen body na HEAD, en content-length. Al het andere weigert hij. Les 4 leert hem de rest.

De kop van een antwoord heeft transfer-encoding: chunked en geen content-length. Waar eindigt de body?

Chunks

Chunked framing schrijft de body als een reeks chunks. Elke chunk is een regel met de grootte van de chunk in hexadecimaal, dan precies zoveel bytes aan data, dan CRLF. Na een grootte kan ; en een extensie volgen, die een lezer mag negeren. Een chunk met grootte 0 zegt dat de data op is. Daarna komen trailerregels, die na de body worden gestuurd, meestal geen, en dan één lege regel die het bericht beëindigt.

Hier is de chunked body die een kleine server in pl_http.py stuurt in les 3 en 4, uitgelegd met één regel per rij. De uitleg rechts staat in het Engels: size is de grootte, data de bytes, extension een extensie, zero chunk de chunk met grootte nul, trailer line een trailerregel en empty line de lege regel waarmee het bericht eindigt.

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

Elke rij eindigt met CRLF. Voer dezelfde bytes uit, gesplitst bij elke CRLF:

Try it

De twee lege b''-regels aan het eind komen van de laatste CRLF CRLF. Splitsen bij CRLF werkt hier alleen omdat de data van geen enkele chunk CRLF bevat. Data mag elke byte bevatten, dus een echte decoder leest een grootteregel, dan precies zoveel bytes, en zoekt nooit naar het einde van de data.

De groottes zijn hexadecimaal, en int leest ze als je het grondtal 16 meegeeft:

Wat drukt dit af?

print(int("18", 16), int("18"))

Waarom zou een server chunks verkiezen boven een lengte? Een lengte moet bekend zijn voordat de eerste byte van de body wordt verstuurd. Een server die de body gaandeweg maakt, uit een trage query of terwijl hij een groot bestand comprimeert, kent het totaal nog niet. Met chunks kan hij meteen beginnen met versturen en toch het einde markeren, zodat de verbinding bruikbaar blijft.

Het sluiten

Het laatste punt van de regel vraagt niets van de server behalve dat hij sluit. Het is geldig, en HTTP/1.0-servers vertrouwden er vaak op. De prijs is de verbinding: het sluiten is de enige markering, dus er kan niets na de body komen op die verbinding. Als pl_http zo’n antwoord tegenkomt, zegt het (the reply has no content-length, so pl_http read until the server closed), en op een open gehouden verbinding duurt dat lezen tot de server na 2 seconden zonder verkeer sluit. Een server die verbindingen open houdt, moet dus elke body een content-length of chunked framing geven, en daarom hoefden de antwoorden van je server uit les 1 niet te veranderen.

Een HTTP/1.1-antwoord met een body heeft geen content-length en geen chunked framing. Wat volgt daaruit?

Ook verzoeken worden afgebakend door chunks of door content-length, met één verschil: een verzoek zonder een van beide headers heeft helemaal geen body (RFC 9112, sectie 6.3). De body van een verzoek kan niet bij het sluiten eindigen, want de client heeft de verbinding nog nodig om het antwoord te lezen. Daarom kon je server uit hoofdstuk 5 content-length eisen voor POST en PUT, en elk ander verzoek zonder die header behandelen als een verzoek zonder body.

Een 304 bevat etag en geen content-length. Hoeveel bytes body volgen na de kop?

Twee verwante technieken blijven buiten deze cursus: meerdere verzoeken versturen voordat je een antwoord leest, pipelining genoemd, en zelf chunked bodies versturen. Je client zal chunks decoderen en ze nooit versturen. Les 3 schrijft die decoder.