0%

Zelf HTTP serveren · eindproject

Eindproject: Project: een leeslijst om alleen te lezen

Je server somt de boeken op, weigert wat hij niet kan, en beantwoordt HEAD zoals hij GET beantwoordt. Een client kan nog steeds niet om één boek vragen. Dit project voegt die route toe en maakt de leeslijst af die je alleen kunt lezen, zoals SERVICE_CONTRACT.md in je werkruimte haar beschrijft.

Wat de dienst belooft

Open SERVICE_CONTRACT.md. De tabel daarin is de eis:

VerzoekAntwoord
GET /books200, {"books": [every book, by id, ascending]}
GET /books/{id}200, het boek; 404 als geen boek dat id heeft
HEAD op een van beidede status en headers die GET zou sturen, en geen body
een andere op een van beide405 met allow: GET, HEAD
elk ander pad404

Elk antwoord met een body is met een content-length waarin de bytes zijn geteld, en elke foutbody is {"error": "..."}. De eerste, derde, vierde en vijfde rij kloppen al voor /books. Het nieuwe werk is de tweede rij, plus de rijen voor HEAD en 405 voor /books/{id}. {id} staat voor één padsegment: /books/2 vraagt om het boek waarvan het id 2 is.

Een id bestaat uit cijfers

Het segment komt aan als tekst, "2", en de sleutels van state["books"] zijn ints, dus "2" zou daar nooit gevonden worden. book_id_from(segment) zet een segment om in de int die het noemt, of geeft None terug als het geen id noemt. int(segment) alleen is niet genoeg: het werpt op voor "abc", en het accepteert ook tekst die hier geen id is, zoals " 12" met een spatie, "-1", en cijfers uit andere schriften. De stringmethode isdigit() alleen is ook niet genoeg:

Try it

De superscript twee en de Arabisch-Indische cijfers voor twaalf tellen allebei als cijfers voor isdigit(). int maakt van de tweede 12 en werpt voor de eerste ValueError op. Beide kunnen je server bereiken: een client verstuurt %C2%B2 of %D9%A1%D9%A2 in het pad, en path_segments decodeert ze. Een id in deze dienst bestaat uit een of meer ASCII-cijfers en niets anders, dus controleer eerst segment.isascii() and segment.isdigit(), en roep pas daarna int aan. Een leeg segment slaagt voor geen van beide controles, dus /books/ krijgt nog steeds een 404.

Of 007 boek 7 noemt, beslis je zelf; int("007") is 7, en dit project beslist het niet.

Mijlpalen

  1. book_id_from(segment). Geef int(segment) terug als het segment uit ASCII-cijfers bestaat, en anders None.

  2. De tak voor /books/{id}. Voeg in handle een tak toe voor segmenten van lengte twee waarvan het eerste segment "books" is. Zet het tweede om in een id. Als het id None is of geen sleutel van state["books"], antwoord dan met 404 via json_error, welke methode het ook is: er is op dat pad niets om toe te staan of te weigeren. Voor GET en HEAD antwoord je met 200 via json_response en het ene boek zoals het is opgeslagen.

  3. Ook daar 405. Elke andere methode op een bestaand boek antwoordt not_allowed("GET, HEAD"). Je HEAD-regel aan het eind van handle dekt de nieuwe tak al, omdat hij na het routeren wordt uitgevoerd.

  4. Voer het scenario uit. Het verstuurt vijf verzoeken: GET /books, GET /books/2, HEAD /books/2, GET /books/99, en een POST /books met een lege body:

python pl_http.py reading-list

Je zou de moeten zien; dan boek 2, Café Society, met content-length: 57; dan dezelfde kop voor HEAD /books/2 met niets na de lege regel; dan een 404 met je foutbody; dan een 405 met allow: GET, HEAD. Het resultaatblok eindigt met serve() returned after 5 connections; the server is closed.

  1. Tests, als je wilt. tests/test_server.py bevat al één test die handle rechtstreeks aanroept, via een hulpfunctie make_request(method, target). Je kunt er op dezelfde manier eigen tests aan toevoegen en ze allemaal uitvoeren vanuit het tabblad Shell:

