CodeGym /Kurse /Java Server /Analyse eines problematischen Aufrufs

Analyse eines problematischen Aufrufs

Java Server
Level 9, Lektion 4
Verfügbar

1. Algorithmus zur Analyse eines HTTP-Aufrufs

An dieser Stelle sind bereits vier verschiedene Schmerzquellen erkennbar: Die Anfrage kann unvollständig sein, die Operation kann die Ressource verändern, es kann keine Antwort geben, und die eingetroffene Antwort muss noch nicht mit der Vereinbarung übereinstimmen. Nun ist es sinnvoll, diese Teile in eine funktionierende Analyseordnung zu bringen, um HTTP nicht intuitiv zu diagnostizieren und nicht das falsche Problem zu beheben. Ein Werkzeug kann später einen Teil des Boilerplate-Codes verbergen, aber die Ursachen für Fehler lassen sich dennoch auf dieselben Fragen zurückführen.

Wenn etwas schiefgeht, macht das Gehirn eines Anfängers normalerweise einen heldenhaften Sprung in die Mitte der Geschichte: „Oh! Im body steht ein seltsamer String“, „Oh! Es kam eine 404 – also ist der Server tot“, „Oh! Eine Exception – also ist das ein Fehler in der Geschäftslogik“. Das Problem ist, dass dies keine Analyse, sondern ein Ratespiel ist, nur mit schöneren Begriffen.

Backend-Denken liebt Ordnung, denn Ordnung spart Zeit. Wenn Sie jedes Problem nach dem gleichen Schema analysieren, finden Sie die Ursache schneller, fluchen weniger auf den Provider (manchmal zu Unrecht) und beheben seltener „das Falsche“. Dies ist besonders wichtig für den ReadLater Starter: Sobald die Anwendung auf einen externen Bücherkatalog zugreift, muss man unterscheiden können zwischen „Der Dienst hat einen Fehler gemeldet“, „Wir haben keine Antwort erhalten“ und „Die Antwort kam, aber sie entspricht nicht dem Vertrag“.

Kurz gesagt: Der Algorithmus wird nicht benötigt, weil Sie nicht intelligent wären, sondern weil Sie ein Mensch sind. Und Menschen brauchen Checklisten, sonst beginnen sie, nach Horoskop zu debuggen.

Drei Fragen: Antwort, Status, Vertrag

Es ist nützlich, drei Fragen im Kopf zu behalten, die man streng in dieser Reihenfolge stellt. Diese Reihenfolge ist wichtiger, als es scheint: Sie schützt vor dem häufigsten Fehler – „Wir haben zuerst in den body geschaut und dann festgestellt, dass es gar keine Antwort gab“. Das Schema ist einfach, fast wie „Schau nach, ob der Computer eingesteckt ist“, nur eben für HTTP.

Die erste Frage: Gibt es überhaupt eine Antwort? Wenn es keine Antwort gab, können Sie per Definition keinen Statuscode analysieren – es gibt keinen. Die zweite Frage: Falls eine Antwort vorhanden ist, welcher Status? Der Status ist ein schneller Indikator für die Bedeutung: Erfolg, Fehler in der Anfrage, fehlende Ressource, Konflikt, interner Fehler. Die dritte Frage: Wenn der Status Erfolg anzeigt, entspricht die Antwort dem Vertrag? Denn 200 OK ist noch keine Garantie, dass Ihnen das gesendet wurde, was vereinbart war.

Dies kann man als einen sehr einfachen Entscheidungsbaum darstellen:

flowchart TD
    A["Problematischer HTTP-Aufruf"] --> B{Aktwort erhalten?}
    B -- Nein --> C["Netzwerkfehler / keine Antwort"]
    B -- Ja --> D{"Statuscode"}
    D -- 4xx/5xx --> E["Error Response erhalten"]
    D -- 2xx --> F{"Vertrag erfüllt?"}
    F -- Nein --> G["Vertragsverletzung"]
    F -- Ja --> H["Erfolg"]

