Kapitel 12 · Abschlussprojekt
Abschlussprojekt: der Leselisten-Dienst und sein Client
Abschlussprojekt: Den Server fertigstellen
Kapitel 11 hat die Leseliste in requests-Client aus Kapitel 10 zurück und verlangt von beiden, gleichzeitig ein Dokument zu erfüllen: SERVICE_CONTRACT.md.
Der Vertrag ist eingefroren. Nichts in diesem Kapitel ändert darin eine Route, einen Status, einen Header oder einen Exit-Code. Zwei Programme verlassen sich jetzt auf dieselben Worte. Wenn der Server location: /books/3 sendet, macht der Client daraus eine
Den Vertrag lesen, die Lücken finden
Öffne SERVICE_CONTRACT.md im Tab Project und scrolle zu seinem letzten Abschnitt, “Not built yet”. Er führt drei Zeilen auf. Jede Zeile hat eine Server-Hälfte, die du in dieser Lektion wiederherstellst, und eine Client-Hälfte, die die nächsten beiden Lektionen bauen:
| Zeile | Server-Hälfte (diese Lektion) | Client-Hälfte |
|---|---|---|
| Alte Pfade | jede /v0/books und /v0/books/... bekommt eine 308 auf denselben Pfad ohne /v0, mit beibehaltener Query | eine Zeile followed 308 from URL für jede |
| Revalidierung | ein passendes If-None-Match macht aus einer 200 für einen Lesezugriff eine 304 ohne Body | eine Cache-Datei, die show wiederverwendet, solange sie frisch ist, und revalidiert, wenn sie veraltet ist (Lektion 2) |
| Neue Bücher | die 201 für POST /books enthält location: /books/{id} | add gibt die absolute URL des neuen Buchs aus (Lektion 2) |
Keine der Server-Hälften ist neu für dich. Die location-Zeile stammt aus Kapitel 5, legacy_location und sein 308-Zweig aus Kapitel 6, und etag_matches, not_modified und die 304-Regel aus Kapitel 8. Sie wurden aus einem ansonsten fertigen Server herausgenommen, damit die Client-Hälften etwas haben, mit dem sie übereinstimmen können.
Der Rest von reading_server.py ist der geprüfte Server vom Ende von Kapitel 9: read_body, das nach jeder Ablehnung die Verbindung schließt, Login und Sessions, Bearer-Tokens und etag und cache-control bei jeder 200 für einen Lesezugriff. Lass ihn so, wie er ist. Check my work beweist diese Zeilen noch einmal, mit Büchern, Benutzern und Tokens, die du noch nicht gesehen hast, denn eine Änderung an einer Stelle kann eine Zeile weit weg kaputtmachen.
Meilensteine
Zuerst die alten Pfade. Schreibe legacy_location(target): Nimm den Pfad mit urlsplit(target).path, schneide die ersten drei Zeichen ab, /v0, und füge ? und die Query hinzu, wenn es eine gibt. Arbeite mit dem rohen Ziel, nicht mit den decodierten Segmenten, damit der handle hinzu, vor jeder anderen Route: Wenn die ersten beiden Segmente v0 und books sind, ist die Antwort redirect(308, legacy_location(request["target"])). Er kommt zuerst, damit ein Schreibzugriff auf einen alten Pfad weitergeleitet wird, bevor das Token geprüft wird, und nichts gespeichert wird.
Dann die Revalidierung. Schreibe etag_matches(values, etag): values ist die if-none-match-Zeilen, und jede Zeile kann mehrere, durch Kommas getrennte Tags enthalten. Entferne bei jedem Element die Leerzeichen, verwirf ein führendes W/ und gib True für * oder für ein Tag zurück, das gleich etag ist. Schreibe not_modified(response): ein neues Antwort-etag und cache-control der 200 und dem Body b"". Füge dann die Regel zu handle hinzu, nach dem Routing und vor den Regeln für no-store und HEAD: Wenn die Antwort ein etag hat und die Anfrage if-none-match gesendet hat und die beiden passen, wird die Antwort zu not_modified(response). Nur eine 200 für einen Lesezugriff trägt ein etag, also bleiben Fehler und Schreibzugriffe unberührt, und * bei einem fehlenden Buch bekommt weiterhin seine 404.
Dann die Location. Füge in create_book vor dem location auf /books/ und die neue ID gesetzt. Der Client löst diesen relativen Wert in Lektion 2 auf.
Zwei Läufe des Hilfsprogramms aus früheren Kapiteln funktionieren mit diesem Server weiterhin:
python pl_http.py moved
python pl_http.py conditional
Im Lauf moved sollte das Log deines Servers für die beiden /v0-Anfragen 308 zeigen, wo der Server, mit dem du dieses Kapitel begonnen hast, 404 geantwortet hat. Im Lauf conditional sollte das Log 200, 304, 304, 200 lauten: Das erste GET bekommt das Tag, das nächste GET und das HEAD senden es zurück und bekommen 304, und die letzte Anfrage sendet ein veraltetes Tag und bekommt wieder die ganze Liste.
Welche Funktion des Clients hört auf zu funktionieren, wenn die 304-Regel des Servers fehlt?
Deine Tests
tests/test_server.py ruft handle direkt mit make_request auf, wie seit Kapitel 5. Führe es nach jedem Meilenstein aus:
python -m pytest -q
Jeder Test, der mit dem Projekt gekommen ist, sollte weiterhin bestehen. Lektion 4 fügt Tests für die drei Zeilen hinzu.
Deine Aufgabe
Schreibe
legacy_locationund den 308-Zweig ganz oben inhandle.Schreibe
etag_matchesundnot_modifiedund die 304-Regel inhandle.Füge
locationzur 201 increate_bookhinzu.Führe
python -m pytest -qaus, dann die beiden Läufe des Hilfsprogramms.Klicke auf Check my work.
Wenn eine Prüfung fehlschlägt, nennt ihre Meldung die Anfrage und die Regel. Bitte Monty, sie mit dir neben der passenden Tabelle von SERVICE_CONTRACT.md zu lesen, ohne die Korrektur zu schreiben.
Der Server erfüllt jetzt jede Server-Zeile des Vertrags. Der Client lädt immer noch jedes Buch jedes Mal herunter und gibt ein neues Buch ohne seine URL aus; die nächste Lektion behebt beides.
Aufgabe
Stelle die Server-Hälfte von SERVICE_CONTRACT.md fertig: die drei Server-Zeilen unter “Not built yet”.
legacy_location(target): der Pfad des Ziels ohne seine ersten drei Zeichen,/v0, mit?und der Query, wenn es eine gibt. Ganz oben inhandle, vor jeder anderen Route, bekommt ein Pfad, dessen erste beide Segmentev0undbookssind,redirect(308, legacy_location(request["target"])), für jede. etag_matches(values, etag):True, wenn irgendein Element irgendeinerif-none-match-Zeile, ohne umgebende Leerzeichen und ohne führendesW/, gleichetagoder gleich*ist.not_modified(response): eine 304 nur mitetagundcache-controlder 200 und dem Bodyb"". Inhandle, nach dem Routing und vor den Regeln fürno-storeundHEAD, wird eine Antwort mit einemetag, zu dem dasif-none-matchder Anfrage passt, zunot_modified(response).In
create_bookenthält die 201location: /books/{id}.
Behalte jede andere Zeile des Vertrags so bei, wie der Server sie schon erfüllt. Führe im python -m pytest -q, python pl_http.py moved und python pl_http.py conditional aus.
Klicke dann auf Check my work. Es ruft handle mit Büchern, Benutzern und Tokens auf, die du noch nicht gesehen hast (die IDs 7, 41 und 1000, der Benutzer zoë), und prüft: Lesezugriffe, HEAD, 404er und 405 mit allow; dass content-length zu jedem Body passt, anhand der Bytes, die der Handler deines Servers im Speicher schreibt; die location der 201, gegen die GET, HEAD, POST, PUT und DELETE auf /v0-Pfaden mit einer prozentcodierten Query; Login, das HttpOnly-/me und Logout; 401 und 403 für Schreibzugriffe; etag und cache-control mit MAX_AGE auf 45 gesetzt; die 304 für ein Tag, eine W/ und *, endend an der Leerzeile; drei Anfragen über eine Verbindung und eine Ablehnung, die sie schließt; und make_server und serve mit einem Ersatz-Server, der Verbindungen zählt. Nichts öffnet einen