Zum Hauptinhalt springen

HTTP kompakt

HTTP ist die Sprache, in der Client und Server miteinander reden. Jeder Aufruf einer REST-API ist eine HTTP-Nachricht. Wer HTTP versteht, kann jede REST-API lesen.

(Andere Baustile sind nicht darauf festgelegt — SOAP kann seine Nachrichten auch über andere Wege schicken. Für REST ist HTTP nicht Beiwerk, sondern Teil des Konzepts.)

Ein Aufruf besteht aus zwei Nachrichten

ClientServerErster AufrufRequest — die AnfrageResponse — die AntwortAnfrage abgeschlossenZweiter Aufruf — beginnt bei nullRequest — die AnfrageResponse — die AntwortDer Server erinnert sich an den ersten Aufruf nicht — das ist Zustandslosigkeit

Mehr passiert nicht. Der Client fragt, der Server antwortet, die Anfrage ist damit abgeschlossen. Für die nächste Frage beginnt alles von vorn — genau das meint Zustandslosigkeit.

Abgeschlossen heißt nicht „Leitung gekappt"

Die Verbindung bleibt in der Regel bestehen: HTTP/1.1 hält sie offen, damit nicht für jedes Bild und jede Anfrage neu verbunden werden muss.

Zustandslosigkeit ist eine Aussage über etwas anderes — über das Gedächtnis des Servers. Er merkt sich zwischen zwei Anfragen nichts über den Gesprächsverlauf, auch wenn beide über dieselbe Verbindung laufen.

Aufbau einer Anfrage

POST /api/v1/persons HTTP/1.1 ← Startzeile: Methode, Pfad, Version
Host: localhost:8080 ┐
Content-Type: application/json │ Header (Kopfzeilen)
Accept: application/json ┘
← Leerzeile trennt Kopf und Rumpf
{"firstname":"Anna","surname":"Schmidt"} ← Body (Rumpf, optional)
TeilBedeutung
MethodeWas soll passieren? GET, POST, PUT, DELETE
PfadWomit? Die Adresse der Ressource
HeaderZusatzangaben — Datenformat, Anmeldung, Sprache …
BodyDie mitgeschickten Daten. Bei GET und DELETE normalerweise leer

Aufbau einer Antwort

HTTP/1.1 201 Created ← Statuszeile: Code und Kurztext
Content-Type: application/json ┐ Header
Location: /api/v1/persons/1 ┘
← Leerzeile
{"firstname":"Anna","id":1,"surname":"Schmidt"} ← Body

Die HTTP-Methoden

MethodeZweckCRUDBody in der Anfrage?
GETDaten abrufenReadnein
POSTNeue Ressource anlegenCreateja
PUTRessource vollständig ersetzenUpdateja
PATCHRessource teilweise ändernUpdateja
DELETERessource löschenDeletenein
PUT oder PATCH?

PUT schickt den kompletten neuen Stand — fehlende Felder werden geleert. PATCH schickt nur die Änderung — alles andere bleibt.

Für eine Person mit Vor- und Nachnamen:

  • PUT mit {"firstname":"Anna"} → der Nachname wäre danach leer.
  • PATCH mit {"firstname":"Anna"} → der Nachname bleibt stehen.

In diesen Tutorials verwenden wir PUT.

Statuscodes

Jede Antwort trägt einen dreistelligen Code. Die erste Ziffer sagt schon das Wichtigste:

1xxInformationMoment noch …selten2xxErfolgHat geklappt.200 · 201 · 2043xxUmleitungSchau woandersnach.4xxClient-FehlerDu hast Mist gebaut.400 · 404 · 4055xxServer-FehlerIch habe Mist gebaut.500alles in Ordnungetwas ist schiefgegangenDie wichtigste Frage im Fehlerfall: Liegt es an mir oder am Server?
GruppeBedeutungMerkhilfe
1xxInformation„Moment noch…"
2xxErfolg„Hat geklappt."
3xxUmleitung„Schau woanders nach."
4xxFehler beim ClientDu hast Mist gebaut."
5xxFehler beim ServerIch habe Mist gebaut."
Die wichtigste Unterscheidung