Beachten Sie eine wichtige psychologische Sache: 404 befindet sich im Zweig „Antwort erhalten“. Das bedeutet, dass der Server lebt, das Netzwerk die Anfrage zugestellt und eine Antwort zurückgesendet hat. Dies ist bereits eine nützliche Information und unterscheidet sich grundlegend von der Situation „gar keine Antwort“.

2. Schritt 1 – Prüfung auf Vorhandensein einer Antwort

Die Analyse muss mit der langweiligsten, aber rettendsten Frage beginnen: „Gab es eine Antwort?“. Langweilig, weil man sofort den body lesen und nachschauen möchte, „was dort nicht stimmt“. Rettend, weil es manchmal gar keine HTTP-Response gibt und Sie versuchen, ein Phantom zu analysieren.

Wenn es keine Antwort gibt, gibt es auf der Statusebene nichts zu diskutieren: Sie haben weder 404 noch 500, keine Header, keinen Body. Die Ursachen können vielfältig sein – Dienst nicht erreichbar, falsche Adresse, Netzwerkausfall, Verbindung nicht aufgebaut –, aber der Diagnosezweig ist derselbe: Zuerst anerkennen, dass keine Response empfangen wurde, und dann herausfinden, warum.

Um den Gedanken an einem einfachen Java-Beispiel zu veranschaulichen, kann man sich vorstellen, dass das Ergebnis eines Aufrufs entweder eine Response oder ein Fehler ist. Wir machen jetzt keinen echten HTTP-Code (der kommt später), aber wir können die Form des Ergebnisses modellieren – das ist nützlich für das Denken.

package com.example.readlater;

// Ergebnis des Aufrufs: entweder gibt es eine HTTP-Antwort oder ein Fehler ist auf dem Weg dorthin aufgetreten (Netzwerk, DNS, Timeout usw.)
public record CallOutcome(RemoteResponse response, Exception error) {

    // Das Wichtigste an diesem Objekt: Wir trennen "keine Antwort" von "Antwort mit schlechtem Status".
    public boolean hasResponse() {
        return response != null;
    }
}

Wenn hasResponse() false zurückgibt, sollten Sie nicht nach dem „richtigen Status“ suchen oder gar den body analysieren: Sie haben einen anderen Problemzweig. Hier wird geklärt, ob ein Netzwerkfehler vorlag, ob host/port/path falsch waren oder ob eine externe Infrastruktur blockierte.

Und hier ist es nützlich, die Semantik der Operation zu bedenken. Für einen lesenden Zugriff bedeutet das Fehlen einer Antwort normalerweise ein verlorenes Ergebnis. Bei einer ändernden Anfrage ist es unangenehmer: Der Client weiß nicht mehr, ob er nur die Antwort nicht abgewartet hat oder ob der Server bereits etwas geändert hat.

3. Schritt 2 – Status als Problemklasse

Wenn Sie sichergestellt haben, dass die Antwort tatsächlich empfangen wurde, ist es an der Zeit, das zu tun, was viele auslassen: den Statuscode anzuschauen und ihn als „Situationsklasse“ zu betrachten. Es ist nicht nur eine Zahl, sondern ein vereinbartes Signal zwischen Client und Server: „Es hat geklappt“, „Deine Anfrage hat ein Problem“, „Die Ressource existiert nicht“, „Konflikt“, „Interner Fehler bei mir“.

Ein wichtiger Punkt: Ein error response ist ebenfalls ein Ergebnis des Vertrags. Wenn der Server mit 400 oder 404 antwortet, ist dies bereits eine „normale Kommunikationsform“: Er konnte Ihre Anfrage so weit verarbeiten, dass er eine strukturierte Antwort (wenn auch mit Fehler) zurückgeben kann. Dies unterscheidet sich von der Situation, in der es überhaupt keine Antwort gibt.

Um dies an einem kleinen Code festzumachen, brauchen wir ein minimales Antwortobjekt. Wir erstellen einen Record, der Status, Content-Type und Body enthält. Ja, body ist vorerst ein String: Für diese Diagnose ist es wichtiger, Status und Form der Antwort zu unterscheiden, als JSON Felder zu parsen.

