Eine Antwort lesen

Statuscodes, Antwort-Header und der Body.

Jeder Request bekommt genau eine Antwort (außer die Verbindung bricht ab, was eine eigene Art von Antwort ist und in einer späteren Lektion vorkommt). Eine Antwort hat drei Teile: eine Statuszeile, Header und einen Body. Wer neu im Testen ist, schaut meist nur auf den Body, weil dort die Daten stehen. Die meisten Bugs, die dabei durchrutschen, stecken in den anderen beiden Teilen.

Wie eine Antwort aussieht

Wie der Request ist auch eine HTTP/1.1-Antwort reiner Text. Das ist die Antwort auf GET /api/health, so wie curl sie mit der Option -i zeigt (die die Header zusammen mit dem Body ausgibt). Ein paar Infrastruktur-Header sind entfernt.

Rohe Antwort
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Transfer-Encoding: chunked
x-powered-by: TestingAPIs

{"status":"healthy","uptimeSeconds":3542,"timestamp":"2026-09-30T18:28:12Z"}

Die erste Zeile ist die Statuszeile: Protokollversion, ein dreistelliger Statuscode und eine kurze Begründung. Dann folgen die Header, einer pro Zeile, dann eine leere Zeile, dann der Body. Die Begründung ist nur für Menschen. Clients und Tests sollten auf die Zahl schauen. HTTP/2 lässt die Begründung ganz weg, ein Test, der den Text "OK" prüft, kann also brechen, wenn der Server aktualisiert wird.

Klassen von Statuscodes

Die erste Ziffer des Statuscodes sagt dir die Klasse der Antwort. Oft kannst du ein Ergebnis schon an dieser Ziffer beurteilen.

KlasseBedeutungWer schuld ist
1xxInformation. Der Request läuft noch.Niemand. Du siehst sie selten direkt.
2xxErfolg. Der Server hat getan, worum du gebeten hast.Niemand.
3xxUmleitung. Schau woanders nach.Niemand. Der Client muss nachfassen.
4xxClientfehler. Der Request war falsch.Der Client: falsche Eingabe, keine Berechtigung, falsche URL.
5xxServerfehler. Der Request war vielleicht in Ordnung.Der Server oder etwas dahinter.

Diese letzte Spalte ist der Grund, warum Statuscodes beim Testen so wichtig sind. Ein 4xx sagt "korrigier deinen Request". Ein 5xx sagt "bei uns ist etwas kaputtgegangen". Wenn du Unsinn schickst und ein 500 bekommst, ist das ein Bug, obwohl der Request schlecht war, denn der Server ist abgestürzt, wo er die Eingabe höflich mit einem 400 hätte ablehnen sollen. Diese 500er zu finden, ist eines der ergiebigsten Dinge, die du als Neuling tun kannst.

Die Codes, die du am häufigsten siehst

CodeNameTypische Verwendung
200OKDer Request hat funktioniert, und der Body enthält das Ergebnis.
201CreatedEin POST hat einen neuen Datensatz angelegt.
204No ContentHat funktioniert, und es gibt absichtlich keinen Body. Häufig nach DELETE.
301 / 302Moved Permanently / FoundDie Ressource liegt unter der URL im Location-Header.
304Not ModifiedDeine Kopie im Cache ist noch aktuell. Kommt beim Caching dran.
400Bad RequestDer Request ist fehlerhaft aufgebaut oder ein Wert ist ungültig.
401UnauthorizedKeine oder falsche Zugangsdaten. Trotz des Namens bedeutet es: nicht authentifiziert.
403ForbiddenDie Zugangsdaten stimmen, aber dieser Nutzer darf das nicht.
404Not FoundNichts unter dieser URL oder kein Datensatz mit dieser ID.
405Method Not AllowedDie URL existiert, aber nicht mit dieser Methode.
409ConflictDer Request kollidiert mit dem aktuellen Zustand, etwa eine doppelte E-Mail.
422Unprocessable ContentDie Syntax stimmt, aber die Daten verletzen eine Regel.
429Too Many RequestsDu hast ein Rate Limit erreicht. Mach langsamer.
500Internal Server ErrorEin unbehandelter Fehler auf dem Server.
502Bad GatewayEin Proxy hat eine schlechte Antwort vom Server dahinter bekommen.
503Service UnavailableDer Server ist überlastet oder wird gewartet.