4xx heißt: Es lag an der Anfrage — falsche Adresse, fehlende Daten, keine Berechtigung. Dieselbe Anfrage unverändert zu wiederholen hilft meist nicht.

„Meist", weil der genaue Code entscheidet: Nach einer Anmeldung verschwindet ein 401, nach dem Ablauf einer Sperre ein 429, und ein 404 kann sich in ein 200 verwandeln, sobald jemand den Datensatz anlegt.

5xx heißt: Der Server ist schuld. Dieselbe Anfrage kann gleich schon funktionieren.

Die Codes, die du brauchst

CodeTextWann
200OKAnfrage erfolgreich, Antwort enthält Daten
201CreatedRessource wurde angelegt. Sollte im Location-Header die Adresse der neuen Ressource mitschicken
204No ContentErfolgreich, aber es gibt nichts zurückzugeben — typisch nach DELETE
400Bad RequestDie Anfrage ist fehlerhaft aufgebaut
401UnauthorizedNicht angemeldet. (Der Name ist irreführend — es geht um Authentifizierung.)
403ForbiddenAngemeldet, aber nicht berechtigt
404Not FoundDie angeforderte Ressource gibt es nicht
405Method Not AllowedRessource existiert, aber nicht mit dieser Methode
406Not AcceptableDer Server kann kein Format liefern, das der Client im Accept-Header verlangt
409ConflictWiderspruch zum aktuellen Zustand, z. B. E-Mail schon vergeben
415Unsupported Media TypeDer Server versteht das Format des mitgeschickten Bodys nicht
500Internal Server ErrorUnerwarteter Fehler im Server
Häufiger Fehler

Ein Server, der bei einem nicht gefundenen Datensatz 200 OK mit dem Inhalt null zurückgibt.

Der Client muss dann raten, ob alles gut ging. Genau dafür gibt es 404.

Wichtige Header

HeaderRichtungBedeutung
Content-TypebeideIn welchem Format ist der Body dieser Nachricht?
AcceptAnfrageIn welchem Format hätte ich die Antwort gern?
AuthorizationAnfrageAnmeldedaten, meist ein Token
LocationAntwortAdresse der neu angelegten Ressource (bei 201)
Content-Type und Accept verwechselt man leicht

Content-Type beschreibt, was ich mitschicke. Accept beschreibt, was ich zurückhaben will.

Bei einem POST stehen oft beide in der Anfrage: „Ich schicke JSON und möchte JSON zurück."

Content Negotiation: mehrere Formate

Content-Type und Accept sind kein Selbstzweck. Über sie handeln Client und Server aus, in welchem Format die Daten übertragen werden. Dieses Aushandeln heißt Content Negotiation (Inhaltsverhandlung).

Solange eine API nur JSON kennt, gibt es nichts zu verhandeln. Interessant wird es, sobald ein Endpunkt mehrere Formate bedienen kann — etwa JSON für die App, CSV für den Export in die Tabellenkalkulation und XML für einen älteren Partnerdienst:

AppAccept: application/jsonExcel-ExportAccept: text/csvPartnerdienstAccept: application/xmlein einzigerEndpunktGET /personsJSONfür ProgrammeCSVfür TabellenkalkulationXMLfür ältere Systeme

Dieselbe Ressource, dieselbe URL — das Format bestimmt der Client über den Accept-Header. In Spring gibt man die möglichen Formate am Endpunkt an:

@GetMapping(value = "/{id}",
produces = { MediaType.APPLICATION_JSON_VALUE,
MediaType.APPLICATION_XML_VALUE })
public Person getPersonById(@PathVariable Long id) { ... }

Für die Gegenrichtung — welche Formate der Endpunkt annimmt — gibt es consumes:

@PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE)

Was passiert, wenn das Format nicht passt?

