0%

Eén verbinding, veel verzoeken · eindproject

Eindproject: Project: elke body lezen

Je server houdt verbindingen open, en je read_chunked decodeert chunks, maar je fetch leest nog steeds maar twee soorten body: geen na HEAD, en precies content-length bytes. Al het andere laat hem opwerpen. Dit project maakt de client af: fetch leest elke body volgens de regel uit les 2, en een nieuwe , fetch_many, stuurt meerdere verzoeken over één verbinding, zoals je server ze nu verwacht.

Wat de client moet doen

SERVICE_CONTRACT.md noemt de regels onder “The client in wire.py”. De twee nieuwe:

  • fetch leest een body via content-length, via chunks of tot het sluiten, en leest geen body na HEAD of bij een 1xx-, 204- of 304-status. Hij weigert transfer-encoding samen met content-length, en elke transfer coding behalve chunked.

  • fetch_many stuurt één verzoek tegelijk over één verbinding, vraagt bij het laatste om te sluiten, en werpt ConnectionError op als de server de verbinding te vroeg beëindigt.

Mijlpalen

Eerst read_until_close(reader, *, limit=MAX_BODY). Die geeft de bytes terug die al in de buffer staan en elke byte daarna, totdat recv b"" teruggeeft, en laat de buffer leeg achter. Ook een antwoord dat bij het sluiten eindigt, krijgt een limiet: werp ValueError op zodra de bytes limit overschrijden, voordat je om meer vraagt. Ongeveer zes regels.

Dan read_body(reader, method, headers, *, status=200). Herschrijf hem volgens de regel uit les 2, in die volgorde:

  1. Geen body voor HEAD, voor een status onder 200, of voor 204 of 304: geef b"" terug zonder te lezen.

  2. Met transfer-encoding: als content-length er ook is, werp dan ValueError op. De header moet precies één bevatten, chunked in willekeurige hoofd- en kleine letters en met spaties eromheen toegestaan; al het andere, zoals gzip, chunked, werpt ValueError op, omdat deze client geen andere codering decodeert. Geef daarna terug wat read_chunked teruggeeft.

  3. Zonder content-length: geef terug wat read_until_close teruggeeft.

  4. Lees anders via content-length, zoals eerder.

De regel uit hoofdstuk 1 die een body zonder lengte weigerde, is verdwenen. De tests die bij dit hoofdstuk in tests/test_wire.py zijn meegekomen, verwachten hem niet meer, en de docstring bovenaan dat bestand zegt dat.

Dan fetch. Die geeft de status van het antwoord door: read_body(reader, method, response["headers"], status=response["status"]). Zonder die status zou een 304 met een content-length fetch laten wachten op bytes die nooit komen.

Voer de helper uit:

python pl_http.py bodies

Je fetch vraagt de kleine server uit les 3 om /length, /chunked en /close, elk over een eigen verbinding. Vóór je wijzigingen eindigt het chunked antwoord in raised ValueError: this client cannot measure a body sent with Transfer-Encoding yet. Erna eindigt elk van de drie rapporten in een body:

body: b'{}'  (2 bytes)
body: b'{"books": ["Kindred", "Caf\xc3\xa9"]}'  (31 bytes)
body: b'{"ends": "at the close"}'  (24 bytes)

Als laatste fetch_many(base_url, targets, *, connect=socket.create_connection, timeout=TIMEOUT). Die stuurt GET base_url + target voor elk doel, op volgorde, over één verbinding, en geeft een records terug zoals het record dat fetch teruggeeft:

  • Open de verbinding één keer, with connect((host, port), timeout=timeout) as sock:, met de host en van base_url (poort 80 als die er geen heeft), en maak één reader voor de hele verbinding. Bytes van het volgende antwoord kunnen in dezelfde recv aankomen als het einde van dit antwoord, en die wachten in de buffer van die reader.

  • Bouw elk verzoek met build_request("GET", base_url + target, headers). Het laatste bevat de header Connection: close, zodat de server sluit zodra elk antwoord is aangekomen; de andere hebben geen Connection-header nodig.

  • Stuur een verzoek, lees de kop en de body ervan, en stuur pas dan het volgende. Ze allemaal versturen voordat je leest, pipelining genoemd, laat deze cursus met .

  • Als een antwoord vóór het laatste doel connection: close zegt, leest de server niets meer op deze verbinding. Werp dan meteen ConnectionError op, met een melding die beide getallen bevat, hoeveel doelen er zijn beantwoord en hoeveel het er waren, zoals the server closed the connection after 1 of 3 requests.

Ongeveer zestien regels. Voer het daarna uit tegen je eigen server:

python pl_http.py many
statuses: [200, 200, 200]
/me: b'{"user": null}'  (14 bytes)
serve() returned after 1 connection; the server is closed

pl_http drukt de drie verzoeken af als één client -> server-regel, omdat het alles wat een verbinding droeg samen toont. Het log van je server bevat drie verzoeken, en serve telde één verbinding.