Bei manchen davon sind sich APIs uneins. Ein Team liefert 400 für jeden Validierungsfehler, ein anderes 422. Manche antworten auf ein Löschen mit 200 und einer Nachricht im Body, andere mit 204 ohne alles. Beides ist nicht falsch. Was du prüfst, ist, dass die API ihrer eigenen Dokumentation entspricht und von Endpunkt zu Endpunkt konsistent ist.

Statuscodes ausprobieren

/api/status/{code} antwortet mit dem Code, den du in den Pfad schreibst. Das ist praktisch, um zu sehen, wie sich ein Werkzeug oder eine Client-Bibliothek bei einem bestimmten Status verhält. Probier ein paar aus:

GET/api/status/201
GET/api/status/503

In curl sieht der 503 so aus:

Antwort
HTTP/1.1 503 Service Unavailable
Content-Type: application/json; charset=utf-8
retry-after: 5

{
  "status": 503,
  "statusText": "Service Unavailable",
  "category": "server-error",
  "message": "This is a 503 response. Use /api/status/{code} to request any status."
}

Achte auf den Header retry-after: 5. Ein 503 oder 429 sagt dem Client oft, wie viele Sekunden er warten soll, bevor er es erneut versucht, und ein gut erzogener Client hält sich daran. Du prüfst, dass der Header da ist, nicht nur den Status.

Codes außerhalb des gültigen Bereichs werden abgelehnt. /api/status/999 kommt als 400 zurück, mit der Nachricht Status code must be between 200 and 599 (1xx cannot be sent as a final response). Und /api/status/abc liefert ein 404 mit leerem Body, weil abc gar nicht zur Route passt. Zwei schlechte Eingaben, zwei verschiedene Arten von Fehlschlag. Dieses Muster begegnet dir oft: Ein Wert vom falschen Typ scheitert meist schon in der Routing-Schicht, bevor der Anwendungscode überhaupt läuft.

Umleitungen

Eine 3xx-Antwort hat meist keinen nützlichen Body. Wichtig ist der Header Location, der sagt, wohin es als Nächstes geht:

Antwort auf GET /api/status/302
HTTP/1.1 302 Found
Content-Type: application/json; charset=utf-8
location: /api/echo

Browser und die meisten HTTP-Bibliotheken folgen Umleitungen automatisch und zeigen dir nur die letzte Antwort. Das ist bequem, bis du die Umleitung selbst testen musst. curl folgt ihnen nur, wenn du -L angibst, und ist dafür daher das bessere Werkzeug.

Antwort-Header

Antwort-Header beschreiben den Body und geben dem Client Anweisungen. Frag nach Seite 3 der Produktliste, zwei Produkte pro Seite:

GET/api/Products?limit=2&page=3
Antwort-Header
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
x-page: 3
x-per-page: 2
x-total-count: 20
x-total-pages: 10

Content-Type sagt, dass der Body JSON in UTF-8 ist. Die vier Header x-page und x-total tragen die Details der Paginierung: Es gibt 20 Produkte, 10 Seiten, und das ist Seite 3. Der Body ist nur ein Array aus zwei Produkten, ein Client, der diese Header ignoriert, kann also nicht wissen, dass es mehr gibt. Header, die mit X- beginnen, sind eigene Erfindungen der API. Dokumentiert werden sie von der API, nicht vom HTTP-Standard.

Manche Header enthalten Zeiten als Unix-Timestamps: die Zahl der Sekunden seit dem 1. Januar 1970, UTC. APIs mit Rate Limits schicken oft einen in x-ratelimit-reset, um zu sagen, wann dein Kontingent zurückgesetzt wird. Rechne ihn um, bevor du ihm traust: Ein Reset-Zeitpunkt in der Vergangenheit oder in Jahren ist ein Bug, den du nur findest, wenn du Header liest, statt sie zu überspringen. (Der Timestamp-Konverter übernimmt die Umrechnung.)

