Anfrage-Bodys und eine JSON-API
Ressourcen und Sammlungen
Dein Server kann jetzt einen Anfrage-Body lesen und prüfen, dass er
Eine URL benennt ein Ding
In Kapitel 2 hast du die Idee kennengelernt, dass eine GET fragt sie ab und ist sicher: Es ändert nichts. PUT und DELETE sind POST ist keins von beidem.
Die Leseliste hat zwei Arten von Ressourcen:
Die Sammlung,
/books: diealler Bücher. Ein Element,
/books/7: ein Buch, benannt durch seine ID.
Ein Schreibzugriff geht entweder an die Sammlung, um ihr ein Buch hinzuzufügen, oder an ein Element, um dieses Buch zu ändern oder zu entfernen. Das ergibt vier Operationen, oft CRUD genannt: create, read, update, delete (anlegen, lesen, ändern, löschen). So bildet diese API sie ab:
| Anfrage | Wirkung | Erfolg | Body der Antwort |
|---|---|---|---|
POST /books | fügt ein Buch unter einer ID hinzu, die der Server wählt | 201 Created, mit location: /books/{id} | das gespeicherte Buch |
GET /books | keine | 200 OK | {"books": [...]} |
GET /books/{id} | keine | 200 OK | das Buch |
PUT /books/{id} | ersetzt dieses Buch durch den Body | 200 OK | das gespeicherte Buch |
DELETE /books/{id} | entfernt dieses Buch | 204 No Content | keiner |
Als Regal gezeichnet sieht dieselbe Tabelle so aus:
/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
Jede andere Methode auf einem der beiden Pfade bekommt weiterhin 405, wobei allow jetzt auflistet, was die Tabelle erlaubt: GET, HEAD, POST auf der Sammlung und GET, HEAD, PUT, DELETE auf einem Element. Ein PUT /books würde die ganze Sammlung ersetzen, und ein DELETE /books würde sie leeren; diese API bietet keins von beidem an.
201 Created und 204 No Content sind beides 2xx-Erfolge, also behandelt ein Client, der zuerst nach der Klasse entscheidet, wie es deiner seit Kapitel 3 tut, sie bereits als Erfolg. Lektion 4 sieht sich an, was jeder davon auf der Leitung verlangt.
Wer die ID wählt
Bei POST wählt der Server. Der Client sendet einen Titel und einen Autor an /books; der Server gibt dem Buch die nächste freie ID und sagt dem Client im Header location, wo es jetzt liegt. Der Client kennt die ID nicht, bevor er fragt, und deshalb ergibt POST /books/7 hier keinen Sinn.
Bei PUT wählt die URL. PUT /books/7 sagt „das Buch unter /books/7 soll jetzt dies sein“, also kommt die ID aus dem Pfad und nie aus dem Body: Ein Body, der "id": 99 sagt, ersetzt trotzdem Buch 7.
Deshalb verhalten sich die beiden Methoden auch unterschiedlich, wenn sie wiederholt werden. Zwei identische Anfragen POST /books legen zwei Bücher mit zwei IDs an. Zwei identische Anfragen PUT /books/7 hinterlassen Buch 7 genau so wie eine. Lektion 6 macht aus diesem Unterschied Tests.
PUT, eine Ressource unter einer URL anzulegen, die noch nichts benennt (RFC 9110, Abschnitt 9.3.4). Diese API tut das nicht: IDs vergibt der Server, also bekommt ein PUT oder DELETE an eine ID ohne Buch 404.
Eine andere API könnte auf ein DELETE mit 200 OK und dem gelöschten Buch im Body antworten, und beide Entwürfe sind gültiges HTTP. Diese sendet 204: Das Buch ist weg, und es gibt nichts zu senden. Wichtig ist, dass die API sagt, was sie tut, und SERVICE_CONTRACT.md sagt es.
Derselbe Speicher, von Hand
Der Zustand, den dein handle bekommt, ist ein next_id. Führ das hier aus, um die drei Schreibzugriffe als Dictionary-Operationen zu sehen, mit den Entscheidungen des Servers ausgegeben:
Das PUT hat das "id": 99 des Bodys ignoriert, und das Löschen von Buch 1 hat next_id nicht verändert: Das nächste POST bekommt 3, also wird eine gelöschte ID nie wieder vergeben.
Welche Anfrage fügt ein Buch hinzu, dessen ID der Server wählt?
Was gibt das aus?
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)
Was sendet ein erfolgreiches DELETE /books/7 in dieser API?
In der nächsten Lektion schreibst du die drei