0%

Doorverwijzingen · oefening

Stuur het oude pad naar een nieuwe plek

De clientkant kan nu elke location oplossen. Deze les geeft hem iets om op te lossen: de leeslijst begint zijn oude /v0/books-paden met te beantwoorden. Kijk eerst wat een oude client vandaag krijgt. Voer het scenario moved van het hulpprogramma uit, dat drie verzoeken stuurt die een oude of slordige client zou kunnen sturen:

python pl_http.py moved

De verzoeken zijn GET /v0/books/2?x=1, een PUT /v0/books/2 met een -boek als body, en GET /books/ met een slash aan het eind. Elk antwoord begint met HTTP/1.0 404 Not Found. Voor de eerste twee bestaat er onder /v0 niets meer; voor de derde geeft path_segments ["books", ""], en een leeg segment is geen boek-id. Alle drie vragen om dingen die de server heeft, op adressen die hij niet meer beantwoordt.

308 voor elke methode

SERVICE_CONTRACT.md zegt wat er in plaats daarvan gestuurd moet worden. Elk verzoek voor /v0/books of een pad daaronder, zoals /v0/books/2, krijgt een 308 Permanent Redirect, wat de ook is: de verhuizing is permanent, en de PUT of POST van een oude client moet het nieuwe pad ongewijzigd bereiken, met zijn body. Het antwoord draagt het nieuwe adres in location, content-length: 0 en een lege body.

Het is verleidelijk om het werk meteen te doen: de PUT is er al, met een geldig boek in de body, dus waarom niet opslaan en 200 antwoorden? Omdat de server dan twee paden heeft die schrijven, en elke regel die je vanaf nu aan /books/{id} toevoegt (hoofdstuk 7 voegt toe wie überhaupt mag schrijven), ook aan /v0 moet worden toegevoegd, anders wordt het oude pad een manier om eromheen te gaan. Een doorverwijzing houdt één pad dat het werk doet. De client stuurt het verzoek opnieuw naar het nieuwe pad, en alleen dat verzoek verandert iets. Een doorverwijzing als antwoord verandert helemaal geen state.

De nieuwe tak komt bovenaan handle, vóór de routes die je al hebt, zodat hij elke methode op dezelfde manier beantwoordt en er eerst geen methodecontrole draait.

Behoud de rest

Een oude client die om /v0/books/7?draft=1 vroeg, wil boek 7, met dezelfde querystring. De location moet alles na /v0 behouden: de rest van het pad en de querystring. Als hij /books zegt, komt een oude PUT die voor boek 7 bedoeld was, op de collectie terecht en krijgt die een 405; als hij ?draft=1 kwijtraakt, vraagt het verzoek stilletjes om iets anders.

legacy_location(target) bouwt die uit het target zoals het aankwam. Neem het pad met urlsplit(target).path, knip de eerste drie tekens, /v0, eraf met een , en voeg ? en de querystring toe als die er is. Gebruik het target zelf, niet de gedecodeerde segmenten uit path_segments: het target is al percent-gecodeerd, dus het is gewone ASCII en past in een header. Een headerwaarde moet in Latin-1 passen om überhaupt geschreven te kunnen worden, en http.server werpt UnicodeEncodeError op bij een location die gebouwd is uit een gedecodeerd segment zoals ✓, voordat er één byte van het antwoord is verstuurd. Omdat de waarde met / begint, is hij relatief: de client lost hem op ten opzichte van de waar hij om vroeg, zoals je resolve_location doet, en de server hoeft de regel Host niet in elke location te kopiëren.

Er komt PUT /v0/books/7?draft=1 binnen. Welke location stuurt de server?

Een slash te veel

/books/ is een tikfout voor /books, en een veelvoorkomende. Het contract beantwoordt die met een 301 Moved Permanently naar /books. 301 past bij een opruimactie die voor leesacties bedoeld is: een GET voor /books/ wordt een GET voor /books. Een client die een POST naar /books/ stuurde, kan na een 301 als GET aankomen, dus zijn body gaat verloren; een schrijfactie hoort op het exacte pad, en het contract vraagt alleen om 308 waar oude clients schrijven. Schrijf de tak voor precies het pad /books/, dat path_segments omzet in ["books", ""]. Niets anders wordt doorverwezen: /books/7/ blijft een 404, net als elk /v0-pad dat niet /v0/books of daaronder is.

redirect(status, location) bouwt beide antwoorden: de status, een header location, content-length op "0", en de body b"". Een doorverwijzing heeft in zijn body niets te zeggen, en content-length: 0 vertelt de client dat, en bakent het antwoord af zoals elk ander antwoord.

Jouw opdracht

In reading_server.py:

  1. Schrijf redirect(status, location).

  2. Schrijf legacy_location(target).

  3. Voeg bovenaan handle, vóór de /books-takken, twee takken toe: een pad waarvan de eerste twee segmenten v0 en books zijn, krijgt redirect(308, legacy_location(...)) met het target van het verzoek, en de segmenten ["books", ""] krijgen redirect(301, "/books"). Laat elke andere route zoals hij is.

Voer het scenario opnieuw uit:

python pl_http.py moved

Elk antwoord draagt nu een doorverwijzing. Lees de regel location ervan:

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'

De waarden van Server en Date zijn hier weggelaten; de jouwe noemen je Python-versie en het tijdstip van de run. Het logblok somt de drie verzoeken op met 308, 308 en 301. De PUT veranderde niets op de server: alleen een verzoek dat /books/2 bereikt, kan boek 2 veranderen.

pl_http stuurt deze verzoeken zelf en leest precies wat er terugkomt, dus het stopt bij elke doorverwijzing. http.client ook: gevraagd om /v0/books/2?x=1 geeft het de 308 terug en gaat niet verder. Volgen is de taak van de client, en de volgende les schrijft de client die dat doet.

Klik daarna op Check my work.

Opdracht

Beantwoord de oude paden in reading_server.py met .

  • redirect(status, location) geeft een antwoord- terug met die status, een header location met location erin, content-length op "0", en de body b"".

  • legacy_location(target) geeft terug waar een /v0/books...-target nu staat: het pad zonder de /v0 aan het begin, gevolgd door ? en de querystring als die er is. Bouw het uit het target zoals het aankwam, nog steeds percent-gecodeerd.

  • Bovenaan handle, vóór de andere routes: elke op een pad waarvan de eerste twee segmenten v0 en books zijn, krijgt redirect(308, legacy_location(request["target"])), en de segmenten ["books", ""] (het pad /books/) krijgen redirect(301, "/books").

  • Een doorverwijzing verandert niets in state. Elk ander pad, ook /books/7/ en /v0/other, houdt het antwoord dat het had.

Voer python pl_http.py moved uit in de en lees elke regel location.

Klik daarna op Check my work. Die roept redirect en legacy_location rechtstreeks aan, en roept daarna handle aan met een state en boek-id’s die je nog niet hebt gezien: GET, HEAD, POST, PUT en DELETE op oude paden met en zonder querystring (een PUT en een POST dragen een geldig boek), /books/, paden die niet mogen doorverwijzen, en de routes uit hoofdstuk 5. Hij controleert de status, dat location de id, de rest van het pad en de querystring exact behoudt (een absolute http://- die op hetzelfde pad eindigt, wordt geaccepteerd), content-length 0, de lege body, en dat de state niet veranderd is. Hij schrijft ook één doorverwijzing in het geheugen door http_adapter en leest de bytes. Niets uit je terminal wordt beoordeeld.