Vielleicht erwartest du einen Header Content-Length. Diese API schickt stattdessen meist Transfer-Encoding: chunked, das heißt, der Body wird in Stücken gesendet und die Gesamtgröße nicht vorab genannt. Beides ist gültig. Eine Antwort hat das eine oder das andere, nicht beides.

Der Body

Der Body sind die Daten: ein Produkt, eine Liste, eine Fehlermeldung, ein Bild. Content-Type sagt, wie er zu lesen ist. Die meisten deiner Prüfungen betreffen den Body, und JSON lesen behandelt das ausführlich. Vorher zwei Sonderfälle.

Gar kein Body

Ein 204 bedeutet Erfolg ohne Body, und zwar absichtlich. Die Antwort hat auch keinen Content-Type, weil es nichts zu beschreiben gibt:

GET/api/formats/no-content
Dieser Request nutzt einen Endpunkt für angemeldete Mitglieder.AnmeldenKostenloses Konto erstellen
Antwort
HTTP/1.1 204 No Content
x-powered-by: TestingAPIs

Client-Code, der immer response.json() aufruft, stürzt hier ab, weil ein leerer String kein gültiges JSON ist. Wenn du Test-Helfer schreibst, die jede Antwort parsen, müssen sie mit einem leeren Body umgehen können.

Wenn die Header lügen

Status, Header und Body werden von verschiedenen Teilen des Servers erzeugt, und nichts zwingt sie, übereinzustimmen. Hier ist eine Antwort, die behauptet, HTML zu sein:

GET/api/formats/wrong-content-type
Dieser Request nutzt einen Endpunkt für angemeldete Mitglieder.AnmeldenKostenloses Konto erstellen
Antwort
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8

{"id":1,"name":"Alice Johnson","email":"alice@example.com","roles":["admin","editor"],"active":true,"score":98.5,"manager":null}

Der Body ist einwandfreies JSON. Ein Mensch, der ihn liest, würde nie ein Problem bemerken. Ein Browser würde versuchen, ihn als Webseite darzustellen, und manche HTTP-Bibliotheken weigern sich, ihn als JSON zu parsen, oder geben ihn als String zurück. Dieser Endpunkt ist absichtlich kaputt. In echten Systemen kommt derselbe Bug meist von einer Fehlerseite, die ein Proxy oder eine Framework-Voreinstellung erzeugt. Dein Test sollte den Content-Type ausdrücklich prüfen.

Das Spiegelbild gibt es auch: ein Status 200 mit einem Body, der {"success": false} sagt. Manche APIs melden jeden Fehler so. Monitoring-Werkzeuge, die 5xx-Antworten zählen, sehen diese Fehler nie, und ein Test, der nur den Statuscode prüft, auch nicht. Wenn eine API das tut, müssen deine Tests bei jeder Antwort den Body lesen.

Was du bei jeder Antwort prüfst

Bevor du dir ein einziges Datenfeld ansiehst, bestätige die Grundlagen. Das dauert ein paar Sekunden und fängt erstaunlich viele Bugs.

  • Der Statuscode ist der, den die Dokumentation für diese Situation verspricht.
  • Content-Type passt zu dem Body, den du tatsächlich bekommen hast.
  • Header, die die Dokumentation erwähnt (Paginierung, Location, Retry-After), sind vorhanden und ergeben Sinn.
  • Der Body lässt sich parsen und hat die Form, die du erwartest.
  • Fehlerantworten folgen demselben Format wie andere Fehler dieser API.

Übung

Notier dir für jeden Request den Statuscode, den Content-Type und einen Satz zum Body.

  • /api/status/418. Schlag nach, warum es diesen Code gibt.
  • /api/formats/emptyanmelden zum Anzeigen. Was unterscheidet ihn von /api/formats/no-contentanmelden zum Anzeigen? Einer liefert 200, der andere 204. Mit welchem kommt ein Client deiner Meinung nach besser zurecht?
  • /api/Products?limit=2&page=99. Es gibt keine Seite 99. Welchen Status und Body hast du bekommen, und was sagen die Paginierungs-Header? Hättest du es genauso entworfen?
  • /api/status/list. Such in der Liste einen Code mit "supported": false und versuch, ihn anzufordern.

Üben und weitergehen