package com.example.readlater;

// Minimales Modell einer HTTP-Antwort für die Diagnose.
// Wichtig sind: Status, Content-Type und Body (vorerst als String, ohne JSON-Parsing).
public record RemoteResponse(int status, String contentType, String body) {

    // 2xx — Klasse "Erfolg": Der Server betrachtet die Anfrage als verarbeitet.
    public boolean is2xx() {
        return status >= 200 && status < 300;
    }
}

Jetzt können wir eine sehr einfache Prüfung hervorheben: „Ist der Status ein Fehler oder nicht“.

package com.example.readlater;

public class Statuses {

    // 4xx/5xx — das ist ein error response: Es gibt eine Antwort, aber der Server meldet einen Fehler.
    public static boolean isError(int status) {
        return status >= 400 && status < 600;
    }
}

Hier reicht es, sich an einem Anker festzuhalten: 404 und 500 gehören bereits zum Zweig „Response empfangen“. Es sind unterschiedliche Probleme, aber beide unterscheiden sich von der Situation, in der es überhaupt keinen Status gibt.

Um dies leichter im Kopf zu behalten, hier eine kleine Interpretationstabelle (ohne die Lektion in ein RFC-Handbuch zu verwandeln):

Was beobachten Sie Das ist… Was man NICHT als Erstes tun sollte
Keine Response (es gibt eine Exception/einen Abbruch) Netzwerkfehler / Nichtverfügbarkeit Nach „richtigem Status“ suchen und Body analysieren
Response mit Status 4xx Fehler in der Anfrage / Client-Problem „Der Dienst ist abgestürzt“ zu erklären (er hat gerade geantwortet)
Response mit Status 5xx Fehler auf Serverseite Annehmen, dass Sie „ganz sicher alles richtig gesendet haben“ (manchmal haben Sie das Problem verursacht)
Response mit Status 2xx Erfolgreiche Zustellung der Antwort Annehmen, dass der Vertrag genau eingehalten wurde (das ist der nächste Prüfschritt)

Im Projektkontext des ReadLater Starter wird dies besonders nützlich sein, wenn wir auf den externen Bücherkatalog zugreifen. Ein externer Provider kann auf einen falschen Pfad (z.B. einen Tippfehler in der URL) ehrlich mit 404 antworten, auf einen ungültigen Parameter mit 400 oder mit 500, weil er Probleme hat. Und alle drei Fälle sind Antworten und keine „fehlenden Antworten“.

4. Schritt 3 – Prüfung des Vertrags

Und hier fügt sich endlich alles zusammen: Nach der Prüfung von Response und Status bleibt zu überprüfen, ob der eingetroffene „Erfolg“ tatsächlich dem entspricht, was vereinbart wurde.

Jetzt kommt der Moment, der oft die romantische Vorstellung von 200 OK zerstört: Manchmal antwortet der Server mit 200, macht es aber „irgendwie falsch“. Und dies ist nicht einfach ein „Anfragefehler“ oder ein „interner Fehler“ – es ist oft ein Zeichen dafür, dass der Vertrag verletzt wurde (oder wir ihn falsch verstanden haben).

Ein Vertrag umfasst nicht nur „Felder im JSON“. Es umfasst auch den Content-Type, die erwartete Form der Antwort und die zulässigen Statuscodes für diesen Endpunkt. Beispielsweise erwarten Sie JSON, erhalten aber eine HTML-Seite mit dem Text „Service vorübergehend nicht verfügbar“ – und ja, so etwas kommt manchmal mit dem Status 200. Oder Sie erhalten einen leeren Body, wo Sie ein Objekt erwarten. Oder die Header sind so, dass der Client nicht versteht, wie er dies interpretieren soll.

Lassen Sie uns dies an einer sehr bodenständigen Prüfung zeigen: Wir betrachten den „Vertrag als JSON-ähnlich“, wenn der Content-Type application/json ist und der Body zumindest einen erwarteten Marker enthält. Dies ist keine „korrekte Validierung“, sondern eine lehrreiche Veranschaulichung der Idee: Selbst ein Erfolg muss mit der Erwartung abgeglichen werden.

