HTTP selbst ausliefern · Abschlussprojekt
Abschlussprojekt: Projekt: eine schreibgeschützte Leseliste
Dein Server listet die Bücher auf, lehnt ab, was er nicht kann, und beantwortet HEAD so, wie er GET beantwortet. Ein Client kann aber noch nicht nach einem einzelnen Buch fragen. Dieses Projekt fügt diese Route hinzu und stellt die schreibgeschützte Leseliste fertig, die SERVICE_CONTRACT.md in deinem Arbeitsbereich beschreibt.
Was der Dienst verspricht
Öffne SERVICE_CONTRACT.md. Seine Tabelle ist die Anforderung:
| Anfrage | Antwort |
|---|---|
GET /books | 200, {"books": [every book, by id, ascending]} |
GET /books/{id} | 200, das Buch; 404, wenn kein Buch diese id hat |
HEAD auf einem von beiden | der Status und die Header, die GET senden würde, und kein Body |
| eine andere | 405 mit allow: GET, HEAD |
| jeder andere Pfad | 404 |
Jede Antwort mit Body ist content-length, das Bytes zählt, und jeder Fehler-Body ist {"error": "..."}. Die erste, dritte, vierte und fünfte Zeile gelten für /books schon. Die neue Arbeit ist die zweite Zeile, plus die Zeilen für HEAD und 405 bei /books/{id}. {id} steht für ein Pfadsegment: /books/2 fragt nach dem Buch mit der id 2.
Eine id besteht aus Ziffern
Das Segment kommt als Text an, "2", und die Schlüssel von state["books"] sind ints, also würde "2" dort nie gefunden. book_id_from(segment) verwandelt ein Segment in den int, den es nennt, oder gibt None zurück, wenn es keine id nennt. int(segment) allein reicht nicht: Es löst "abc" aus, und es akzeptiert auch Text, der hier keine id ist, etwa " 12" mit einem Leerzeichen, "-1" und Ziffern aus anderen Schriftsystemen. Die isdigit() allein reicht auch nicht:
Die hochgestellte Zwei und die arabisch-indischen Ziffern für zwölf zählen für isdigit() beide als Ziffern. int macht aus den zweiten 12 und löst für die erste ValueError aus. Beide können deinen Server erreichen: Ein Client sendet %C2%B2 oder %D9%A1%D9%A2 im Pfad, und path_segments dekodiert sie. Eine id ist in diesem Dienst eine oder mehrere ASCII-Ziffern und nichts sonst, also prüfe zuerst segment.isascii() and segment.isdigit() und rufe erst dann int auf. Ein leeres Segment besteht keine der beiden Prüfungen, also bekommt /books/ weiterhin ein 404.
Ob 007 Buch 7 nennt, entscheidest du; int("007") ist 7, und dieses Projekt legt sich in keine Richtung fest.
Meilensteine
book_id_from(segment). Gibint(segment)zurück, wenn das Segment aus ASCII-Ziffern besteht, sonstNone.Der Zweig für
/books/{id}. Füge inhandleeinen Zweig für Segmente der Länge zwei hinzu, deren erstes Segment"books"ist. Verwandle das zweite in eine id. Wenn die idNoneist oder kein Schlüssel vonstate["books"], antworte mit 404 überjson_error, egal mit welcher Methode: Unter diesem Pfad gibt es nichts, was erlaubt oder abgelehnt werden könnte. BeiGETundHEADantworte mit 200 überjson_responseund dem einen Buch, so wie es gespeichert ist.Auch dort 405. Jede andere Methode auf einem existierenden Buch antwortet mit
not_allowed("GET, HEAD"). Deine Regel fürHEADam Ende vonhandledeckt den neuen Zweig schon ab, weil sie nach dem Routing läuft.Führe das Szenario aus. Es sendet fünf Anfragen:
GET /books,GET /books/2,HEAD /books/2,GET /books/99und einPOST /booksmit leerem Body:
python pl_http.py reading-list
Du solltest die content-length: 57; dann denselben Kopf für HEAD /books/2 mit nichts nach der Leerzeile; dann ein 404 mit deinem Fehler-Body; dann ein 405 mit allow: GET, HEAD. Der Ergebnisblock endet mit serve() returned after 5 connections; the server is closed.
Tests, wenn du möchtest.
tests/test_server.pyenthält schon einen Test, derhandledirekt aufruft, über eine Hilfsfunktionmake_request(method, target). Du kannst auf dieselbe Weise eigene hinzufügen und sie alle im Tab Shell ausführen:
def test_unknown_book_is_404():
"""GET /books/99 answers 404 with an error body."""
response = reading_server.handle(make_request("GET", "/books/99"), reading_server.sample_state())
assert response["status"] == 404
assert "error" in json.loads(response["body"].decode("utf-8"))
python -m pytest -q
Diese Tests sind für dich; Check my work führt sie nicht aus. Kapitel 5 bittet dich, Tests zu schreiben, die eine Eigenschaft des Servers beweisen, und bewertet diese.
Was dieser Server nicht prüft
Eine Host tragen, und ein HTTP/1.1-Server muss eine Anfrage ohne ihn mit 400 Bad Request beantworten (RFC 9112, Abschnitt 3.2). Dieser Kurs lässt diese Prüfung bei der Leseliste weg. Alles andere im Vertrag erfüllt der Server jetzt.
Wenn du Check my work drückst, wird das ganze Kapitel noch einmal mit eigenen Büchern, ids und Pfaden geprüft, und dein handle läuft außerdem in der echten Adapter-Klasse mit Anfrage-Bytes, die im Speicher gehalten werden, sodass die Prüfung die Bytes liest, die ein Client empfangen würde: die Statuszeile, ein content-length, das gleich dem Body ist, der darauf folgt, nichts nach der Leerzeile bei HEAD und eine Zeile allow bei einem 405. Kein
Die Leseliste kann auf jede Art gelesen werden, die HTTP erlaubt, und auf keine geändert: Es gibt keine Möglichkeit, ein Buch hinzuzufügen. Kapitel 5 liest Anfrage-Bodys und macht die Liste mit POST, PUT und DELETE beschreibbar.
Aufgabe
Stelle in reading_server.py die schreibgeschützte Leseliste fertig, so wie SERVICE_CONTRACT.md sie beschreibt.
Schreibe
book_id_from(segment): Gibint(segment)zurück, wenn das Segment aus einer oder mehreren ASCII-Ziffern besteht (segment.isascii()undsegment.isdigit()), undNonefür alles andere.Füge
handledie Route/books/{id}hinzu: Für eine id, dieNoneist oder kein Schlüssel vonstate["books"], antworte mit 404 überjson_error, egal mit welcher; bei GETundHEADantworte mit 200 und dem Buch, so wie es gespeichert ist; bei jeder anderen Methode antworte mitnot_allowed("GET, HEAD").Behalte alles aus den Lektionen 2 bis 5:
/booksbeantwortetGETundHEADmit derund andere Methoden mit 405, jeder andere Pfad antwortet mit 404, HEADbekommt Status und Header vonGETmit einem leeren Body, undmake_serverundservebleiben so, wie du sie geschrieben hast.
Führe im python pl_http.py reading-list aus. Tests in tests/test_server.py zu schreiben und python -m pytest -q auszuführen ist optional.
Drücke dann Check my work. Die Prüfung wiederholt die Prüfungen der Lektionen 2 bis 5 mit Büchern und Pfaden, die du nicht gesehen hast. Sie ruft book_id_from mit ASCII-Ziffern auf und mit Text, der keine id ist, etwa "-1", "1.5", "²", " 12" und arabisch-indischen Ziffern. Sie ruft handle für existierende und fehlende ids auf, für Segmente, die keine ids sind, für HEAD auf jeder Route und für PUT, POST und DELETE auf beiden Pfaden, und erwartet 200, 404 und 405 mit allow, das GET und HEAD nennt, und einen content-length, das Bytes zählt, bei jeder Antwort mit Body. Dann führt sie dein handle in der Handler-Klasse von http_adapter auf Anfrage-Bytes aus, die im Speicher gehalten werden, und liest die zurückgeschriebenen Bytes: eine Statuszeile 200 und ein content-length gleich dem Body für ein Buch, nichts nach der Leerzeile bei HEAD und eine Zeile allow bei einem 405. Sie prüft make_server und serve so, wie Lektion 3 es getan hat, ohne einen