0%

Bodies van verzoeken en een JSON-API · eindproject

Eindproject: Project: een beschrijfbare leeslijst

Je leeslijst maakt nu boeken aan, leest ze, vervangt ze en verwijdert ze, weigert elk fout verzoek met een eigen status, en heeft tests die hem aan de beloften van zijn houden. Eén rij van SERVICE_CONTRACT.md klopt nog niet: GET /books?limit=N, de eerste N boeken. Dit project voegt die toe en controleert daarna de hele service tegen het contract.

Wat het contract vraagt

Open SERVICE_CONTRACT.md en lees het nog één keer als checklist. Elke rij behalve ?limit= zou al moeten kloppen:

  • GET en HEAD op /books en /books/{id}; POST /books met 201 en location; PUT /books/{id} met 200; DELETE /books/{id} met 204, geen body en geen content-length.

  • 405 met allow: GET, HEAD, POST op de collectie en allow: GET, HEAD, PUT, DELETE op een item, en overal elders 404.

  • De volgorde van de fouten: route en methode, framing (411, 400, 413), de boek-id, mediatype (415), syntaxis (400), vorm en (422), waarbij bij geen enkele fout iets wordt opgeslagen of geteld. read_body controleert de framing voordat handle draait, dus een POST /nowhere zonder lengte krijgt nog steeds 411.

Voer het volledige scenario van het hulpprogramma uit om te zien waar je staat:

python pl_http.py crud

Het vraagt om GET /books?limit=1, maakt boek 3 aan, vervangt het, verwijdert het, en vraagt om GET /books?limit=0. Vandaag antwoorden beide GETs 200 met alle boeken, content-length: 132 voor de twee voorbeeldboeken, omdat handle de query negeert. De drie schrijfacties daartussen zouden al HTTP/1.0 201 Created, HTTP/1.0 200 OK en HTTP/1.0 204 No Content moeten tonen.

Een queryparameter

In hoofdstuk 2 bouwde je querystrings met urlencode en haalde je uit elkaar met urlsplit. Een querystring op de server lezen is het omgekeerde: urlsplit(target).query is de tekst na de ?, en parse_qs maakt daar een van. Elke naam verwijst naar een waarden, omdat een naam meer dan eens kan voorkomen; parse_qs decodeert ook %-escapes en maakt van + een spatie, zoals je zag dat het bij formulieren doet:

Try it

Dat drukt {'limit': ['2'], 'sort': ['title']} af, dan {'limit': ['1', '2']} en dan {}. parse_qs wordt al bovenaan reading_server.py geïmporteerd.

De regels voor limit volgen de regels die read_body op een lengte toepast. Geen limit betekent geen limiet. Een limit moet precies één keer voorkomen en uit ASCII-cijfers bestaan, gecontroleerd met isascii() en isdigit() vóór int, zodat two, -1, 1.5 en ² worden geweigerd in plaats van je server te laten crashen. En hij moet tussen 1 en 100 liggen: limit=0 vraagt om niets, en een client die tienduizend boeken in één antwoord wil, hoort die niet te krijgen. Elke limiet die een regel breekt, krijgt 400 met een -fout. Andere queryparameters, zoals sort=title, worden genegeerd.

query_limit(target) maakt van die regels één waarde voor handle: 0 voor geen limiet, het getal voor een geldige limiet, en None voor een ongeldige. 0 werkt als “geen limiet” omdat een geldige limiet nooit 0 is.

Een echte API combineert een limiet meestal met een manier om de volgende pagina op te vragen; die ontwerpen vallen buiten deze cursus. Deze stopt bij de eerste N.

Mijlpalen

  1. Schrijf query_limit(target) met de regels hierboven.

  2. Roep in handle, in de tak voor GET en HEAD op /books, query_limit(request["target"]) aan. Als die None teruggeeft, antwoord je met json_error(400, ...). Bouw anders de lijst zoals eerder, op id, oplopend, en stop zodra hij limit boeken bevat als limit niet 0 is. De HEAD-regel aan het eind van handle maakt de body nog steeds leeg, dus HEAD /books?limit=1 draagt de lengte die GET /books?limit=1 zou sturen.

  3. Voer python pl_http.py crud opnieuw uit. Het eerste antwoord is nu HTTP/1.0 200 OK met content-length: 73, één boek, en het laatste is HTTP/1.0 400 Bad Request.

  4. Voer python -m pytest -q uit. Je tests uit les 6 zouden nog steeds moeten slagen. Je kunt eigen tests voor ?limit= toevoegen, op dezelfde manier geschreven, bijvoorbeeld:

def test_a_limit_lists_the_first_books():
    response = reading_server.handle(make_request("GET", "/books?limit=1"), reading_server.sample_state())
    data = json.loads(response["body"].decode("utf-8"))
    assert len(data["books"]) == 1
python -m pytest -q

Wat er wordt gecontroleerd

Check my work controleert het hele hoofdstuk opnieuw met boeken die hij je niet heeft laten zien. Hij roept read_body, query_limit en handle rechtstreeks aan, met goede en foute limieten, elke soort schrijfactie en één fout verzoek per oorzaak, en vergelijkt de state voor en na elke fout. Hij bouwt je server met make_server zonder een te openen, stuurt een POST, een DELETE en een POST /nowhere zonder lengte als bytes door de handler-klasse, en leest de bytes die terugkomen. Hij voert serve uit met een vervangende server, en hij voert je tests één keer uit tegen zijn eigen correcte handle.

Klik daarna op Check my work. Je service houdt zich nu aan zijn hele contract. Sommige clients vragen echter nog om boeken op paden uit een oudere versie van de API, /v0/books; in hoofdstuk 6 vertelt je server hun waar de boeken naartoe zijn gegaan, en leert je client die te volgen.

Opdracht

Maak de beschrijfbare leeslijst af: voeg GET /books?limit=N toe, en laat elke rij van SERVICE_CONTRACT.md kloppen.

In reading_server.py:

  1. Schrijf query_limit(target). Lees de querystring met parse_qs(urlsplit(target).query). Geef 0 terug als er geen limit is. Geef None terug als limit meer dan eens voorkomt, niet uit ASCII-cijfers bestaat, of niet tussen 1 en 100 ligt. Geef anders de limiet als int terug.

  2. In handle, voor GET en HEAD op /books: als query_limit None teruggeeft, antwoord je met 400 via json_error; geef anders de eerste limit boeken op id, oplopend, of alle boeken als de limiet 0 is. Andere queryparameters worden genegeerd.

Alles uit les 1 tot en met 6 moet nog steeds werken: read_body die door make_server aan de adapter wordt meegegeven, body_error, de drie schrijfacties met 201 en location, 200, en 204 zonder body of content-length, status naar oorzaak zonder wijziging van de state bij welke fout dan ook, de allow-, en je tests in tests/test_server.py.

In de voert python pl_http.py crud een volledige reeks uit tegen je server, en voert python -m pytest -q je tests uit.

Klik daarna op Check my work. Met boeken, id’s en limieten die je nog niet hebt gezien, roept hij query_limit, read_body en handle rechtstreeks aan, voor geldige en ongeldige limieten, HEAD met een limiet, elke schrijfactie en één fout verzoek per oorzaak, en vergelijkt hij de state voor en na elke fout. Hij bouwt je server met make_server zonder een te openen en stuurt een POST, een DELETE en een POST /nowhere zonder lengte als bytes door de handler-klasse. Hij voert serve uit met een vervangende server. Als laatste voert hij je tests één keer uit met zijn eigen correcte handle op zijn plaats; die moeten allemaal slagen. Niets uit je terminal wordt beoordeeld.