Weiterleitungen · Übung
Schick den alten Pfad an einen neuen Ort
Die Client-Seite kann jetzt jedes location auflösen. Diese Lektion gibt ihr etwas zum Auflösen: Die Leseliste beginnt, ihre alten /v0/books-Pfade mit moved des Hilfsprogramms, das drei Anfragen sendet, die ein alter oder unachtsamer Client senden könnte:
python pl_http.py moved
Die Anfragen sind GET /v0/books/2?x=1, ein PUT /v0/books/2 mit einem GET /books/ mit einem Schrägstrich am Ende. Jede Antwort beginnt mit HTTP/1.0 404 Not Found. Bei den ersten beiden existiert unter /v0 nichts mehr; bei der dritten liefert path_segments ["books", ""], und ein leeres Segment ist keine Buch-ID. Alle drei fragen nach Dingen, die der Server hat, unter Adressen, die er nicht mehr beantwortet.
308 für jede Methode
SERVICE_CONTRACT.md sagt, was stattdessen gesendet werden soll. Jede Anfrage für /v0/books oder einen Pfad darunter, etwa /v0/books/2, bekommt eine 308 Permanent Redirect, egal mit welcher PUT oder POST eines alten Clients muss den neuen Pfad unverändert erreichen, mit seinem Body. Die Antwort trägt die neue Adresse in location, content-length: 0 und einen leeren Body.
Es ist verlockend, die Arbeit stattdessen gleich zu erledigen: Das PUT ist ja da, mit einem gültigen Buch im Body, warum es also nicht speichern und mit 200 antworten? Weil der Server dann zwei Pfade hat, die schreiben, und jede Regel, die du ab jetzt zu /books/{id} hinzufügst (Kapitel 7 fügt hinzu, wer überhaupt schreiben darf), auch zu /v0 hinzugefügt werden muss, sonst wird der alte Pfad zu einem Weg drum herum. Eine Weiterleitung behält einen einzigen Pfad, der die Arbeit macht. Der Client sendet die Anfrage noch einmal an den neuen Pfad, und nur diese Anfrage ändert etwas. Eine Weiterleitungsantwort ändert überhaupt keinen Zustand.
Der neue Zweig kommt an den Anfang von handle, vor die Routen, die du schon hast, damit er jede Methode gleich beantwortet und keine Methodenprüfung vorher läuft.
Behalte den Rest
Ein alter Client, der nach /v0/books/7?draft=1 gefragt hat, will Buch 7, mit derselben Query. Das location muss alles nach /v0 behalten: den Rest des Pfads und die Query. Wenn es /books sagt, landet ein altes PUT, das für Buch 7 gedacht war, auf der Sammlung und bekommt eine 405; wenn es ?draft=1 verliert, fragt die Anfrage stillschweigend nach etwas anderem.
legacy_location(target) baut diesen urlsplit(target).path, schneide mit einem /v0, und hänge ? und die Query an, wenn es eine gibt. Verwende das Ziel selbst, nicht die dekodierten Segmente aus path_segments: Das Ziel ist bereits prozentkodiert, also reines ASCII, und passt in einen Header. Ein Headerwert muss in Latin-1 passen, damit er überhaupt geschrieben werden kann, und http.server löst bei einem location, das aus einem dekodierten Segment wie ✓ gebaut wurde, UnicodeEncodeError aus, bevor ein einziges Byte der Antwort gesendet ist. Weil der Wert mit / beginnt, ist er relativ: Der Client löst ihn gegen die resolve_location tut, und der Server muss die Zeile Host nicht in jedes location kopieren.
PUT /v0/books/7?draft=1 kommt an. Welches location sendet der Server?
Ein Schrägstrich zu viel
/books/ ist ein Tippfehler für /books, und ein häufiger. Der Vertrag beantwortet ihn mit einer 301 Moved Permanently nach /books. 301 passt zu einer Aufräumaktion, die für Lesezugriffe gedacht ist: Ein GET für /books/ wird zu einem GET für /books. Ein Client, der ein POST an /books/ gesendet hat, kommt nach einer 301 vielleicht als GET an, also geht sein Body verloren; ein Schreibzugriff gehört auf den genauen Pfad, und der Vertrag verlangt 308 nur dort, wo alte Clients schreiben. Schreib den Zweig für genau den Pfad /books/, den path_segments in ["books", ""] verwandelt. Sonst leitet nichts weiter: /books/7/ bleibt eine 404, und ebenso jeder /v0-Pfad, der nicht /v0/books oder darunter ist.
redirect(status, location) baut beide Antworten: den Status, einen Header location, content-length auf "0" gesetzt und den Body b"". Eine Weiterleitung hat in ihrem Body nichts zu sagen, und content-length: 0 sagt das dem Client und rahmt sie wie jede andere Antwort.
Deine Aufgabe
In reading_server.py:
Schreib
redirect(status, location).Schreib
legacy_location(target).Füge am Anfang von
handle, vor den/books-Zweigen, zwei Zweige hinzu: Ein Pfad, dessen erste zwei Segmentev0undbookssind, bekommtredirect(308, legacy_location(...))mit dem Ziel der Anfrage, und die Segmente["books", ""]bekommenredirect(301, "/books"). Lass jede andere Route, wie sie ist.
Starte das Szenario noch einmal:
python pl_http.py moved
Jede Antwort trägt jetzt eine Weiterleitung. Lies ihre Zeile location:
server -> client b'HTTP/1.0 308 Permanent Redirect\r\nServer: ...\r\nDate: ...\r\nlocation: /books/2?x=1\r\ncontent-length: 0\r\n\r\n'
server -> client b'HTTP/1.0 308 Permanent Redirect\r\nServer: ...\r\nDate: ...\r\nlocation: /books/2\r\ncontent-length: 0\r\n\r\n'
server -> client b'HTTP/1.0 301 Moved Permanently\r\nServer: ...\r\nDate: ...\r\nlocation: /books\r\ncontent-length: 0\r\n\r\n'
Die Werte von Server und Date sind hier weggelassen; deine nennen deine Python-Version und die Zeit des Laufs. Der Log-Block listet die drei Anfragen mit 308, 308 und 301 auf. Das PUT hat auf dem Server nichts geändert: Nur eine Anfrage, die /books/2 erreicht, kann Buch 2 ändern.
pl_http sendet diese Anfragen selbst und liest genau, was zurückkommt, also hält es bei jeder Weiterleitung an. Das tut auch http.client: Nach /v0/books/2?x=1 gefragt, gibt es die 308 zurück und geht nicht weiter. Folgen ist die Aufgabe des Clients, und die nächste Lektion schreibt den Client, der das tut.
Drück dann Check my work.
Aufgabe
Beantworte die alten Pfade in reading_server.py mit
redirect(status, location)gibt ein Antwort-mit diesem Status zurück, einem Header location, derlocationenthält,content-lengthauf"0"gesetzt und dem Bodyb"".legacy_location(target)gibt zurück, wo ein/v0/books...-Ziel jetzt liegt: den Pfad ohne sein führendes/v0, dann?und die Query, wenn es eine gibt. Bau es aus dem Ziel, so wie es angekommen ist, noch prozentkodiert.Am Anfang von
handle, vor den anderen Routen: Jedeauf einem Pfad, dessen erste zwei Segmente v0undbookssind, bekommtredirect(308, legacy_location(request["target"])), und die Segmente["books", ""](der Pfad/books/) bekommenredirect(301, "/books").Eine Weiterleitung ändert nichts an
state. Jeder andere Pfad, einschließlich/books/7/und/v0/other, behält die Antwort, die er hatte.
Führ python pl_http.py moved im location.
Drück dann Check my work. Es ruft redirect und legacy_location direkt auf und dann handle mit einem Zustand und Buch-IDs, die du noch nicht gesehen hast: GET, HEAD, POST, PUT und DELETE auf alten Pfaden mit und ohne Query (ein PUT und ein POST tragen ein gültiges Buch), /books/, Pfade, die nicht weiterleiten dürfen, und die Routen aus Kapitel 5. Es prüft den Status, dass location die ID, den Rest des Pfads und die Query genau behält (ein absoluter http://-content-length 0, den leeren Body und dass sich der Zustand nicht geändert hat. Es schreibt außerdem eine Weiterleitung im Speicher durch http_adapter und liest die Bytes. Nichts aus deinem Terminal wird bewertet.