def test_unknown_book_is_404():
    """GET /books/99 answers 404 with an error body."""
    response = reading_server.handle(make_request("GET", "/books/99"), reading_server.sample_state())
    assert response["status"] == 404
    assert "error" in json.loads(response["body"].decode("utf-8"))
python -m pytest -q

Deze tests zijn voor jou; Check my work voert ze niet uit. Hoofdstuk 5 vraagt je tests te schrijven die een eigenschap van de server bewijzen, en beoordeelt die.

Wat deze server niet controleert

Een /1.1-verzoek moet een header Host hebben, en een HTTP/1.1-server moet een verzoek zonder die header beantwoorden met 400 Bad Request (RFC 9112, sectie 3.2). Deze cursus laat die controle weg uit de leeslijst. Al het andere in het contract doet de server nu.

Als je op Check my work drukt, wordt het hele hoofdstuk opnieuw gecontroleerd met eigen boeken, id’s en paden, en draait je handle ook binnen de echte adapterklasse met verzoekbytes die in het geheugen worden gehouden, zodat de controle de bytes leest die een client zou ontvangen: de statusregel, een content-length gelijk aan de body die erop volgt, niets na de lege regel voor HEAD, en een regel allow bij een 405. Er wordt geen geopend.

De leeslijst kan op elke manier worden gelezen die HTTP toestaat, en op geen enkele manier worden veranderd: er is geen manier om een boek toe te voegen. Hoofdstuk 5 leest verzoekbodies en maakt de lijst schrijfbaar met POST, PUT en DELETE.

Opdracht

Maak de leeslijst die je alleen kunt lezen af in reading_server.py, zoals SERVICE_CONTRACT.md haar beschrijft.

  • Schrijf book_id_from(segment): geef int(segment) terug als het segment uit een of meer ASCII-cijfers bestaat (segment.isascii() en segment.isdigit()), en None voor al het andere.

  • Voeg /books/{id} toe aan handle: voor een id dat None is of geen sleutel van state["books"], antwoord je met 404 via json_error, welke het ook is; voor GET en HEAD antwoord je met 200 en het boek zoals het is opgeslagen; voor elke andere methode antwoord je not_allowed("GET, HEAD").

  • Houd alles uit les 2 tot en met 5: /books beantwoordt GET en HEAD met de en andere methoden met 405, elk ander pad antwoordt met 404, HEAD krijgt de status en headers van GET met een lege body, en make_server en serve blijven zoals je ze schreef.

Voer python pl_http.py reading-list uit in de . Tests schrijven in tests/test_server.py en python -m pytest -q uitvoeren is optioneel.

Druk daarna op Check my work. Die herhaalt de controles van les 2 tot en met 5 met boeken en paden die je niet hebt gezien. Ze roept book_id_from aan met ASCII-cijfers en met tekst die geen id is, zoals "-1", "1.5", "²", " 12" en Arabisch-Indische cijfers. Ze roept handle aan voor bestaande en ontbrekende id’s, voor segmenten die geen id zijn, voor HEAD op elke route, en voor PUT, POST en DELETE op beide paden, en verwacht 200, 404 en 405 met allow dat GET en HEAD noemt, en een -body met een content-length waarin de bytes zijn geteld bij elk antwoord met een body. Daarna voert ze je handle uit binnen de handlerklasse van http_adapter op verzoekbytes in het geheugen en leest ze de bytes die worden teruggeschreven: een statusregel 200 en een content-length gelijk aan de body voor één boek, niets na de lege regel voor HEAD, en een regel allow bij een 405. Ze controleert make_server en serve zoals les 3 deed, zonder dat er een wordt gebonden. Je eigen tests en je terminal worden niet beoordeeld.