HTTP-Statuscode in Microservices

mental.dIseASe

Lieutenant
Registriert
Dez. 2008
Beiträge
676
Hallo zusammen,

ich habe einen Microservice, der im Kontext eines angemeldeten Anwenders eine Singleton-REST-Ressource verwaltet, nennen wir sie /configuration. Das heißt, jeder angemeldete Anwender kann maximal eine Konfiguration haben, er hat also entweder eine oder er hat keine (eine Default-Konfiguration gibt es übrigens nicht). Ich habe es jetzt so umgesetzt, dass man mit GET, POST und PUT die Konfiguration abfragen, erstellen oder anpassen kann. Ferner habe ich es jetzt so umgesetzt, dass GET darauf einen 404 zurückgibt, wenn der angemeldete Anwender (noch) keine Konfiguration hat. Soweit so RESTig.

Ein Consumer dieser Ressource beschwert sich jetzt, dass da in diesem Fall 404 zurückkommt und der Betrieb beschwert sich, dass in den Logs soviele 404er stehen (die von einem Servlet-Filter, über den ich keine Kontrolle habe, geloggt werden). Ich stehe jetzt auf dem Standpunkt, dass ein 404er erstmal nichts Schlimmes ist, sondern nach meinem Verständnis so REST bzw. HTTP funktionieren und der Consumer einfach drauf reagieren muss. Vielmehr denke ich, dass genannter Servlet-Filter nicht so aufgeregt rumschreien sollte.

Wie würdet ihr damit umgehen? Nachgeben und stattdessen 200 mit 'ner Dummy-Payload zurückgeben? Oder darauf drängen, dass die vorgeschriebenen REST-Utilities nicht so aufgeregt mit 4xx-Statuscodes umgehen?

beste Grüße
 
