0%

Zelf HTTP serveren · oefening

Routeren op methode en pad

Je server antwoordt. Vraag hem iets wat hij niet heeft, en hij antwoordt nog steeds met de . Voer het scenario voor deze les uit, dat vier verzoeken verstuurt:

python pl_http.py routes

GET /books, GET /nowhere en DELETE /books komen allemaal terug als HTTP/1.0 200 OK met dezelfde lijst van 132 bytes. Er is niets op /nowhere, en er werd niets verwijderd, toch zeggen de antwoorden dat beide verzoeken slaagden. Een client kan dat op geen enkele manier zien. Het vierde verzoek, PATCH /books, krijgt 501 Unsupported method ('PATCH') en een korte HTML-pagina, met Connection, Content-Type en Content-Length geschreven door http.server zelf: zoals je in les 1 las, heeft de handler van de adapter geen do_PATCH, dus je handle zag dat verzoek nooit. Dat antwoord is van de bibliotheek, niet van jou, en dat blijft zo. Deze les leert handle nee te zeggen tegen de andere twee, met de juiste status voor elk.

Het pad, gedecodeerd

Een route is een beslissing op basis van de en het pad. Het doel kan meer bevatten dan het pad: /books?limit=1 bevat een query, en de query hoort niet bij de route, dus /books?limit=1 is nog steeds de lijst. Het pad kan ook gepercent-encodeerd aankomen. In hoofdstuk 2 codeerde je client elk segment met quote, en zette decode_segment er een terug met unquote. Een client kan een teken coderen dat dat niet nodig had, dus /b%6Foks is /books: %6F is de letter o.

Drie stappen maken van een doel de lijst segmenten die een route vergelijkt:

  1. Neem het pad met urlsplit(target).path, dat de query laat vallen. urlsplit is de die je in hoofdstuk 2 gebruikte om een uit elkaar te halen; op een doel werkt ze op dezelfde manier.

  2. Splits het pad bij / en laat het eerste stuk vallen, de lege tekst vóór de eerste /, met [1:].

  3. Decodeer elk segment met unquote.

Eerst splitsen, dan decoderen. Een segment kan een gecodeerde schuine streep bevatten, %2F, die bij de tekst van dat segment hoort, zoals een titel met een schuine streep erin. Decodeer je eerst het hele pad, dan zou die schuine streep één segment in tweeën splitsen. Zowel urlsplit als unquote worden al bovenaan reading_server.py geïmporteerd.

Try it

De lijst voor de leeslijst is ["books"]. Lijsten vergelijken is exact, dus /BOOKS is een ander pad, en /books/ ook:

Wat drukt dit af?

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

De / aan het eind laat een leeg laatste segment achter, dus ["books", ""] is niet ["books"], en /books/ krijgt van deze server een 404. Sommige servers sturen zo’n verzoek in plaats daarvan door naar /books; hoofdstuk 6 laat zien hoe.

404 of 405

Twee foutstatussen dekken wat handle moet weigeren, en ze zeggen verschillende dingen:

  • 404 Not Found: er is niets op dit pad, welke methode het ook is.

  • 405 Method Not Allowed: er is iets op dit pad, maar niet met deze methode. Een 405 moet een header Allow hebben die de methoden opsomt die daar wel werken (RFC 9110, sectie 15.5.6), zodat de client kan zien wat hij in plaats daarvan mag vragen.

/nowhere is een 404 voor elke methode, DELETE inbegrepen. /books bestaat, dus DELETE /books is een 405, en de header allow ervan zegt GET. In les 5 komt HEAD erbij. De volgorde van de vragen doet ertoe: eerst het pad, dan de methode.

Try it

DELETE /books komt aan bij een server waarvan /books alleen GET en HEAD toestaat. Welk antwoord is juist?

Foutbodies

Een fout is nog steeds een antwoord met een body, dus hij krijgt dezelfde behandeling als de lijst: , content-type: application/json, en een content-length waarin de bytes zijn geteld. De foutbody van deze server is een met één sleutel, {"error": "a sentence naming the problem"}, zoals SERVICE_CONTRACT.md zegt. json_error(status, message) bouwt hem met je json_response, en not_allowed(allowed) bouwt een 405 met json_error en voegt daarna "allow" toe aan de headers van dat antwoord. allowed is de van de header als tekst, zoals "GET".

Eén regel over headerwaarden: http.server schrijft elke waarde precies zoals ze is gegeven, zonder haar te controleren. Een waarde met \r\n erin zou haar regel te vroeg beëindigen en een eigen header beginnen. Elke waarde die je in dit hoofdstuk schrijft, is tekst die je zelf koos, dus dat kan hier niet gebeuren; hoofdstuk 7 komt erop terug als waarden uit een verzoek komen.

Je opdracht

  1. Schrijf path_segments(target): de drie stappen hierboven, die de lijst gedecodeerde segmenten teruggeven.

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

  3. Schrijf not_allowed(allowed): een 405 van json_error met een melding naar keuze, plus een header "allow" met als waarde allowed.

  4. Verander handle: bereken de segmenten van request["target"]. Zijn ze ["books"] en is de methode GET, antwoord dan met de lijst zoals eerst. Zijn ze ["books"] met een andere methode, antwoord dan not_allowed("GET"). Al het andere krijgt json_error(404, ...) met een melding naar keuze.

Voer python pl_http.py routes opnieuw uit. GET /books krijgt nog steeds de lijst. GET /nowhere krijgt 404 Not Found met je foutbody, DELETE /books krijgt 405 Method Not Allowed met een regel allow die GET noemt, en PATCH /books krijgt nog steeds de 501 van de bibliotheek.

Druk daarna op Check my work. Eén methode heb je nog niet afgehandeld: HEAD, die nu een 405 krijgt. De volgende les lost dat op.

Opdracht

Schrijf drie in reading_server.py en breid handle uit.

  • path_segments(target) geeft de gedecodeerde segmenten van het pad van het doel terug: neem urlsplit(target).path, splits het bij /, laat het eerste, lege stuk vallen met [1:], en decodeer elk segment met unquote. "/a%2Fb/c?x=1" geeft ["a/b", "c"], en "/" geeft [""].

  • json_error(status, message) geeft json_response(status, {"error": message}) terug.

  • not_allowed(allowed) geeft een 405 terug, gebouwd met json_error, met een header "allow" waarvan de de tekst allowed is, zoals "GET".

  • handle(request, state) routeert op de segmenten van request["target"]: ["books"] met GET antwoordt met de zoals eerst; ["books"] met een andere antwoordt not_allowed("GET"); elk ander pad antwoordt json_error(404, ...), welke methode het ook is. De foutmeldingen formuleer je zelf.

Voer python pl_http.py routes uit in de om de vier antwoorden te zien.

Druk daarna op Check my work. Die roept path_segments aan op doelen die je niet hebt gezien, met gecodeerde schuine strepen, gecodeerde letters en queries; json_error en not_allowed met eigen waarden; en handle met een eigen state: GET op /books, op /books met een query en op een gecodeerd /books moet antwoorden met 200 en de lijst; /BOOKS, /books/, / en andere paden moeten antwoorden met 404, ook voor DELETE; POST, PUT en DELETE op /books moeten antwoorden met 405 en een header allow die GET noemt. Elk foutantwoord moet een -body {"error": ...} hebben, content-type: application/json, en een content-length gelijk aan de lengte van de body in bytes. Niets uit je terminal wordt beoordeeld, en er wordt geen server gestart.