Je tests

Je kunt tests toevoegen aan tests/test_wire.py, zoals in hoofdstuk 6. fakes.scripted_socket staat voor een zwijgende server die de verbinding open houdt als het script None bevat, en voor een gesloten server als het script gewoon ophoudt. Een antwoord zonder lengte wordt gelezen tot het sluiten, dus een test van zo’n antwoord eindigt zijn script zonder None:

def test_a_body_that_ends_at_the_close():
    sock = scripted_socket([b"HTTP/1.1 200 OK\r\n\r\nab", b"c"])
    response = wire.fetch("GET", ITEM_URL, connect=fake_connect(sock, []))
    assert response["body"] == b"abc"

Deze keer voert Check my work je tests één keer uit, zoals python -m pytest -x dat zou doen. Geen enkele mag falen, en geen enkele mag een echte verbinding openen. Voer ze eerst zelf uit:

python -m pytest -q

Wat er bewezen wordt, en hoe

Check my work koppelt je client aan je server zonder . De connect die hij aan fetch_many geeft, geeft een vervangende socket terug: wat je client verstuurt, gaat in dit proces door je eigen /1.1-handlerklasse, en wat je server schrijft, komt terug uit recv. Zodra elk antwoord is gelezen en de verbinding nog open is, werpt recv TimeoutError op, en dat betrapt een client die tot het sluiten leest en een serverantwoord zonder lengte. De runs bodies en many zijn er om naar te kijken en worden niet beoordeeld.

Druk daarna op Check my work. De framing, keep-alive en het afhandelen van het sluiten die requests op een verbinding voor je doet, heb je nu met de hand geschreven. Les 5 zet dezelfde bytes in .

Opdracht

Leer wire.py elke body te lezen en meerdere verzoeken over één verbinding te sturen.

  1. read_until_close(reader, *, limit=MAX_BODY): geef de bytes in reader["buffer"] terug, gevolgd door elke byte totdat recv b"" teruggeeft, en laat de buffer leeg achter. Werp op zodra de bytes limit overschrijden, voordat je om meer vraagt.

  2. read_body(reader, method, headers, *, status=200), volgens de regel uit les 2: b"" zonder te lezen voor HEAD en voor een status onder 200, 204 of 304; met transfer-encoding ValueError als content-length er ook is of als de header iets anders is dan één chunked (hoofd- en kleine letters maken niet uit, spaties toegestaan), anders read_chunked; zonder content-length read_until_close; anders content-length zoals eerder.

  3. fetch geeft status=response["status"] door aan read_body.

  4. fetch_many(base_url, targets, *, connect=socket.create_connection, timeout=TIMEOUT): één connect-aanroep met de host en van base_url (80 als die er geen heeft) en timeout, één reader, en voor elk doel op volgorde een GET gebouwd met build_request("GET", base_url + target, headers), volledig gelezen voordat het volgende wordt verstuurd. Alleen het laatste verzoek bevat Connection: close. Geef de records terug, één per doel, elk de parse_head- met "body" erbij, zoals fetch ze teruggeeft. Als een antwoord vóór het laatste doel connection: close zegt, werp dan ConnectionError op met een melding die beide getallen bevat, hoeveel doelen er zijn beantwoord en hoeveel het er waren, zoals the server closed the connection after 1 of 3 requests.

Je server moet nog steeds /1.1 spreken en sluiten na elke body die read_body weigert (les 1). Je tests in tests/ moeten slagen: voer python -m pytest -q uit. In de tonen python pl_http.py bodies en python pl_http.py many je client op een echte verbinding.

Druk daarna op Check my work. Die roept read_body en read_until_close aan met readers rond een vervangende en bodies die je niet hebt gezien: antwoorden op HEAD, 204, 304 en 101 zonder iets om te lezen; chunked bodies met chunked in verschillende schrijfwijzen; lengtes; bodies die bij het sluiten eindigen en al deels in de buffer staan; een body boven limit; en transfer-encoding met content-length, gzip, chunked, twee waarden en identity. Waar een verbinding open blijft, werpt de vervangende socket TimeoutError op als er meer van wordt gevraagd dan de server stuurde. Hij voert fetch uit op een 304, een 204, een chunked antwoord en een antwoord dat bij het sluiten eindigt. Hij voert fetch_many uit tegen de HTTP/1.1-handlerklasse van je eigen server, in dit proces, met boeken die je niet hebt gezien, en controleert één verbinding, de verzoekregels, Connection: close alleen op het laatste verzoek, de time-out en elke body; daarna tegen antwoorden die te vroeg connection: close zeggen. Hij stuurt je server drie verzoeken over één verbinding en een geweigerde chunked body. Als laatste voert hij je tests één keer uit, en stopt bij de eerste fout, zonder netwerk en met een limiet van 3 seconden. Niets uit je terminal wordt beoordeeld.