Anfrage-Bodys und eine JSON-API · Übung
Lehne fehlerhafte Anfragen nach Ursache ab
Deine Schreibzugriffe funktionieren für gültige Bücher, und nur für diese. Sende die fehlerhaften Anfragen des Hilfsprogramms und sieh zu, was passiert:
python pl_http.py bad-requests
Die erste Anfrage ist POST /books mit dem Body title=Dune, gekennzeichnet als text/plain. handle ruft create_book auf, das json_body aufruft, das eine (no bytes: the server closed the connection without answering), und der Log-Block enthält einen json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0) endet. Wenn handle eine Ausnahme auslöst, gibt http.server den Traceback aus und schließt die Verbindung ohne Antwort, also erfährt der Client nichts. pl_http hört nach dieser Anfrage auf zu senden und sagt das.
Eine data["title"] scheitern; ein Buch mit leerem Titel würde gespeichert. Jede fehlerhafte Anfrage braucht einen Status, der benennt, was falsch ist, und keine davon darf den Zustand verändern.
Status nach Ursache
SERVICE_CONTRACT.md listet diese Prüfungen auf, und die erste, die fehlschlägt, entscheidet die Antwort. Hier sind sie in der Reihenfolge, in der der Server sie ausführt:
| Prüfung | Schlägt fehl, wenn | Status | Wer prüft |
|---|---|---|---|
| Rahmung | keine Länge bei POST oder PUT; eine fehlerhafte Länge oder ein zu kurzer Body; zu lang | 411; 400; 413 | read_body |
| Route und | kein solcher Pfad, oder der Pfad nimmt diese Methode nicht an | 404, 405 | handle |
| Buch-ID | kein Buch hat diese ID | 404 | handle |
| Medientyp | content-type ist nicht application/json | 415 | body_error |
| Syntax | die Bytes sind kein | 400 | body_error |
| Form und | kein title und author | 422 | book_error |
Eine Anfrage, die jede Zeile besteht, erreicht den Schreibzugriff und bekommt 201, 200 oder 204. Zwei Zeilen brauchen ein Wort zu ihrem Platz. Die Rahmung kommt vor der Route, weil read_body im Adapter läuft, bevor handle irgendetwas sieht: Deshalb bekommt ein POST /nowhere ohne Länge 411, nicht 404. Und die Buch-ID kommt vor dem Body, also ist ein PUT an ein Buch, das nicht existiert, eine 404, egal was sein Body sagt. Verfolge im Diagramm eine fehlerhafte Anfrage die Prüfungen hinunter, bis sie rechts hinausgeht:

