0%

Bodies van verzoeken en een JSON-API

Resources en collecties

Je server kan nu de body van een verzoek lezen en controleren dat die - is en ook zo is gelabeld. Voordat je de code schrijft die een boek opslaat, beslis je waar een nieuw boek terechtkomt, welk verzoek een bestaand boek wijzigt, en wat elk verzoek als antwoord geeft. Die beslissing is de vorm van de API, en elke client die hem gebruikt, is ervan afhankelijk.

Een URL geeft een ding een naam

In hoofdstuk 2 zag je het idee dat een een resource een naam geeft en dat de zegt wat je ermee doet. GET vraagt erom en is veilig: het verandert niets. PUT en DELETE zijn : er één twee keer sturen laat de server in dezelfde toestand achter als er één één keer sturen. POST is geen van beide.

De leeslijst heeft twee soorten resources:

  • De collectie, /books: de van alle boeken.

  • Een item, /books/7: één boek, aangeduid met zijn id.

Een schrijfactie gaat óf naar de collectie, om er een boek aan toe te voegen, óf naar één item, om dat boek te wijzigen of te verwijderen. Dat geeft vier bewerkingen, vaak CRUD genoemd: create, read, update, delete (aanmaken, lezen, bijwerken, verwijderen). Zo koppelt deze API ze:

VerzoekEffectSuccesBody van het antwoord
POST /booksvoegt een boek toe op een id die de server kiest201 Created, met location: /books/{id}het opgeslagen boek
GET /booksgeen200 OK{"books": [...]}
GET /books/{id}geen200 OKhet boek
PUT /books/{id}vervangt dat boek door de body200 OKhet opgeslagen boek
DELETE /books/{id}verwijdert dat boek204 No Contentgeen

Getekend als een boekenplank ziet dezelfde tabel er zo uit:

/books        the shelf         POST adds a book with a new id      -> 201
                                GET reads the whole shelf           -> 200
/books/7      one book on it    GET reads it                        -> 200
                                PUT replaces it                     -> 200
                                DELETE takes it off the shelf       -> 204

Elke andere methode op een van beide paden krijgt nog steeds 405, waarbij allow nu opsomt wat de tabel toestaat: GET, HEAD, POST op de collectie en GET, HEAD, PUT, DELETE op een item. Een PUT /books zou de hele collectie vervangen, en een DELETE /books zou hem leegmaken; deze API biedt geen van beide.

201 Created en 204 No Content zijn allebei 2xx-successen, dus een client die eerst op klasse beslist, zoals de jouwe sinds hoofdstuk 3 doet, behandelt ze al als succes. Les 4 kijkt naar wat elk van beide op de verbinding vereist.

Wie kiest de id

Bij POST kiest de server. De client stuurt een titel en een auteur naar /books; de server geeft het boek de volgende vrije id en vertelt de client waar het nu staat, in de header location. De client kent de id niet voordat hij het vraagt, en daarom heeft POST /books/7 hier geen zin.

Bij PUT kiest de URL. PUT /books/7 zegt “het boek op /books/7 moet nu dit zijn”, dus de id komt uit het pad en nooit uit de body: een body die "id": 99 zegt, vervangt nog steeds boek 7.

Daarom gedragen de twee methoden zich ook anders als ze worden herhaald. Twee identieke POST /books-verzoeken maken twee boeken met twee id’s. Twee identieke PUT /books/7-verzoeken laten boek 7 precies zo achter als één verzoek deed. Les 6 maakt van dat verschil tests.

staat toe dat PUT een resource aanmaakt op een URL die nog niets aanduidt (RFC 9110, sectie 9.3.4). Deze API doet dat niet: id’s worden door de server uitgedeeld, dus een PUT of DELETE naar een id waar geen boek staat, krijgt 404.

Een andere API zou een DELETE kunnen beantwoorden met 200 OK en het verwijderde boek in de body, en beide ontwerpen zijn geldige HTTP. Deze stuurt 204: het boek is weg, en er is niets te versturen. Waar het om gaat, is dat de API zegt wat hij doet, en SERVICE_CONTRACT.md zegt dat.

Dezelfde opslag, met de hand

De state die je handle ontvangt, is een met boeken met de id als sleutel, plus next_id. Voer dit uit om de drie schrijfacties als dict-bewerkingen te zien, met de keuzes van de server afgedrukt:

Try it

De PUT negeerde de "id": 99 uit de body, en het verwijderen van boek 1 veranderde next_id niet: de volgende POST krijgt 3, dus een verwijderde id wordt nooit opnieuw uitgedeeld.

Welk verzoek voegt een boek toe waarvan de server de id kiest?

Wat drukt dit af?

books = {1: "Kindred", 2: "Dune"}
next_id = 3
books[next_id] = "Piranesi"
next_id += 1
books[2] = "Dune (revised)"
books.pop(1)
print(books, next_id)

Wat stuurt een geslaagde DELETE /books/7 in deze API?

In de volgende les schrijf je de drie die deze tabel waarmaken.