package com.example.readlater;

public class ContractChecks {

    // Sehr lehrreiche Prüfung: Sieht das wie die JSON-Antwort aus, die wir erwarten?
    // Dies ist KEIN "richtiger" JSON-Parser. Hier ist die Idee wichtig: Selbst bei 200 kann der Vertrag verletzt sein.
    public static boolean looksLikeJson(RemoteResponse r) {
        return "application/json".equals(r.contentType())
                && r.body() != null
                && r.body().contains("{");
    }
}

Stellen Sie sich nun zwei Antworten vor.

Die erste – sie sieht so aus, wie wir es wollen:

// "Normale" Antwort: JSON und erwartete Struktur.
RemoteResponse ok = new RemoteResponse(
        200,
        "application/json",
        """
        {"items":[],"count":0}
        """
);

Die zweite – eine „erfolgreiche“, aber seltsame:

// "Seltsame" Antwort: Status 200, aber Content-Type und Body entsprechen nicht dem JSON-API.
RemoteResponse weird = new RemoteResponse(
        200,
        "text/html",
        """
        Hoppla
        """
);

Beide sind formal 200, aber die zweite verletzt die Erwartung: Der Client dachte, es wäre ein JSON-API, aber er hat HTML erhalten. Dies ist eine typische Situation, wenn der Dienst hinter einem Proxy/einer Fehlerseite steht oder wenn Sie falsch navigiert sind (z.B. auf die „menschliche“ Website statt auf das API). Und Ihre Analyse sollte sagen können: „Der Status ist erfolgreich, aber der Vertrag stimmt nicht überein“. Dies ist eine separate Kategorie und sehr nützlich, denn sie hilft, keine Zeit damit zu verschwenden, „warum JSON nicht geparst wird“, wenn dort eigentlich gar kein JSON ist.

Uns reicht vorerst die bloße Tatsache der Formabweichung. Selbst wenn der Antwortbody für uns nicht mehr nur ein String ist, sondern eine Menge konkreter Felder, bleibt die Denkordnung dieselbe: Zuerst Response, dann Status, dann Übereinstimmung mit der Vereinbarung.

Ein weiterer wichtiger Punkt: Der Vertrag kann nicht nur durch die Form des Bodys verletzt werden, sondern auch durch den Status. Beispielsweise erwarten Sie, dass GET /books/search immer 200 zurückgibt (selbst wenn nichts gefunden wurde – eine leere Liste), aber der Dienst beginnt plötzlich, 404 zu liefern. Aus HTTP-Sicht ist dies ein „mögliches Verhalten“, aber aus Sicht Ihrer Vereinbarung ist es eine Änderung der Spielregeln. Und dies sollte genau als „Vertrag ist verletzt“ erkannt werden, nicht als „naja, schon wieder 404, also ist alles verloren“.

5. Minidiagnostik im Code

Jetzt machen wir einen kleinen, aber sehr aussagekräftigen Schritt: Wir verwandeln unser Schema in Code, der das Ergebnis eines problematischen Aufrufs klassifiziert. Dies ist keine „fertige Bibliothek“, kein „Framework“ und nicht einmal ein echter HTTP-Client – es ist ein lehrreiches Denkgerüst, das Sie später (wenn der echte Aufruf kommt) einfach mit echten Daten füllen.

Beginnen wir mit der Aufzählung möglicher Ergebnisse. Vier reichen uns:

package com.example.readlater;

public enum CallVerdict {

    // Keine HTTP-Antwort (Verbindung nicht aufgebaut, Timeout, DNS, Abbruch usw.)
    NETWORK_FAILURE,

    // HTTP-Antwort ist vorhanden, aber Status 4xx/5xx (Server hat protokollgemäß einen Fehler gemeldet).
    HTTP_ERROR_RESPONSE,

    // HTTP-Antwort "sieht nicht aus" wie vereinbart (Status/Typen/Body).
    CONTRACT_VIOLATION,

