0%

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:

AnfrageAntwort
GET /books200, {"books": [every book, by id, ascending]}
GET /books/{id}200, das Buch; 404, wenn kein Buch diese id hat
HEAD auf einem von beidender Status und die Header, die GET senden würde, und kein Body
eine andere auf einem von beiden405 mit allow: GET, HEAD
jeder andere Pfad404

Jede Antwort mit Body ist mit einem 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 für "abc" aus, und es akzeptiert auch Text, der hier keine id ist, etwa " 12" mit einem Leerzeichen, "-1" und Ziffern aus anderen Schriftsystemen. Die -Methode isdigit() allein reicht auch nicht:

Try it

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

  1. book_id_from(segment). Gib int(segment) zurück, wenn das Segment aus ASCII-Ziffern besteht, sonst None.

  2. Der Zweig für /books/{id}. Füge in handle einen Zweig für Segmente der Länge zwei hinzu, deren erstes Segment "books" ist. Verwandle das zweite in eine id. Wenn die id None ist oder kein Schlüssel von state["books"], antworte mit 404 über json_error, egal mit welcher Methode: Unter diesem Pfad gibt es nichts, was erlaubt oder abgelehnt werden könnte. Bei GET und HEAD antworte mit 200 über json_response und dem einen Buch, so wie es gespeichert ist.

  3. Auch dort 405. Jede andere Methode auf einem existierenden Buch antwortet mit not_allowed("GET, HEAD"). Deine Regel für HEAD am Ende von handle deckt den neuen Zweig schon ab, weil sie nach dem Routing läuft.

  4. Führe das Szenario aus. Es sendet fünf Anfragen: GET /books, GET /books/2, HEAD /books/2, GET /books/99 und ein POST /books mit leerem Body:

python pl_http.py reading-list

Du solltest die sehen; dann Buch 2, Café Society, mit 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.

  1. Tests, wenn du möchtest. tests/test_server.py enthält schon einen Test, der handle direkt aufruft, über eine Hilfsfunktion make_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 /1.1-Anfrage muss einen Header 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 wird geöffnet.

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): Gib int(segment) zurück, wenn das Segment aus einer oder mehreren ASCII-Ziffern besteht (segment.isascii() und segment.isdigit()), und None für alles andere.

  • Füge handle die Route /books/{id} hinzu: Für eine id, die None ist oder kein Schlüssel von state["books"], antworte mit 404 über json_error, egal mit welcher ; bei GET und HEAD antworte mit 200 und dem Buch, so wie es gespeichert ist; bei jeder anderen Methode antworte mit not_allowed("GET, HEAD").

  • Behalte alles aus den Lektionen 2 bis 5: /books beantwortet GET und HEAD mit der und andere Methoden mit 405, jeder andere Pfad antwortet mit 404, HEAD bekommt Status und Header von GET mit einem leeren Body, und make_server und serve bleiben 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 -Body mit einem 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 zu binden. Deine eigenen Tests und dein Terminal werden nicht bewertet.