Zwei Statuscodes gehören genau hierher, und sie sind leicht zu verwechseln:

CodeWer hat das falsche Format?Auslöser
415 Unsupported Media TypeDer Client schickt etwas, das der Server nicht lesen kannContent-Type: text/plain bei einem JSON-Endpunkt
406 Not AcceptableDer Server kann nicht liefern, was der Client verlangtAccept: application/xml bei einem JSON-Endpunkt

Merkhilfe: 415 betrifft die Hinfahrt (den Request-Body), 406 die Rückfahrt (die Antwort).

Probiere es aus

Beide Codes kannst du an deiner eigenen Anwendung auslösen, ohne eine Zeile zu ändern:

# 415 – falsches Format geschickt
curl -i -X POST http://localhost:8080/api/v1/persons \
-H "Content-Type: text/plain" -d "abc"

# 406 – unmögliches Format verlangt
curl -i -H "Accept: application/xml" http://localhost:8080/api/v1/persons/1

Interessant dabei: Deine Endpunkte tragen kein consumes und kein produces — und liefern die richtigen Codes trotzdem. Spring erzwingt den Vertrag also schon von sich aus, weil JSON das einzige eingerichtete Format ist.

Genau deshalb sind die beiden Angaben im Normalfall verzichtbar. Sie werden erst nötig, wenn es wirklich mehr als ein Format gibt.

Fehlerantworten nach RFC 9457

Für Fehler gibt es ein standardisiertes Antwortformat namens Problem Details. Es ist am Content-Type application/problem+json erkennbar:

{
"type": "about:blank",
"title": "Not Found",
"status": 404,
"detail": "Keine Person mit der Id 99",
"instance": "/api/v1/persons/99"
}
FeldBedeutung
typeURI, die den Fehlertyp beschreibt
titleKurze, allgemeine Bezeichnung
statusDer HTTP-Statuscode, noch einmal im Rumpf
detailDie konkrete, auf diesen Fall bezogene Meldung
instanceWelche Anfrage den Fehler ausgelöst hat

Der Vorteil gegenüber einer selbst erfundenen Struktur: Andere Programme und Werkzeuge kennen dieses Format bereits.

HTTP sichtbar machen

WerkzeugWofür
Browser-AdresszeileNur GET. Schnellster Weg für einen Blick
Entwicklerwerkzeuge (F12), Reiter NetzwerkZeigt zu jeder Anfrage Methode, Status, Header und Body
.http-Datei in der IDERequests als Textdatei speichern und ausführen — versionierbar
PostmanEigenständiges grafisches Werkzeug, in Betrieben verbreitet. Sammelt Anfragen in „Collections". In diesen Tutorials nicht nötig — was es kann, decken die .http-Datei und die Swagger-Oberfläche ab. Wenn es dir im Betrieb begegnet: Es ist dasselbe HTTP darunter.
curlKommandozeile, überall vorhanden, gut für Skripte

Dieselbe Anfrage in drei Werkzeugen:

### .http-Datei
POST http://localhost:8080/api/v1/persons
Content-Type: application/json

{"firstname":"Anna","surname":"Schmidt"}
# curl
curl -X POST http://localhost:8080/api/v1/persons \
-H "Content-Type: application/json" \
-d '{"firstname":"Anna","surname":"Schmidt"}'
Das hast du gelernt
  • Eine HTTP-Nachricht besteht aus Startzeile, Headern, Leerzeile und Body.
  • Die Methode sagt, was passieren soll; der Pfad, womit.
  • Die erste Ziffer des Statuscodes trennt 4xx (Client schuld) von 5xx (Server schuld).
  • Content-Type beschreibt das Mitgeschickte, Accept das Gewünschte.
  • Über diese beiden Header handeln Client und Server das Format aus (Content Negotiation). 415 meint ein unlesbares Format auf der Hinfahrt, 406 ein unmögliches auf der Rückfahrt.
  • Fehler werden standardisiert als Problem Details (application/problem+json) zurückgegeben.

Weiterlesen