    // Alles gut: Antwort vorhanden, Erfolgsstatus, Vertrag eingehalten.
    SUCCESS
}

Jetzt bauen wir den Analysator. Er wendet genau die Reihenfolge an, die wir besprochen haben: Zuerst Fehler/keine Antwort, dann Status, dann Vertrag.

package com.example.readlater;

public class CallAnalyzer {

    public CallVerdict analyze(CallOutcome outcome) {
        // 1) Die wichtigste erste Frage: Gab es überhaupt eine Antwort?
        if (!outcome.hasResponse()) return CallVerdict.NETWORK_FAILURE;

        RemoteResponse r = outcome.response();

        // 2) Wenn Status 4xx/5xx – das ist ein "error response": Server hat geantwortet, aber einen Fehler gemeldet.
        if (Statuses.isError(r.status())) return CallVerdict.HTTP_ERROR_RESPONSE;

        // 3) Alles, was weder 2xx noch 4xx/5xx ist, betrachten wir in diesem lehrreichen Schema als unerwarteten Status-Zweig.
        // Hier verzichten wir bewusst auf die Analyse von Redirects und anderen Spezialfällen.
        if (!r.is2xx()) return CallVerdict.CONTRACT_VIOLATION;

        // 4) Selbst bei 2xx prüfen wir, ob die Antwort den Erwartungen (Vertrag) entspricht.
        if (!ContractChecks.looksLikeJson(r)) return CallVerdict.CONTRACT_VIOLATION;

        return CallVerdict.SUCCESS;
    }
}

Hier gibt es eine bewusste Vereinfachung: Alles, was nicht in den erwarteten Erfolgs- oder expliziten Fehlerzweig fällt, betrachten wir vorläufig als unerwartet für den aktuellen Vertrag. Uns ist jetzt die Reihenfolge der Diagnose wichtig, ohne separate Behandlung aller speziellen Protokollfälle.

Beachten Sie die bewusste Einfachheit. Wir bauen keine „schöne“ Ausnahmehierarchie, keine universellen Abstraktionen, keine Wiederholungsversuche. Unser Ziel ist, dass sich die Reihenfolge der Fragen in Ihrem Kopf festigt. Dieser Code lässt sich fast wie deutscher Text lesen: „Wenn keine Antwort vorhanden ist – Netzwerkfehler; wenn der Status fehlerhaft ist – error response; wenn der Status seltsam ist – Vertragsverletzung; wenn die Antwortform nicht stimmt – Vertragsverletzung; sonst Erfolg“.

Jetzt können wir zeigen, wie dies in einer Minidemo aussieht. Ja, es wird ein „Spielzeug“ sein, bei dem wir Outcomes von Hand erstellen, aber es veranschaulicht die Klassifikation hervorragend.

package com.example.readlater;

public class ReadLaterApplication {
    public static void main(String[] args) {
        CallAnalyzer analyzer = new CallAnalyzer();

        // Simuliere eine erfolgreiche Antwort: Antwort vorhanden, 200, JSON-ähnlicher Body.
        CallOutcome ok = new CallOutcome(
                new RemoteResponse(200, "application/json", """
                {"items":[],"count":0}
                """),
                null
        );

        System.out.println(analyzer.analyze(ok)); // SUCCESS
    }
}

Und noch ein Beispiel – 404 (Antwort vorhanden, aber fehlerhafter Status):

package com.example.readlater;

public class Demo404 {
    public static void main(String[] args) {
        // 404 ist NICHT "keine Antwort", sondern "Antwort vorhanden, aber Server meldet Anfrage-/Ressourcenfehler".
        CallVerdict v = new CallAnalyzer().analyze(
                new CallOutcome(
                        new RemoteResponse(404, "application/json", """
                        {"message":"not found"}
                        """),
                        null
                )
        );

        System.out.println(v); // HTTP_ERROR_RESPONSE
    }
}

Und ein Beispiel für „gar keine Antwort“:

package com.example.readlater;

