Eine Verbindung, viele Anfragen · Abschlussprojekt
Abschlussprojekt: Projekt: Jeden Body lesen
Dein Server hält Verbindungen offen, und dein read_chunked decodiert Chunks, aber dein fetch liest immer noch nur zwei Arten von Body: keinen nach HEAD und genau content-length Bytes. Bei allem anderen löst es fetch liest jeden Body nach der Regel aus Lektion 2, und eine neue fetch_many, sendet mehrere Anfragen über eine Verbindung, so wie dein Server sie jetzt erwartet.
Was der Client tun muss
SERVICE_CONTRACT.md führt die Regeln unter “The client in wire.py” auf. Die beiden neuen:
fetchliest einen Body anhand voncontent-length, anhand der Chunks oder bis zum Schließen, und liest keinen nachHEADoder bei einem Status 1xx, 204 oder 304. Es lehnttransfer-encodingzusammen mitcontent-lengthab, ebenso jede Übertragungscodierung außerchunked.fetch_manysendet eine Anfrage nach der anderen über eine Verbindung, bittet bei der letzten um das Schließen und löstConnectionErroraus, wenn der Server die Verbindung zu früh beendet.
Meilensteine
Zuerst read_until_close(reader, *, limit=MAX_BODY). Es gibt die Bytes zurück, die schon im Puffer liegen, und jedes Byte danach, bis recv b"" zurückgibt, und hinterlässt einen leeren Puffer. Auch eine Antwort, die mit dem Schließen endet, bekommt eine Grenze: Löse ValueError aus, sobald die Bytes limit überschreiten, bevor du nach mehr fragst. Etwa sechs Zeilen.
Dann read_body(reader, method, headers, *, status=200). Schreibe es nach der Regel aus Lektion 2 um, in ihrer Reihenfolge:
Kein Body bei
HEAD, bei einem Status unter 200 oder bei 204 oder 304: Gibb""zurück, ohne zu lesen.Mit
transfer-encoding: Wenn auchcontent-lengthvorhanden ist, löseValueErroraus. Der Header muss genau einenenthalten, chunkedin beliebiger Groß- und Kleinschreibung und mit erlaubten Leerzeichen drumherum. Alles andere, etwagzip, chunked, löstValueErroraus, weil dieser Client keine andere Codierung decodiert. Gib dann zurück, wasread_chunkedzurückgibt.Ohne
content-length: Gib zurück, wasread_until_closezurückgibt.Sonst lies wie bisher anhand von
content-length.
Die Regel aus Kapitel 1, die einen Body ohne Länge abgelehnt hat, gibt es nicht mehr. Die Tests, die mit diesem Kapitel in tests/test_wire.py gekommen sind, erwarten sie nicht mehr, und der Docstring oben in dieser Datei sagt das.
Dann fetch. Es übergibt den Status der Antwort: read_body(reader, method, response["headers"], status=response["status"]). Ohne ihn würde eine 304, die eine content-length enthält, fetch auf Bytes warten lassen, die nie kommen.
Führe das Hilfsprogramm aus:
python pl_http.py bodies
Dein fetch fragt den kleinen Server aus Lektion 3 nach /length, /chunked und /close, mit je einer Verbindung. Vor deinen Änderungen endet die Antwort mit Chunked Encoding in raised ValueError: this client cannot measure a body sent with Transfer-Encoding yet. Danach endet jeder der drei Berichte mit einem Body:
body: b'{}' (2 bytes)
body: b'{"books": ["Kindred", "Caf\xc3\xa9"]}' (31 bytes)
body: b'{"ends": "at the close"}' (24 bytes)
Zuletzt fetch_many(base_url, targets, *, connect=socket.create_connection, timeout=TIMEOUT). Es sendet für jedes Ziel der Reihe nach GET base_url + target über eine Verbindung und gibt eine fetch einen zurückgibt:
Öffne die Verbindung einmal,
with connect((host, port), timeout=timeout) as sock:, mit Host undaus base_url(Port 80, wenn sie keinen hat), und erstelle einen Reader für die ganze Verbindung. Bytes der nächsten Antwort können im selbenrecvankommen wie das Ende dieser Antwort, und sie warten im Puffer dieses Readers.Baue jede Anfrage mit
build_request("GET", base_url + target, headers). Die letzte enthält den HeaderConnection: close, damit der Server schließt, sobald jede Antwort angekommen ist. Die anderen brauchen keinenConnection-Header.Sende eine Anfrage, lies ihren Kopf und ihren Body, und sende erst dann die nächste. Alle zu senden, bevor man liest, das sogenannte Pipelining, lässt dieser Kurs beiseite.
Wenn eine Antwort vor dem letzten Ziel
connection: closesagt, liest der Server auf dieser Verbindung nichts mehr. Löse sofortConnectionErroraus, mit einer Meldung, die beide Zahlen enthält, wie viele Ziele beantwortet wurden und wie viele es waren, etwathe server closed the connection after 1 of 3 requests.
Etwa sechzehn Zeilen. Führe es dann gegen deinen eigenen Server aus:
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 gibt die drei Anfragen als eine client -> server-Zeile aus, weil es alles, was eine Verbindung transportiert hat, zusammen zeigt. Das Log deines Servers enthält drei Anfragen, und serve hat eine Verbindung gezählt.
Deine Tests
Du kannst Tests zu tests/test_wire.py hinzufügen, wie in Kapitel 6. fakes.scripted_socket steht für einen stummen Server, der die Verbindung offen hält, wenn sein Skript None enthält, und für einen geschlossenen, wenn das Skript einfach endet. Eine Antwort ohne Länge wird bis zum Schließen gelesen, also endet das Skript eines Tests für eine solche Antwort ohne 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"
Diesmal führt Check my work deine Tests einmal aus, so wie python -m pytest -x es tun würde. Keiner darf fehlschlagen, und keiner darf eine echte Verbindung öffnen. Führe sie zuerst selbst aus:
python -m pytest -q
Was bewiesen wird, und wie
Check my work verbindet deinen Client ohne connect, das es fetch_many übergibt, gibt einen Ersatz-Socket zurück: Was dein Client sendet, geht in diesem Prozess durch deine eigene recv zurück. Sobald jede Antwort gelesen wurde und die Verbindung noch offen ist, löst recv TimeoutError aus. So fallen ein Client, der bis zum Schließen liest, und eine Serverantwort ohne Länge auf. Die Läufe bodies und many sind zum Zuschauen da und werden nicht bewertet.
Klicke dann auf Check my work. Das Framing, die offene Verbindung und den Umgang mit dem Schließen, die requests auf einer Verbindung für dich erledigt, hast du jetzt von Hand geschrieben. Lektion 5 steckt genau diese Bytes in
Aufgabe
Bring wire.py bei, jeden Body zu lesen und mehrere Anfragen über eine Verbindung zu senden.
read_until_close(reader, *, limit=MAX_BODY): Gib die Bytes inreader["buffer"]zurück, gefolgt von jedem Byte, bisrecvb""zurückgibt, und hinterlasse einen leeren Puffer. Löseaus, sobald die Bytes limitüberschreiten, bevor du nach mehr fragst.read_body(reader, method, headers, *, status=200), nach der Regel aus Lektion 2:b""ohne zu lesen beiHEADund bei einem Status unter 200, 204 oder 304; mittransfer-encodingValueError, wenn auchcontent-lengthvorhanden ist oder wenn der Header etwas anderes als der einechunkedist (beliebige Groß- und Kleinschreibung, Leerzeichen erlaubt), sonstread_chunked; ohnecontent-lengthread_until_close; sonst wie bishercontent-length.fetchübergibtstatus=response["status"]anread_body.fetch_many(base_url, targets, *, connect=socket.create_connection, timeout=TIMEOUT): einconnect-Aufruf mit Host undaus base_url(80, wenn sie keinen hat) undtimeout, ein Reader, und für jedes Ziel der Reihe nach einGET, gebaut mitbuild_request("GET", base_url + target, headers), vollständig gelesen, bevor das nächste gesendet wird. Nur die letzte Anfrage enthältConnection: close. Gib die Datensätze zurück, einen pro Ziel, jeder dasparse_head-mit zusätzlichem "body", so wiefetches zurückgibt. Wenn eine Antwort vor dem letzten Zielconnection: closesagt, löseConnectionErroraus, mit einer Meldung, die beide Zahlen enthält, wie viele Ziele beantwortet wurden und wie viele es waren, etwathe server closed the connection after 1 of 3 requests.
Dein Server muss weiterhin read_body ablehnt (Lektion 1). Deine Tests in tests/ müssen bestehen: Führe python -m pytest -q aus. Im python pl_http.py bodies und python pl_http.py many deinen Client auf einer echten Verbindung.
Klicke dann auf Check my work. Es ruft read_body und read_until_close mit Readern um einen Ersatz-HEAD, 204, 304 und 101, bei denen nichts zu lesen ist; Bodys mit Chunked Encoding, mit chunked in verschiedenen Schreibweisen; Längen; Bodys, die mit dem Schließen enden und schon teilweise im Puffer liegen; einen Body über limit; und transfer-encoding mit content-length, gzip, chunked, zwei Werten und identity. Wo eine Verbindung offen bleibt, löst der Ersatz-Socket TimeoutError aus, wenn mehr von ihm verlangt wird, als der Server gesendet hat. Es führt fetch mit einer 304, einer 204, einer Antwort mit Chunked Encoding und einer aus, die mit dem Schließen endet. Es führt fetch_many gegen die HTTP/1.1-Handler-Klasse deines eigenen Servers aus, in diesem Prozess, mit Büchern, die du noch nicht gesehen hast, und prüft die eine Verbindung, die Anfragezeilen, Connection: close nur bei der letzten Anfrage, den Timeout und jeden Body; danach gegen Antworten, die zu früh connection: close sagen. Es steuert deinen Server mit drei Anfragen über eine Verbindung und mit einem abgelehnten Body mit Chunked Encoding. Zuletzt führt es deine Tests einmal aus, hält beim ersten Fehlschlag an, ohne Netzwerk und mit einer Grenze von 3 Sekunden. Nichts aus deinem Terminal wird bewertet.