0%

HTTP selbst ausliefern · Übung

Nach Methode und Pfad routen

Dein Server antwortet. Frag ihn nach etwas, das er nicht hat, und er antwortet trotzdem mit der . Führe das Szenario für diese Lektion aus, das vier Anfragen sendet:

python pl_http.py routes

GET /books, GET /nowhere und DELETE /books kommen alle mit HTTP/1.0 200 OK und derselben Liste von 132 Bytes zurück. Unter /nowhere gibt es nichts, und nichts wurde gelöscht, trotzdem sagen die Antworten, dass beide Anfragen erfolgreich waren. Ein Client kann das nicht erkennen. Die vierte Anfrage, PATCH /books, bekommt 501 Unsupported method ('PATCH') und eine kurze HTML-Seite, mit Connection, Content-Type und Content-Length, geschrieben von http.server selbst: Wie du in Lektion 1 gelesen hast, hat der Handler des Adapters kein do_PATCH, also hat dein handle diese Anfrage nie gesehen. Diese Antwort ist die der Bibliothek, nicht deine, und das bleibt so. Diese Lektion bringt handle bei, zu den anderen beiden nein zu sagen, jeweils mit dem richtigen Status.

Der Pfad, dekodiert

Eine Route ist eine Entscheidung, die aus und Pfad getroffen wird. Das Ziel kann mehr als den Pfad enthalten: /books?limit=1 trägt eine Query, und die Query gehört nicht zur Route, also ist /books?limit=1 immer noch die Liste. Der Pfad kann auch prozentkodiert ankommen. In Kapitel 2 hat dein Client jedes Segment mit quote kodiert, und decode_segment hat eines mit unquote zurückverwandelt. Ein Client kann ein Zeichen kodieren, das es nicht gebraucht hätte, also ist /b%6Foks gleich /books: %6F ist der Buchstabe o.

Drei Schritte machen aus einem Ziel die Liste von Segmenten, die eine Route vergleicht:

  1. Nimm den Pfad mit urlsplit(target).path, das die Query weglässt. urlsplit ist die , mit der du in Kapitel 2 eine auseinandergenommen hast; auf einem Ziel funktioniert sie genauso.

  2. Zerlege den Pfad an / und lass mit [1:] das erste Stück weg, den leeren Text vor dem führenden /.

  3. Dekodiere jedes Segment mit unquote.

Zuerst zerlegen, dann dekodieren. Ein Segment kann einen kodierten Schrägstrich enthalten, %2F, der zum Text dieses Segments gehört, etwa ein Titel mit einem Schrägstrich darin. Dekodierst du zuerst den ganzen Pfad, würde dieser Schrägstrich ein Segment in zwei teilen. Sowohl urlsplit als auch unquote sind schon oben in reading_server.py importiert.

Try it

Die Liste für die Leseliste ist ["books"]. Der Vergleich von Listen ist exakt, also ist /BOOKS ein anderer Pfad, und /books/ ebenso:

Was gibt das aus?

print("/books/".split("/")[1:])

Der abschließende / hinterlässt ein leeres letztes Segment, also ist ["books", ""] nicht ["books"], und /books/ bekommt von diesem Server ein 404. Manche Server schicken eine solche Anfrage stattdessen an /books weiter; Kapitel 6 zeigt, wie.

404 oder 405

Zwei Fehlerstatus decken ab, was handle ablehnen muss, und sie sagen verschiedene Dinge:

  • 404 Not Found: Unter diesem Pfad gibt es nichts, egal mit welcher Methode.

  • 405 Method Not Allowed: Unter diesem Pfad gibt es etwas, aber nicht mit dieser Methode. Ein 405 muss einen Header Allow tragen, der die Methoden auflistet, die dort funktionieren (RFC 9110, Abschnitt 15.5.6), damit der Client sieht, was er stattdessen fragen darf.

/nowhere ist für jede Methode ein 404, auch für DELETE. /books existiert, also ist DELETE /books ein 405, und sein Header allow sagt GET. In Lektion 5 kommt HEAD dazu. Die Reihenfolge der Fragen ist wichtig: zuerst der Pfad, dann die Methode.

Try it

DELETE /books kommt bei einem Server an, dessen /books nur GET und HEAD erlaubt. Welche Antwort ist richtig?