public class DemoNetworkFailure {
    public static void main(String[] args) {
        // response == null: Keine HTTP-Antwort, nur eine Exception auf Client-/Netzwerkseite.
        CallOutcome out = new CallOutcome(null, new IllegalStateException("Connection refused"));

        System.out.println(new CallAnalyzer().analyze(out)); // NETWORK_FAILURE
    }
}

Aus der Perspektive der Entwicklung des ReadLater Starter ist dies nützlich, weil wir später einen echten „externen Bücherkatalog“ haben werden. Und wenn Sie dann einen Aufruf zur „Büchersuche“ debuggen, wird Ihnen eine einfache Gewohnheit helfen: „Was ist das? Keine Antwort? Antwort mit Status? Erfolg, aber Vertrag verletzt?“ Das ist weitaus besser als „irgendwas stimmt nicht, lass uns einfach alles ausgeben“.

6. Typische Fehler bei der Analyse von Aufrufen

Fehler Nr. 1: Die Analyse beginnt mit dem Body.
Die häufigste Falle: Sie lesen sofort den Body, versuchen zu verstehen, „warum da nicht das Richtige steht“, und stellen dann fest, dass der Body gar nicht das ist, was Sie denken, oder dass er fehlt. Die richtige Reihenfolge ist streng: Zuerst klären, ob eine Response existiert, dann den Status, und erst danach den Body.

Fehler Nr. 2: 404 wird als „Dienst nicht erreichbar“ interpretiert.
404 ist eine Antwort eines lebenden Dienstes. Ja, sie gefällt Ihnen nicht, aber sie bedeutet, dass Netzwerk und Server gut genug funktioniert haben, um ein strukturiertes Signal zurückzugeben. Wenn Sie dies als „Nichterreichbarkeit“ bezeichnen, werden Sie automatisch beginnen, das Problem „in der Infrastruktur“ zu suchen, obwohl es oft nur ein falscher Pfad oder eine fehlende Ressource ist.

Fehler Nr. 3: Jede Exception wird als 500 auf Serverseite betrachtet.
Eine Exception auf Client-Seite (oder in Ihrer Anwendung) bedeutet sehr oft „keine Antwort“, nicht „der Server hat 500 zurückgegeben“. 500 hat ein konkretes Kennzeichen: Sie haben eine Response erhalten und den Status gesehen. Wenn es keine Response gibt, ist das eine andere Problemklasse und muss getrennt werden.

Fehler Nr. 4: 200 OK bedeutet automatisch „alles in Ordnung“.
200 bedeutet, dass der Server „Erfolg“ gesagt hat, aber garantiert nicht, dass Sie und der Server das gleiche Verständnis des Antwortformats haben. Wenn der Content-Type nicht stimmt, der Body leer ist, das Format „verrutscht“ ist, kann es ein Vertragsproblem sein. Und dieses ist besonders unangenehm, weil es wie ein Erfolg aussieht, aber den Client zerstört.

Fehler Nr. 5: Anfragen zum Lesen und Ändern von Daten werden gleich leicht wiederholt.
Wenn Sie Netzwerkprobleme haben, ist es psychologisch einfacher, einen GET zu wiederholen: Lesen sollte den Zustand nicht ändern. Aber „ändernde“ Anfragen (Erstellen/Ändern/Löschen) müssen vorsichtiger wiederholt werden, denn bei einem Netzwerkfehler wissen Sie nicht immer, was der Server bereits getan hat. Heute bauen wir kein Wiederholungssystem auf, aber die richtige Vorsicht sollte bereits jetzt vorhanden sein.

1
Aufgabe
Java Server, Level 9, Lektion 4
Gesperrt
Einheitlicher Analyse für Problemaufrufe
Einheitlicher Analyse für Problemaufrufe
1
Aufgabe
Java Server, Level 9, Lektion 4
Gesperrt
Nächstes Vorgehen nach problematischem Aufruf
Nächstes Vorgehen nach problematischem Aufruf
1
Umfrage/Quiz
HTTP-Prinzipien, Level 9, Lektion 4
Nicht verfügbar
HTTP-Prinzipien
stateless, safe und Vertrag
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION