0%

HTTP selbst ausliefern · Übung

Ein Handler ist eine Funktion

Der Adapter ist bereit, handle(request, state) für jede Anfrage aufzurufen, und handle löst noch NotImplementedError aus. Diese Lektion schreibt es, zusammen mit einer Hilfsfunktion, die -Antworten baut. Weil handle ein nimmt und ein Dict zurückgibt, kannst du es prüfen, indem du es aufrufst, ohne dass ein Server läuft, so wie du in Python in der Praxis getestet und in Networks with Python deinem Code Fake- übergeben hast. Server und Socket kommen in der nächsten Lektion.

Das Antwort-Dict

Öffne reading_server.py. Ein Antwort-Dict hat drei Schlüssel:

  • "status": der als int, etwa 200.

  • "headers": ein Dict vom Headernamen auf den . Schreibe die Namen klein, etwa "content-type". vergleicht Headernamen ohne Rücksicht auf Groß- und Kleinschreibung, also bedeuten content-type und Content-Type für jeden Client dasselbe; HTTP/2 verlangt sogar Kleinschreibung. Die Werte sind .

  • "body": Bytes, weil Bytes das sind, was auf den Socket geht.

handle bekommt die Bücher aus seinem zweiten , state, nicht aus einem Dict, das oben in der Datei gespeichert ist. sample_state() baut die beiden Bücher, die du im sehen wirst, aber ein Test kann jeden beliebigen State übergeben und sich danach genau diesen State ansehen. Nichts, wovon die Funktion abhängt, ist in einem Namen auf Modulebene versteckt, und nichts, was sie ändert, kann von einem Test zum nächsten überleben, außer der Test hat es übergeben.

Warum nimmt handle den state, statt ein Dict auf Modulebene zu lesen?

Bytes zählen, nicht Zeichen

json.dumps(data) macht aus Daten JSON-Text, und .encode("utf-8") macht aus diesem Text Bytes. Übergib ensure_ascii=False, um Zeichen wie é so zu behalten, wie sie sind; ohne das schreibt json.dumps das é als die sechs Zeichen \u00e9, was ebenfalls gültiges JSON ist, nur auf der Leitung schwerer zu lesen:

Try it

Der Text hat 25 Zeichen, und der Body hat 26 Bytes, weil é in zwei Bytes braucht. Content-Length zählt Bytes, genau wie damals, als build_request es in Kapitel 2 für einen Anfrage-Body gesetzt hat. Nimm die Länge des kodierten Bodys, nie die des Texts.

Was gibt das aus?

print(len("Café Society"), len("Café Society".encode("utf-8")))

Der Content-Type ist application/json, ohne Parameter charset: JSON ist auf der Leitung immer UTF-8, und die Definition von application/json hat keinen solchen Parameter. practice-api hat trotzdem einen hinzugefügt; Clients ignorieren ihn.

Keine Länge, kein Ende

Ein Client liest den Kopf bis zur Leerzeile, und dann muss er wissen, wie viele Body-Bytes folgen. content-length sagt es ihm. Ohne diesen Header kann er das Ende nur finden, indem er liest, bis der Server die Verbindung schließt. Unter HTTP/1.0, das der Adapter spricht, schließt der Server nach jeder Antwort, also lässt sich das Ende auf diese Weise trotzdem finden, und http.client liest eine solche Antwort bis zum Schließen. Dein wire.py aus Kapitel 1 lehnt sie stattdessen ab: Sein read_body löst aus, wenn kein Content-Length angekommen ist. In Kapitel 9 bleibt die Verbindung nach einer Antwort offen, und eine Antwort ohne Länge lässt den Client auf ein Schließen warten, das nie kommt. Sende die Länge bei jeder Antwort, die einen Body hat.

Deine Aufgabe

  1. Schreibe json_response(status, data). Baue den Body mit json.dumps(data, ensure_ascii=False).encode("utf-8") und gib ein Antwort-Dict mit diesem Status zurück, mit den Headern "content-type" (die Konstante JSON_TYPE, schon oben in der Datei) und "content-length" (str(len(body))) und mit dem Body.

  2. Schreibe die erste Version von handle(request, state). Baue eine aller Bücher in state["books"], aufsteigend nach id, und gib json_response(200, {"books": books}) zurück. SERVICE_CONTRACT.md verspricht diese Reihenfolge. Der State hält seine Bücher schon so, weil jedes neue Buch die nächste, höhere id bekommt und ein Dict seine Schlüssel in der Reihenfolge behält, in der sie hinzugefügt wurden; eine for- über state["books"] trifft sie also aufsteigend nach id. Vorerst ignoriert handle die Anfrage: Jede und jedes Ziel bekommt die Liste. Lektion 4 bringt ihm bei, nein zu sagen.

Prüfe es im Tab Shell. main() ruft handle für drei Ziele auf und gibt jedes Antwort-Dict aus, immer noch ohne Server und ohne Socket:

python reading_server.py

Du solltest drei Zeilen sehen, für /books, /books/2 und /nowhere, jede mit Status 200, deinen beiden Headern mit content-length '132' und demselben Body. Im Body erscheint é als \xc3\xa9, seine beiden UTF-8-Bytes. Alle drei Ziele bekommen die ganze Liste, weil handle sich das Ziel noch nicht ansieht.

Drücke dann Check my work. Als Nächstes erreicht zum ersten Mal eine echte Verbindung handle.

Aufgabe

Schreibe zwei in reading_server.py.

  • json_response(status, data) gibt ein Antwort- zurück: "status" ist der übergebene Status, "body" ist json.dumps(data, ensure_ascii=False).encode("utf-8"), und "headers" enthält "content-type" mit dem JSON_TYPE und "content-length" mit dem Wert str(len(body)), der Länge des Bodys in Bytes.

  • handle(request, state) gibt json_response(200, {"books": books}) zurück, wobei books alle Bücher in state["books"] aufsteigend nach id auflistet. Vorerst beantwortet es jede Anfrage so, egal mit welcher oder welchem Ziel. Es liest die Bücher aus state und ändert nichts daran.

Führe im python reading_server.py aus, um zu sehen, was handle für drei Ziele zurückgibt.

Drücke dann Check my work. Die Prüfung ruft json_response mit Status und Daten auf, die du nicht gesehen hast, darunter Text außerhalb von ASCII, und prüft den Status, dass der Body aus Bytes besteht, die diese Daten als - enthalten, die Auszeichnung application/json und dass content-length gleich der Länge des Bodys in Bytes ist. Sie ruft handle für GET /books mit eigenen States auf, mit drei Büchern, zwei Büchern und keinem, und prüft den Status, die Header, den Body {"books": [...]} und dass der State danach unverändert ist. Nichts aus deinem Terminal wird bewertet, und kein Server wird gestartet.