HTTP-Methoden
GET, POST, PUT, PATCH und DELETE, und welche davon sich gefahrlos wiederholen lassen.
Die Methode ist das erste Wort jedes Requests. Sie sagt dem Server, was du mit der Ressource unter der URL tun willst. Dieselbe URL, /api/Books/1anmelden zum Anzeigen, liefert mit GET ein Buch, ändert es mit PUT oder PATCH und entfernt es mit DELETE. Die meisten APIs kommen für fast alles mit fünf Methoden aus, ein paar weitere tauchen ab und zu auf.
Bei den Methoden fängt der Unterschied zwischen einem Browser und einem Testwerkzeug an, wichtig zu werden. Die Adressleiste sendet nur GET. Für den Rest brauchst du curl, Postman oder einen Senden-Button wie die auf dieser Seite. Die Beispiele, die geteilte Daten ändern, nutzen curl, damit die Senden-Buttons auf dieser Seite keine Datensätze verändern, die andere gerade ansehen. Lektion 6, Requests mit curl und Postman senden, richtet beide Werkzeuge ordentlich ein. Wenn du curl noch nicht hast, lies die curl-Beispiele hier und führ sie nach dieser Lektion aus.
Die fünf alltäglichen Methoden
| Methode | Tut | Typischer Erfolgsstatus |
|---|---|---|
GET | Eine Ressource oder Liste lesen. Sendet keinen Body. | 200 |
POST | Eine neue Ressource anlegen oder eine Aktion auslösen. | 201, manchmal 200 oder 202 |
PUT | Eine Ressource durch den gesendeten Body ersetzen. | 200 oder 204 |
PATCH | Einige Felder einer Ressource ändern. | 200 oder 204 |
DELETE | Eine Ressource entfernen. | 200 oder 204 |
Die Methoden-Endpunkte dieser API nehmen jeweils ein Verb an und beschreiben, was sie bekommen haben. Schick ein GET:
/api/methods/get?x=1Und ein POST mit Body:
/api/methods/post{"hello":"world"}Diese Endpunkte speichern nichts. Um zu sehen, was Methoden mit echten Daten machen, brauchst du eine Ressource, und der Rest dieser Lektion nutzt Bücher.
POST legt an
Leg ein Buch an:
curl -i -X POST https://api.testingapis.com/api/Books \
-H "Content-Type: application/json" \
-d '{"title":"The Pragmatic Programmer","author":"David Thomas",
"isbn":"9780135957059","genre":"programming","publishedYear":2019,
"pages":352,"language":"English","price":42.5,"rating":4.6,
"available":true,"tags":["career"]}'HTTP/1.1 201 Created
Content-Type: application/json; charset=utf-8
{
"message": "Book created successfully",
"data": {
"id": 29,
"title": "The Pragmatic Programmer",
"author": "David Thomas",
"isbn": "9780135957059",
"genre": "programming",
"publishedYear": 2019,
"pages": 352,
"language": "English",
"price": 42.5,
"rating": 4.6,
"available": true,
"tags": ["career"]
}
}Der Server hat mit 201 Created geantwortet, die ID 29 vergeben und den neuen Datensatz zurückgegeben. Du bekommst eine andere ID, weil auch andere Bücher anlegen. Das ist das Erste, was du bei jedem POST testest: Der Server wählt die ID, und dein Test muss sie aus der Antwort lesen, statt sie zu raten.
Viele APIs schicken bei einem 201 auch einen Location-Header mit der URL der neuen Ressource, etwa Location: /api/Books/29anmelden zum Anzeigen. Diese hier nicht. Der Standard verlangt es nicht, aber wenn die Dokumentation es versprochen hätte, wäre es ein Bug.
Führ jetzt denselben POST ein zweites Mal aus. Du bekommst ein zweites Buch mit neuer ID, identisch bis auf diese Zahl. POST ist nicht sicher wiederholbar: Jeder Aufruf erzeugt etwas Neues. Behalte das für die Tabelle weiter unten im Kopf.
PUT ersetzt, PATCH ändert
Beide Methoden aktualisieren einen Datensatz, und sie werden ständig verwechselt. Der Unterschied zeigt sich an den Feldern, die du weglässt.
PATCH
Ändere nur den Preis von Buch 29:
curl -X PATCH https://api.testingapis.com/api/Books/29 \
-H "Content-Type: application/json" \
-d '{"price":39.99}'{
"message": "Book patched successfully",
"changed": ["price"],
"ignored": [],
"data": {
"id": 29,
"title": "The Pragmatic Programmer",
"author": "David Thomas",
"isbn": "9780135957059",
"genre": "programming",
"publishedYear": 2019,
"pages": 352,
"language": "English",
"price": 39.99,
"rating": 4.6,
"available": true,
"tags": ["career"]
}
}Nur der Preis hat sich geändert. Alles, was du nicht erwähnt hast, hat seinen Wert behalten. Diese API sagt dir außerdem, welche Felder sie geändert hat, was ungewöhnlich und fürs Testen praktisch ist.
PUT
Schick jetzt ein PUT mit nur drei Feldern:
curl -X PUT https://api.testingapis.com/api/Books/29 \
-H "Content-Type: application/json" \
-d '{"title":"The Pragmatic Programmer","author":"David Thomas","price":39.99}'{
"message": "Book updated successfully",
"data": {
"id": 29,
"title": "The Pragmatic Programmer",
"author": "David Thomas",
"isbn": "",
"genre": "",
"publishedYear": 0,
"pages": 0,
"language": "",
"price": 39.99,
"rating": 0,
"available": false,
"tags": []
}
}ISBN, Genre, Jahr, Seitenzahl, Sprache, Bewertung und Tags sind weg. PUT bedeutet "hier ist die vollständige neue Version dieser Ressource". Jedes Feld, das du nicht gesendet hast, wurde durch einen leeren Wert oder null ersetzt. Die API hat genau getan, was PUT definitionsgemäß tut, und trotzdem den größten Teil eines Datensatzes gelöscht, weil der Client einen unvollständigen Body geschickt hat.
Das ist einer der häufigsten echten Bugs in Apps. Ein Entwickler nutzt PUT, um ein Feld zu aktualisieren, schickt nur dieses Feld, und alle anderen Felder des Datensatzes werden stillschweigend gelöscht. Wenn du eine Update-Funktion testest, schick einen unvollständigen Body und hol dir den Datensatz danach mit GET, um zu sehen, was übrig ist.
Manche APIs handhaben das anders. Sie behandeln ein PUT mit fehlenden Feldern wie ein PATCH oder lehnen es mit 400 ab, weil Pflichtfelder fehlen. Letzteres ist wohl am sichersten. Worauf du testest, hängt von der Dokumentation ab, aber du testest es immer.
DELETE entfernt
Lösch Buch 29 und versuch dann, es noch einmal zu löschen:
$ curl -X DELETE https://api.testingapis.com/api/Books/29
{"message":"Book deleted successfully"} (200 OK)
$ curl -X DELETE https://api.testingapis.com/api/Books/29
{"status":404,"error":"Not Found","message":"Book with ID 29 does not exist."} (404 Not Found)Der erste Aufruf liefert 200 mit einer Nachricht. Der zweite liefert 404, weil nichts mehr zu löschen ist. Prüf nach einem Löschen auch, dass ein GET auf dieselbe ID 404 liefert. Manche Systeme markieren Datensätze nur als gelöscht und liefern sie weiter aus, was ein Bug ist, wenn die Dokumentation etwas anderes sagt.
Sichere und idempotente Methoden
Zwei Eigenschaften aus dem HTTP-Standard bestimmen, wie Clients und Tests jede Methode behandeln dürfen.
Eine sichere Methode ändert nichts auf dem Server. Du kannst sie so oft aufrufen, wie du willst. Eine idempotente Methode kann etwas ändern, aber zwei Aufrufe haben auf dem Server dieselbe Wirkung wie einer.
| Methode | Sicher | Idempotent |
|---|---|---|
GET | Ja | Ja |
HEAD | Ja | Ja |
OPTIONS | Ja | Ja |
PUT | Nein | Ja |
DELETE | Nein | Ja |
POST | Nein | Nein |
PATCH | Nein | Nicht garantiert |
PUT ist idempotent, weil zweimal derselbe vollständige Datensatz denselben Datensatz hinterlässt. DELETE ist idempotent, obwohl der zweite Aufruf ein 404 bekam: Die Antwort war anders, aber der Zustand des Servers (Buch 29 gibt es nicht) ist in beiden Fällen gleich. Bei Idempotenz geht es um die Wirkung, nicht um die Antwort. POST ist nicht idempotent, wie du gesehen hast, als der zweite POST ein zweites Buch angelegt hat. PATCH kann so oder so sein. Den Preis auf 39.99 zu setzen, ist idempotent; ein Patch, der sagt "erhöhe den Bestand um 1", nicht.
Das ist wichtig, weil Netzwerke ausfallen. Läuft ein Request in einen Timeout, weiß der Client nicht, ob der Server ihn bekommen hat. Ein GET oder PUT zu wiederholen, ist harmlos. Ein POST zu wiederholen, kann eine Karte doppelt belasten. Browser, Proxys und HTTP-Bibliotheken nutzen diese Regeln, wenn sie entscheiden, ob sie von selbst wiederholen, und Timeouts, Retries und Rate Limits zeigt, wie APIs POST mit Idempotency Keys sicher wiederholbar machen.
Beim Testen liefern dir diese Regeln Testfälle gratis. Ruf ein GET auf und prüf, dass sich nichts geändert hat. Ruf ein PUT zweimal auf und prüf, dass der zweite Aufruf nichts angelegt oder verdoppelt hat. Ein GET, das Daten ändert (wie /api/deleteUser?id=5anmelden zum Anzeigen, das es in manchen Codebasen tatsächlich gibt), ist ein Designfehler, denn Crawler, Link-Vorschauen und das Prefetching im Browser schicken alle GET-Requests, ohne zu fragen.
HEAD und OPTIONS
HEAD ist ein GET ohne Body. Der Server schickt denselben Status und dieselben Header wie bei einem GET und hört dort auf. Clients nutzen es, um zu prüfen, ob etwas existiert oder wie groß eine Datei ist, ohne sie herunterzuladen.
HTTP/1.1 200 OK
x-message: HEAD request received. Responses to HEAD carry headers only.
x-method: HEADOPTIONS fragt, was unter einer URL erlaubt ist. Browser schicken es automatisch vor manchen Cross-Origin-Requests (ein "Preflight"), deshalb taucht es in Netzwerk-Logs auf, auch wenn kein Code es aufruft. Du wirst es selten direkt testen, außer du arbeitest an CORS-Einstellungen.
/api/methods/optionsDie falsche Methode verwenden
/api/methods/get nimmt nur GET an. Schick ihm ein POST mit curl -i -X POST https://api.testingapis.com/api/methods/get:
HTTP/1.1 405 Method Not Allowed
Content-Length: 0
allow: GET405 Method Not Allowed bedeutet, dass die URL existiert, diese Methode aber nicht unterstützt. Der Header allow listet die, die sie unterstützt. Ein POST auf ein einzelnes Buch, was keinen Sinn ergibt, weil Bücher unter /api/Booksanmelden zum Anzeigen angelegt werden, wird genauso behandelt:
HTTP/1.1 405 Method Not Allowed
Content-Length: 0
allow: DELETE, GET, HEAD, PATCH, PUTJede Methode an jeden Endpunkt zu schicken, ist ein billiger Test, der echte Probleme findet. Du suchst nach Methoden, die abgelehnt werden sollten, es aber nicht werden (ein DELETE, das an einem eigentlich schreibgeschützten Endpunkt funktioniert), und nach 500ern oder 404ern, wo ein 405 hingehört. Beachte, dass diese API das 405 mit leerem Body liefert, während ihre anderen Fehler JSON sind. Clients, die bei jedem 4xx einen JSON-Fehler-Body erwarten, stolpern darüber.
Übung
Arbeite das mit curl durch (oder komm nach der curl-Lektion darauf zurück).
- Leg mit POST ein Buch an. Notier dir seine ID.
- Ändere mit PATCH den Titel. Hol es mit GET und bestätige, dass sich nur der Titel geändert hat.
- Schick ein PUT mit einem Body, der jedes Feld außer
tagsenthält. Was ist mit den Tags passiert? - Schick dasselbe PUT noch einmal. Hat sich beim zweiten Mal etwas geändert?
- Lösch es mit DELETE, hol es mit GET und lösch es noch einmal. Notier dir alle drei Statuscodes.
- Schick ein DELETE an
/api/Booksanmelden zum Anzeigen (ohne ID). Sag vorher den Statuscode voraus. Prüf dann den Headerallow.