0%

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 zu beantworten. Sieh dir zuerst an, was ein alter Client heute bekommt. Starte das Szenario 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 -Buch als Body und 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 : Der Umzug ist dauerhaft, und das 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 aus dem Ziel, so wie es angekommen ist. Nimm den Pfad mit urlsplit(target).path, schneide mit einem die ersten drei Zeichen ab, /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 auf, nach der er gefragt hat, wie es dein 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:

  1. Schreib redirect(status, location).

  2. Schreib legacy_location(target).

  3. Füge am Anfang von handle, vor den /books-Zweigen, zwei Zweige hinzu: Ein Pfad, dessen erste zwei Segmente v0 und books sind, bekommt redirect(308, legacy_location(...)) mit dem Ziel der Anfrage, und die Segmente ["books", ""] bekommen redirect(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, der location enthält, content-length auf "0" gesetzt und dem Body b"".

  • 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: Jede auf einem Pfad, dessen erste zwei Segmente v0 und books sind, bekommt redirect(308, legacy_location(request["target"])), und die Segmente ["books", ""] (der Pfad /books/) bekommen redirect(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 aus und lies jede Zeile 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://-, der auf denselben Pfad endet, wird akzeptiert), 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.