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
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 /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:
Neem het pad met
urlsplit(target).path, dat de query laat vallen.urlsplitis dedie je in hoofdstuk 2 gebruikte om een uit elkaar te halen; op een doel werkt ze op dezelfde manier. Splits het pad bij
/en laat het eerste stuk vallen, de lege tekst vóór de eerste/, met[1:].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.
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
Allowhebben 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.
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 {"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 "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
Schrijf
path_segments(target): de drie stappen hierboven, die de lijst gedecodeerde segmenten teruggeven.Schrijf
json_error(status, message):json_response(status, {"error": message}).Schrijf
not_allowed(allowed): een 405 vanjson_errormet een melding naar keuze, plus een header"allow"met als waardeallowed.Verander
handle: bereken de segmenten vanrequest["target"]. Zijn ze["books"]en is de methodeGET, antwoord dan met de lijst zoals eerst. Zijn ze["books"]met een andere methode, antwoord dannot_allowed("GET"). Al het andere krijgtjson_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 reading_server.py en breid handle uit.
path_segments(target)geeft de gedecodeerde segmenten van het pad van het doel terug: neemurlsplit(target).path, splits het bij/, laat het eerste, lege stuk vallen met[1:], en decodeer elk segment metunquote."/a%2Fb/c?x=1"geeft["a/b", "c"], en"/"geeft[""].json_error(status, message)geeftjson_response(status, {"error": message})terug.not_allowed(allowed)geeft een 405 terug, gebouwd metjson_error, met een header"allow"waarvan dede tekst allowedis, zoals"GET".handle(request, state)routeert op de segmenten vanrequest["target"]:["books"]metGETantwoordt met dezoals eerst; ["books"]met een andereantwoordt not_allowed("GET"); elk ander pad antwoordtjson_error(404, ...), welke methode het ook is. De foutmeldingen formuleer je zelf.
Voer python pl_http.py routes uit in de
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 {"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.