0%

Zelf HTTP serveren · oefening

Een handler is een functie

De adapter staat klaar om voor elk verzoek handle(request, state) aan te roepen, en handle werpt nog NotImplementedError op. Deze les schrijft het, samen met een hulpfunctie die -antwoorden bouwt. Omdat handle een aanneemt en een dict teruggeeft, kun je het controleren door het aan te roepen, zonder dat er een server draait, op dezelfde manier waarop je in Python in Practice testte en in Networks with Python fake aan je code gaf. De server en de socket komen in de volgende les.

De antwoord-dict

Open reading_server.py. Een antwoord-dict heeft drie sleutels:

  • "status": de als int, zoals 200.

  • "headers": een dict van headernaam naar . Schrijf de namen in kleine letters, zoals "content-type". vergelijkt headernamen zonder op hoofdletters te letten, dus content-type en Content-Type betekenen voor elke client hetzelfde; HTTP/2 eist zelfs kleine letters. De waarden zijn .

  • "body": bytes, omdat bytes zijn wat er op de socket gaat.

handle haalt de boeken uit zijn tweede , state, niet uit een dict bovenaan het bestand. sample_state() bouwt de twee boeken die je in de zult zien, maar een test kan elke state meegeven die hij wil en daarna naar diezelfde state kijken. Niets waarvan de functie afhangt, zit verborgen in een naam op moduleniveau, en niets wat ze verandert, kan van de ene test naar de volgende overleven, tenzij de test het meegaf.

Waarom neemt handle state aan in plaats van een dict op moduleniveau te lezen?

Tel bytes, geen tekens

json.dumps(data) zet data om in JSON-tekst, en .encode("utf-8") zet die tekst om in bytes. Geef ensure_ascii=False mee om tekens zoals é te houden zoals ze zijn; zonder dat schrijft json.dumps é als de zes tekens \u00e9, wat ook geldige JSON is, alleen moeilijker te lezen op de lijn:

Try it

De tekst is 25 tekens, en de body is 26 bytes, omdat é in twee bytes kost. Content-Length telt bytes, precies zoals toen build_request hem in hoofdstuk 2 instelde voor een verzoekbody. Neem de lengte van de gecodeerde body, nooit van de tekst.

Wat drukt dit af?

print(len("Café Society"), len("Café Society".encode("utf-8")))

Het contenttype is application/json, zonder parameter charset: JSON is op de lijn altijd UTF-8, en de definitie van application/json heeft zo’n parameter niet. practice-api voegde er toch een toe; clients negeren hem.

Geen lengte, geen einde

Een client leest de kop tot aan de lege regel, en dan moet hij weten hoeveel bodybytes er volgen. content-length vertelt het hem. Zonder die header is de enige manier om het einde te vinden lezen totdat de server de verbinding sluit. Onder HTTP/1.0, dat de adapter spreekt, sluit de server na elk antwoord, dus het einde is op die manier nog te vinden, en http.client leest zo’n antwoord tot het sluiten. Je wire.py uit hoofdstuk 1 weigert het in plaats daarvan: zijn read_body werpt op als er geen Content-Length aankwam. In hoofdstuk 9 blijft de verbinding na een antwoord open, en een antwoord zonder lengte laat de client wachten op een sluiting die nooit komt. Stuur de lengte mee bij elk antwoord dat een body heeft.

Je opdracht

  1. Schrijf json_response(status, data). Bouw de body met json.dumps(data, ensure_ascii=False).encode("utf-8") en geef een antwoord-dict terug met die status, de headers "content-type" (de constante JSON_TYPE, die al bovenaan het bestand staat) en "content-length" (str(len(body))), en de body.

  2. Schrijf de eerste versie van handle(request, state). Bouw een van elk boek in state["books"], op id, oplopend, en geef json_response(200, {"books": books}) terug. SERVICE_CONTRACT.md belooft die volgorde. De state houdt zijn boeken al zo bij, omdat elk nieuw boek het volgende, hogere id krijgt, en een dict zijn sleutels bewaart in de volgorde waarin ze zijn toegevoegd, dus een for- over state["books"] komt ze op id tegen, oplopend. Voorlopig negeert handle het verzoek: elke en elk doel krijgt de lijst. Les 4 leert het nee te zeggen.

Controleer het vanuit het tabblad Shell. main() roept handle aan voor drie doelen en drukt elke antwoord-dict af, nog steeds zonder server en zonder socket:

python reading_server.py

Je zou drie regels moeten zien, voor /books, /books/2 en /nowhere, elk met status 200, je twee headers met content-length '132', en dezelfde body. In de body verschijnt é als \xc3\xa9, zijn twee UTF-8-bytes. Alle drie de doelen krijgen de hele lijst, omdat handle nog niet naar het doel kijkt.

Druk daarna op Check my work. Daarna bereikt voor het eerst een echte verbinding handle.

Opdracht

Schrijf twee in reading_server.py.

  • json_response(status, data) geeft een antwoord- terug: "status" is de status die het kreeg, "body" is json.dumps(data, ensure_ascii=False).encode("utf-8"), en "headers" bevat "content-type" met de JSON_TYPE en "content-length" met de waarde str(len(body)), de lengte van de body in bytes.

  • handle(request, state) geeft json_response(200, {"books": books}) terug, waarbij books elk boek in state["books"] opsomt, op id, oplopend. Voorlopig beantwoordt het elk verzoek zo, welke of welk doel het ook is. Het leest de boeken uit state en verandert er niets in.

Voer python reading_server.py uit in de om te zien wat handle teruggeeft voor drie doelen.

Druk daarna op Check my work. Die roept json_response aan met statussen en data die je niet hebt gezien, ook tekst buiten ASCII, en controleert de status, dat de body bytes zijn die die data als - bevatten, het label application/json, en dat content-length gelijk is aan de lengte van de body in bytes. Ze roept handle aan voor GET /books met eigen states, met drie boeken, twee boeken en geen, en controleert de status, de headers, de body {"books": [...]} en dat de state daarna ongewijzigd is. Niets uit je terminal wordt beoordeeld, en er wordt geen server gestart.