0%

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 auslöst. Die Antwortzeile im Protokoll lautet (no bytes: the server closed the connection without answering), und der Log-Block enthält einen , der mit 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 - käme weiter und würde bei 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üfungSchlägt fehl, wennStatusWer prüft
Rahmungkeine Länge bei POST oder PUT; eine fehlerhafte Länge oder ein zu kurzer Body; zu lang411; 400; 413read_body
Route und kein solcher Pfad, oder der Pfad nimmt diese Methode nicht an404, 405handle
Buch-IDkein Buch hat diese ID404handle
Medientypcontent-type ist nicht application/json415body_error
Syntaxdie Bytes sind kein -JSON400body_error
Form und kein mit nicht leeren title und author422book_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:

Die Prüfungen laufen von der Rahmung hinunter bis zu Form und Werten, die erste, die fehlschlägt, entscheidet den Status, und nur eine Anfrage, die alle besteht, erreicht den Schreibzugriff.

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, zu prüfen. isinstance(value, dict) fragt, ob ein Wert ein ist, also das, was aus einem JSON-Objekt wird; 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:

  1. Schreib book_error(data): Gib eine 422-Antwort zurück, gebaut mit json_error, wenn data kein Dictionary ist, wenn title oder author fehlt oder wenn eins davon kein String ist oder nach dem Entfernen der Leerzeichen leer ist. Andernfalls gib None zurück.

  2. Schreib book_request_error(request): Gib body_error(request) zurück, wenn es nicht None ist; andernfalls gib book_error(json_body(request)) zurück.

  3. Ruf in handle book_request_error vor create_book und vor replace_book auf 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:

  1. Schreib book_error(data). Gib eine 422-Antwort zurück, wenn data kein ist, wenn es kein title oder kein author hat oder wenn eins davon kein ist oder nach dem Entfernen der Leerzeichen leer ist. Andernfalls gib None zurück.

  2. Schreib book_request_error(request). Gib zurück, was body_error(request) zurückgibt, wenn das nicht None ist; andernfalls gib book_error(json_body(request)) zurück.

  3. Ruf in handle book_request_error vor create_book und vor replace_book auf. 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 , dann die Buch-ID, dann der Body.

Im sendet python pl_http.py bad-requests fünf fehlerhafte Anfragen an deinen Server.

Drück dann Check my work. Es ruft book_error mit auf, die du noch nicht gesehen hast: , Zahlen, 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.