Hmm, haben vermutlich beide Seiten irgendwo Recht.
/something/42 kann bedeuten, dass etwas (noch) nicht da ist (z.B. durch Race Conditions, Replication Latency etc.), kann aber auch bedeuten, dass es am Client ein Problem gibt.
/something vs `/somehting ist z.B. eher ein Beispiel für zweiteres.
Aus Monitoring-Sicht macht daher eine Anomalie-Erkennung auf erhöhte 4xx er Fehler durchaus Sinn.
Eine Rückgabe von 404 aus REST-API Sicht macht aber IMHO genau so Sinn und ist IMHO auch best practice.

Bleibt die Frage, wer sich bewegt.
Wenn mit allen Clients ein 404 gemäß Protokoll vereinbart ist, müsste man alle Clients anpassen. Da müsste sich IMHO das Monitoring (als nachgelagertes Hilfssystem) hinten anstellen und müsste das über Filter etc. (ggf. ein weiteres Attribut "log-level" am Log-Eintrag?) lösen.

Wenn es im Unternehmen hingegen eine Vorgabe gibt und die Anpassung der Clients keine größeren Probleme macht, kann man um des Friedens willen z.B. auch auf 204 oder einen 200 mit "error: no content" ausweichen (seh ich bei SOAP öfter) - was allerdings das Monitoring (nur) anhand von Status-Codes auch wieder in Frage stellt.

Aus Log-Sicht gewinnst du weder mit der einen noch der anderen Variante
Code:
/something/42 404 (correct route, but no content)
/somehting/42 404 (wrong route, no content)
vs
/something/42 200 (correct route, no content)
/somehting/42 404 (wrong route, no content)
Besser die 404 noch unterscheiden:
Code:
/something/42 404 operation=something (correct route! less critical error)
/somehting/42 404 operation=<null> (invalid route! need to check client)
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: BeBur
Rein vom Verhalten ist ein 404 an der Stelle richtig, aber nachvollziehbar nervig ist es dennoch.

Welche Fehlercodes wirft der Server beim Zugriff auf illegale bzw. nicht vorhandene, sonstige Ressourcen? Wenn da genauso 404s geschmissen werden, ist es wirklich ungünstig wenn die definierte Ressource /configuration das selbe Verhalten im normalen Betrieb zeigt.

Mein Ansatz wäre da ein Default zu setzen, im Zweifelsfall ist /configuration halt schlicht leer. Ein GET auf die Ressource bekommt die 200 zurück und auf Clientseite lässt sich "empty" auch leicht behandeln.

Die 204 als Rückgabewert ist Imho unpassend. Das wäre die passende Antwort auf PUT, DELETE, aber nicht auf GET. https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/204


Edit: An sich wäre ein Default, der nicht leer, ist noch etwas besser. Eine Leere Rückgabe kann ja auch bedeuten, dass beim Server irgendwas im Backend nicht funktioniert. Daher wäre es wahrscheinlich sinnvoll das Default von /configuration mit eindeutigem Inhalt zu füllen. Also sowas wie "No configuration". Von sowas wie 0, Null, \0, etc. würde ich abraten, das ist wieder zu nah dran an einem Server der sich verschluckt ;).
 
Zuletzt bearbeitet:
2xx Bereich hat geklappt, was auch immer
4xx Bereich Fehler beim Clienten
5xx Bereich Fehler beim Server

Was hättens denn gerne ??
 
  • Gefällt mir
Reaktionen: Bagbag, Ginlock, dermoritz und eine weitere Person
ich finde 404 exakt das richtige. die resource existiert nicht. in der message kannst dann mehr schrieben "für user - der der die Anfrage erstellt hat - wurde keine coinfig gefunden. bitte erst erstellen" was auch immer du denkst was den Kleinten helfen könnte. kannst auch eine URL für das post mitgeben.

Ich gehe davon aus, das nach einem post man eine id oder was auch immer bekommt um das get korrekt auszuführen. also der Klient die Chance hat ein korrektes get abzusetzen.

wenn man leere Konfigurationen erstellen kann wäre das der Fall für 204.
 
204 würde ich liefern, wenn ich so etwas wie einen asynchronen Prozess starte, der dann mir demnächst ne Email schreibt, das er fertig ist

Der 4er Bereich sind wirkliche Bedienfehler der Schnittstelle wie in etwa 405, wenn ein GET statt POST gemacht wurde
Ergänzung ()

Ist Keine Konfig also ein regulärer, valider Use Case oder eine falsche Bedienung?
 
Die Rückgabe einer leeren Menge mit http 200 OK wäre IMHO semantisch sehr sauber. (Erinnere dich an die Mengenlehre in der Grundschule)

Beispiel von wo anders: Eine SQL-Query gibt ja auch keinen speziellen Returncode zurück wenn eine Query keine Treffer hat.
 
Ist nicht meine starke Seite, aber meine Intuition ist, dass 404 nicht nur bedeutet "hier ist nichts", sondern "hier sollte eigentlich etwas sein, aber aus unbekannten Gründen ist hier nichts".
Ganz blödes Beispiel: Wenn ich was suche und es keine Treffer gibt, dann erwarte ich keinen 404, sondern ein "hat geklappt, 0 Ergebnisse".
 
@BeBur
Nein, die Ressource, die du zum Suchen aufgerufen hast war ja erfolgreich erreichbar.

Der TE hat halt eine Ressource definiert, die vorhandensein kann, aber als Default nicht vorhanden ist. Entsprechend ist die 404 durchaus korrekt, der Client wollte eine Ressource haben, die es als Standard nicht gibt.
Der Ansatz vom TE, die Ressource so zu definieren, das sie unter Umständen nicht gibt, ist halt ungünstig.
 
In einer API-Definition hast Du eigentlich immer einen primären Ressourcen Identifier; dieser kann eine künstliche ID sein oder - wenn ich das richtig verstehe - eben der Username
Normalerweise holt man sich so einen Identifier mit einem ersten Request und gibt diesen dann immer wieder mit .. das wäre dann das S REST - Stateless das ist aber ein Kontext, wie oben beschrieben ehr nicht.

Zu diesem Identifier gibt es dann die angeforderte Ressource und die kann ja auch tatsächlich leer sein.

Eigentlich definiert man den Zugriff auf solche Ressourcen auch immer die möglichen Rückgabe HTTP Codes und was sie weiter bedeuten und am besten sogar mit Beispielen; Stichwort hier ist OpenApiSpecification.

Es kann durchaus valide sein eine 200 zu liefern, wenn ein JSON angefordert / erwartetet wird und der Client mit einem leeren, aber gültigem JSON was anfangen kann vor allem dann, wenn er es erwartet; ein {} ist tatsächlich also eine gültige Rückgabe mit 200 weil das bereits ein gültiges JSON ist; per definition bestimmst DU denn, ob einer leere Ressource eine gültige Ressource ist ( bei Datenbankabfragen ist so etwas ja der Fall, wenn eine Abfrage kein Ergebnis bringt).

Ebenso korrekt kann sein eine 204 ohne weiteres zurückzugeben; wäre dann sogar einfacher für den Clienten, weil er sich die Auswertung des Payloads spart.

Es kann auch richtig sein, das dies ein Bedienfehler der API ist, von daher wäre eine 4xx Code auch richtig; weil hier wird eine Ressource angefragt wurde mit einem Identifier (Username) der nicht existiert.; macht also nicht wirklich Sinn und ist vermutlich auch nicht beabsichtigt.

Ganz klar wird das, wenn ich z.B, die aktuelle Uhrzeit per Ländercode anfragen würde

GET time/DE
GET time/US

GET time/NULL

NULL ist eindeutig ein falscher Ländercode, da scheint der Programmierer nicht aufgepasst zu haben, ist also eine falsche Bedienung, die beim Client auch im log aufschlagen sollte mindestens auf Level WARNING ;

Würde der GET /time/NULL einen 200er Code liefern .. ich denke, ich wäre sehr lange auf der Suche, was denn da in meinem Programm an vermutlich völlig anderer Stelle passiert.

Eine Definition einer API stellt also einen Vertrag dar, die eine Seite weiß, was sie zu erwarten hat und die andere Seite erfüllt diese Erwartung.

Ich sag immer meinen Kollegen "Hier schaut euch die Api-Defintion an, wenn ihr das nicht versteht, dann habe ich einen Fehler geamacht, weil ich sie nicht eindeutig definiert habe."
 
  • Gefällt mir
Reaktionen: BeBur
KeepCalm schrieb:
Ganz klar wird das, wenn ich z.B, die aktuelle Uhrzeit per Ländercode anfragen würde

GET time/DE
GET time/US

GET time/NULL

NULL ist eindeutig ein falscher Ländercode, da scheint der Programmierer nicht aufgepasst zu haben, ist also eine falsche Bedienung, die beim Client auch im log aufschlagen sollte mindestens auf Level WARNING ;

Würde der GET /time/NULL einen 200er Code liefern .. ich denke, ich wäre sehr lange auf der Suche, was denn da in meinem Programm an vermutlich völlig anderer Stelle passiert.
Ganz davon ab das Uhrzeiten die anders als time_t definiert sind der Weg in die Hölle sind und Uhrzeiten nur vor direkt vor Anzeige nur für Menschen in ein Zeitzonen abhängiges Format konvertieren sollte.

Der Vergleich passt auch nicht weil eine aktuelle Uhrzeit seit dem Urknall immer existiert und niemals eine leere Menge ist.

Daher wäre für das Problem des TOs ein 404 möglicherweise angemessen wenn der User nicht existiert.
 
Dann nimm halt die aktuelle Zahl an Katzen oder Hunde .. darum geht's nicht aber ja, ein evlt. ungeschicktes Beispiel !!
 
Ich stehe hier auf dem Standpunkt, dass eine GET auf eine nicht vorhandene Resource mit einem 404 quittiert werden muss. Das ist die einzig korrekte Verhaltensweise. Ein Client muss immer dazu in der Lage sein, unterscheiden zu können, ob eine Resource nicht existiert oder ob sie leer ist. 204 statt 404 kann auf Clientseite zu vielfälltigen Logikfehlern führen.
Generell: REST folgt dem Uniform Interface Prinzip. Der Statuscode ist Teil des Interfaces. Wenn du die Semantik verwässerst, verlierst du Interoperabilität und Klarheit. Third-Party Middleware z.B. für Caching kann dann auch nicht zuverlässig funktionieren.

Im Prinzip gibts da also nichts zu diskutieren. 404 ist die einzig korrekte Antwort! (Sicherheits-Usecases, wo man Enumeration vermeiden möchte mal außen vor).
 
  • Gefällt mir
Reaktionen: kali-hi und BeBur
Piktogramm schrieb:
Mein Ansatz wäre da ein Default zu setzen, im Zweifelsfall ist /configuration halt schlicht leer. Ein GET auf die Ressource bekommt die 200 zurück und auf Clientseite lässt sich "empty" auch leicht behandeln.
Wenn ich an Programmiersprachen denke, dann sollte eine Funktion, die etwas vom Typ T zurückliefert laut Interface auch wirklich ein gültiges T zurückliefert oder ansonsten etwas vom Typ optional<T> zurückliefern stattdessen.

Meine Intuition ist daher, das man nicht 200 + leere config zurückgeben sollte (default config, falls das sinn ergibt wäre dieser Logik folgend aber natürlich okay).

Oder ist das gängig bei services, dass es ausreicht, wenn rein strukturell was korrektes raus kommt @SheepShaver ? Also die config wird als JSON ausgeliefert und es wird dann ein leeres JSON ausgeliefert (das ist ja der zitierte Vorschlag).

Ich kenne das von APIs sonst auch so, dass sowieso immer ein wrapper-objekt zurückgegeben wird, das dann eben eine config enthält oder nicht, sowie warnungen/infos, etc.. Aber ich vermute, sowas macht man bei microservices tendenziell nicht?
 
  • Gefällt mir
Reaktionen: kali-hi
Meine Sichtweise ist, dass es eine Ressource vom Typ object | null bzw. Optional<UserConfiguration> gibt, die unter deren "Uniform Resource Locator" (URL) https://<host>/configuration existiert. D.h., da die URL auf jeden Fall gültig ist, auch, wenn der Rückgabewert null sein mag, ist es kein Client Error (Status Code 4xx), diese URL aufzurufen.

Aber wie auch immer, soweit die Theorie. Ich habe mich gefragt, wie das eigentlich in der Praxis aussieht. Und weil mental.dIseASe Servlets erwähnt hat (d.h. zumindest Client-seitig ist Java im Spiel): wie liefert JAX-RS eigentlich null oder ein leeres Optional<UserConfiguration> aus, und konsumiert ein zugehörierer MicroProfile REST Client das entsprechend?

Getestet habe ich es konkret mit Quarkus (RESTEasy Reactive). Jersey oder Apache CXF könnten sich anders verhalten. Es gibt sicherlich auch eine mehr oder weniger konkrete Spezifikation dazu, habe aber auf die Schnelle nichts belastbares gefunden.

@GET UserConfiguration getUserConfiguration() { return null; }:
-> Status 204, leerer Body.

@GET Optional<UserConfiguration> getUserConfiguration() { return Optional.empty(); }:
-> Status 200, Body ist null (gültiges JSON).

Der Unterschied zwischen den beiden Varianten ist bemerkenswert. Der zugehörige Client konsumiert beides, wie er sollte.

Meine Empfehlung: mach eins von beidem, dürfte für den im OP erwähnten Consumer am einfachsten zu konsumieren sein.

Aber auf gar keinen Fall Dummy-Objekte zurückliefern, jedenfalls nicht ohne Diskriminator. Sonst steigen Euch die Konsumenten mit statisch typisierten Programmiersprachen so richtig aufs Dach. 😜
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: KeepCalm
Das ist aber im Falle von REST APIs schlicht falsch. Die Konvention ist, dass das Abrufen einer nicht vorhandenen Resource mit einem 404 quittiert werden muss.
 
Zurück
Oben