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 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:
GETenHEADop/booksen/books/{id};POST /booksmet 201 enlocation;PUT /books/{id}met 200;DELETE /books/{id}met 204, geen body en geencontent-length.405 met
allow: GET, HEAD, POSTop de collectie enallow: GET, HEAD, PUT, DELETEop 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_bodycontroleert de framing voordathandledraait, dus eenPOST /nowherezonder 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 urlsplit. Een querystring op de server lezen is het omgekeerde: urlsplit(target).query is de tekst na de ?, en parse_qs maakt daar een parse_qs decodeert ook %-escapes en maakt van + een spatie, zoals je zag dat het bij formulieren doet:
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 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
Schrijf
query_limit(target)met de regels hierboven.Roep in
handle, in de tak voorGETenHEADop/books,query_limit(request["target"])aan. Als dieNoneteruggeeft, antwoord je metjson_error(400, ...). Bouw anders de lijst zoals eerder, op id, oplopend, en stop zodra hijlimitboeken bevat alslimitniet 0 is. DeHEAD-regel aan het eind vanhandlemaakt de body nog steeds leeg, dusHEAD /books?limit=1draagt de lengte dieGET /books?limit=1zou sturen.Voer
python pl_http.py crudopnieuw uit. Het eerste antwoord is nuHTTP/1.0 200 OKmetcontent-length: 73, één boek, en het laatste isHTTP/1.0 400 Bad Request.Voer
python -m pytest -quit. 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 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:
Schrijf
query_limit(target). Lees de querystring metparse_qs(urlsplit(target).query). Geef 0 terug als er geenlimitis. GeefNoneterug alslimitmeer dan eens voorkomt, niet uit ASCII-cijfers bestaat, of niet tussen 1 en 100 ligt. Geef anders de limiet als int terug.In
handle, voorGETenHEADop/books: alsquery_limitNoneteruggeeft, antwoord je met 400 viajson_error; geef anders de eerstelimitboeken 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-tests/test_server.py.
In de 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 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.