Fehler-Bodys

Ein Fehler ist trotzdem eine Antwort mit einem Body, also wird er genauso behandelt wie die Liste: , content-type: application/json und ein content-length, das Bytes zählt. Der Fehler-Body dieses Servers ist ein mit einem Schlüssel, {"error": "a sentence naming the problem"}, wie SERVICE_CONTRACT.md sagt. json_error(status, message) baut ihn mit deinem json_response, und not_allowed(allowed) baut ein 405 mit json_error und fügt dann "allow" zu den Headern dieser Antwort hinzu. allowed ist der des Headers als Text, etwa "GET".

Eine Regel zu Headerwerten: http.server schreibt jeden Wert genau so, wie er übergeben wird, ohne ihn zu prüfen. Ein Wert, der \r\n enthält, würde seine Zeile zu früh beenden und einen eigenen Header beginnen. Jeder Wert, den du in diesem Kapitel schreibst, ist Text, den du gewählt hast, also kann das hier nicht passieren; Kapitel 7 kommt darauf zurück, wenn Werte aus einer Anfrage stammen.

Deine Aufgabe

  1. Schreibe path_segments(target): die drei Schritte von oben, die die Liste der dekodierten Segmente zurückgeben.

  2. Schreibe json_error(status, message): json_response(status, {"error": message}).

  3. Schreibe not_allowed(allowed): ein 405 aus json_error mit einer Meldung deiner Wahl, plus einem Header "allow", dessen Wert allowed ist.

  4. Ändere handle: Berechne die Segmente von request["target"]. Wenn sie ["books"] sind und die Methode GET ist, antworte wie vorher mit der Liste. Wenn sie ["books"] sind, mit irgendeiner anderen Methode, antworte mit not_allowed("GET"). Alles andere bekommt json_error(404, ...) mit einer Meldung deiner Wahl.

Führe python pl_http.py routes noch einmal aus. GET /books bekommt weiterhin die Liste. GET /nowhere bekommt 404 Not Found mit deinem Fehler-Body, DELETE /books bekommt 405 Method Not Allowed mit einer Zeile allow, die GET nennt, und PATCH /books bekommt weiterhin das 501 der Bibliothek.

Drücke dann Check my work. Eine Methode, die du noch nicht behandelt hast, ist HEAD: Im Moment bekommt sie ein 405. Die nächste Lektion behebt das.

Aufgabe

Schreibe drei in reading_server.py und erweitere handle.

  • path_segments(target) gibt die dekodierten Segmente des Pfads des Ziels zurück: Nimm urlsplit(target).path, zerlege es an /, lass das erste, leere Stück mit [1:] weg und dekodiere jedes Segment mit unquote. "/a%2Fb/c?x=1" ergibt ["a/b", "c"], und "/" ergibt [""].

  • json_error(status, message) gibt json_response(status, {"error": message}) zurück.

  • not_allowed(allowed) gibt ein mit json_error gebautes 405 zurück, mit einem Header "allow", dessen der Text allowed ist, etwa "GET".

  • handle(request, state) routet nach den Segmenten von request["target"]: ["books"] mit GET antwortet wie vorher mit der ; ["books"] mit jeder anderen antwortet mit not_allowed("GET"); jeder andere Pfad antwortet mit json_error(404, ...), egal mit welcher Methode. Die Fehlermeldungen formulierst du selbst.

Führe im python pl_http.py routes aus, um die vier Antworten zu sehen.

Drücke dann Check my work. Die Prüfung ruft path_segments auf Zielen auf, die du nicht gesehen hast, mit kodierten Schrägstrichen, kodierten Buchstaben und Querys; json_error und not_allowed mit eigenen Werten; und handle mit einem eigenen State: GET auf /books, auf /books mit einer Query und auf einem kodierten /books muss mit 200 und der Liste antworten; /BOOKS, /books/, / und andere Pfade müssen mit 404 antworten, auch bei DELETE; POST, PUT und DELETE auf /books müssen mit 405 und einem Header allow antworten, der GET nennt. Jede Fehlerantwort muss einen -Body {"error": ...} tragen, content-type: application/json und ein content-length, das gleich der Länge des Bodys in Bytes ist. Nichts aus deinem Terminal wird bewertet, und kein Server wird gestartet.