400 oder 422
Eine 400 und eine 422 können beide auf einen fehlerhaften Body antworten; der Unterschied ist, wie weit der Server gekommen ist. 400 Bad Request sagt, dass die Anfrage überhaupt nicht gelesen werden konnte: Diese Bytes lassen sich nicht als JSON parsen. 422 Unprocessable Content sagt, dass der Server sie problemlos gelesen hat und nicht tun kann, was sie verlangt (RFC 9110, Abschnitt 15.5.21): Das JSON ist gültig, aber es ist eine Liste, wo ein Objekt hingehört, oder sein Titel ist 7, oder sein Autor besteht aus drei Leerzeichen.
Die Form zu prüfen heißt, isinstance(value, dict) fragt, ob ein Wert ein isinstance(value, str) fragt, ob er ein String ist. Ein Titel, der leer ist, sobald strip() die Leerzeichen entfernt hat, ist so leer wie "".
Was gibt das aus?
print(isinstance([1, 2], dict), isinstance({"title": 3}["title"], str))
PUT /books/7 (Buch 7 existiert) trägt {"title": "", "author": "Ana Ruiz"}, gekennzeichnet als application/json, mit korrekter Länge. Welcher Status?
Erst prüfen, dann ändern
book_error(data) prüft einen dekodierten Wert: kein Dictionary, ein fehlendes title oder author oder eins davon, das kein nicht leerer String ist, bekommt jeweils eine 422, deren Fehler das Problem benennt. Schreib jede Meldung für die Person, die die Antwort liest: author is missing sagt ihr, was sie ändern muss, und bad request nicht. Du kannst title oder author zuerst prüfen; so oder so wird das erste gefundene Problem gemeldet. book_request_error(request) führt die ganze Body-Prüfung aus: zuerst body_error, und nur wenn das nichts findet, book_error(json_body(request)). Jede gibt None oder die zu sendende Antwort zurück, wie read_body und body_error, also behält handle den ersten Fehler, den es bekommt:
response = book_request_error(request)
if response is None:
response = create_book(request, state)
Die Reihenfolge darin zählt doppelt. book_error darf nicht auf Bytes laufen, die body_error abgelehnt hat, weil json_body bei ihnen eine Ausnahme auslösen würde. Und create_book darf nicht laufen, bevor jede Prüfung bestanden ist: Ein gespeichertes Buch oder ein hochgezähltes next_id vor einer 422 wäre eine Änderung, von der man dem Client gesagt hat, sie sei nicht passiert.
PUT /books/99 (kein solches Buch) trägt einen Body, der als text/csv gekennzeichnet ist. Welcher Status, nach der Reihenfolge dieses Vertrags?
Deine Aufgabe
In reading_server.py:
Schreib
book_error(data): Gib eine 422-Antwort zurück, gebaut mitjson_error, wenndatakein Dictionary ist, wenntitleoderauthorfehlt oder wenn eins davon kein String ist oder nach dem Entfernen der Leerzeichen leer ist. Andernfalls gibNonezurück.Schreib
book_request_error(request): Gibbody_error(request)zurück, wenn es nichtNoneist; andernfalls gibbook_error(json_body(request))zurück.Ruf in
handlebook_request_errorvorcreate_bookund vorreplace_bookauf und sende seine Antwort, wenn es eine zurückgibt.
Führ python pl_http.py bad-requests noch einmal aus. Jede Anfrage bekommt jetzt ihre eigene Antwort: HTTP/1.0 415 Unsupported Media Type für den text/plain-Body, HTTP/1.0 400 Bad Request für das abgeschnittene JSON, HTTP/1.0 422 Unprocessable Content für die Liste, HTTP/1.0 404 Not Found für das PUT an Buch 99 und HTTP/1.0 411 Length Required für das POST ohne Länge, das dein read_body beantwortet hat, bevor handle lief.
Drück dann Check my work. Dein Server lehnt jetzt jeden fehlerhaften Schreibzugriff mit einem Grund ab; die nächste Lektion beweist mit eigenen Tests, dass die guten Schreibzugriffe ihre Versprechen halten.
Aufgabe
Gib jedem fehlerhaften Schreibzugriff seinen eigenen Status und ändere nichts, wenn eine Anfrage abgelehnt wird.
In reading_server.py:
Schreib
book_error(data). Gib eine 422-Antwort zurück, wenndatakeinist, wenn es kein titleoder keinauthorhat oder wenn eins davon keinist oder nach dem Entfernen der Leerzeichen leer ist. Andernfalls gib Nonezurück.Schreib
book_request_error(request). Gib zurück, wasbody_error(request)zurückgibt, wenn das nichtNoneist; andernfalls gibbook_error(json_body(request))zurück.Ruf in
handlebook_request_errorvorcreate_bookund vorreplace_bookauf. Wenn es eine Antwort zurückgibt, sende diese Antwort und ruf den Schreibzugriff nicht auf.
Bau jede Fehlerantwort mit json_error und einem Satz, der das Problem benennt. Behalte die Reihenfolge von handle bei: Route und
Im python pl_http.py bad-requests fünf fehlerhafte Anfragen an deinen Server.
Drück dann Check my work. Es ruft book_error mit null, fehlende Felder, Zahlen und Leerzeichen, wo Strings hingehören, und gültige Bücher mit zusätzlichen Feldern. Es ruft book_request_error und handle mit jeweils einer Ursache auf, bei POST /books und bei PUT an ein vorhandenes Buch, und prüft jeweils den Status und dass der Zustand, einschließlich next_id, genau wie vorher ist. Es sendet außerdem ein PUT mit falscher Kennzeichnung an ein Buch, das nicht existiert, und prüft, dass gültige Schreibzugriffe weiterhin funktionieren. Nichts aus deinem Terminal wird bewertet.