CodeGym /Kurse /Java Server /Externer Aufruf in ReadLaterApplication

Externer Aufruf in ReadLaterApplication

Java Server
Level 14, Lektion 4
Verfügbar

1. Vom Beispiel zur Anwendung

Wenn wir Code im Format „ausprobiert – hat geklappt“ schreiben, lebt er oft als einzelnes Stück: eine Methode, die eine Anfrage stellt, und ein System.out.println(). Das ist fürs Lernen in Ordnung, aber für ein Projekt eher schlecht: Nach zwei Tagen vergessen Sie, wie man diese Methode startet, und sie wird zum Museumsstück. Unser Ziel ist es jetzt, den Aufruf in ReadLaterApplication zu verankern und ihn per Befehl startbar zu machen.

Grob gesagt haben wir zwei Welten. Die erste Welt ist der „Demo-Code“, der nur funktioniert, wenn Sie ihn manuell aufrufen – und manchmal auch nur, wenn Sie die richtige Stimmung haben. Die zweite Welt ist die „Anwendung“, die reproduzierbar über Gradle startet, Kommandozeilenargumente entgegennimmt und eine nützliche Aktion ausführt. Wir bauen die zweite Welt.

Wichtig ist, dass wir noch keine „schöne Architektur für die Ewigkeit“ bauen. Hier erstellen wir ein kleines, verständliches, ausführbares Fragment. Wenn Sie jetzt sofort versuchen, einen „universellen HTTP-Client, Adapter, Fassaden, Fabriken, Repositories und einen Event-Bus“ zu bauen, erreichen Sie nur eines: Sie verlieren den Faden – wo ist denn nun der eigentliche HTTP-Aufruf, um den es eigentlich ging?

Deshalb ist das Erfolgskriterium hier einfach und sehr bodenständig: Sie starten die Anwendung mit einem Befehl wie catalog search ..., sie führt einen externen GET aus, erhält eine Antwort und gibt den Status sowie einen kurzen JSON-Ausschnitt aus, damit Sie mit eigenen Augen bestätigen: „Ja, wir waren wirklich draußen unterwegs.“

2. Einstiegspunkt und Client-Klasse

Jetzt möchten wir, dass der Code gleichzeitig „im Projekt“ ist und „die main() nicht zu einem 200-Zeilen-Monster wird“. Der Kompromiss ist ganz einfach: Wir lassen ReadLaterApplication als Dirigenten, der die Startargumente versteht und entscheidet, welches Szenario ausgeführt wird, und den HTTP-Aufruf selbst verstecken wir in einer kleinen Klasse, nennen wir sie CatalogHttpClient. Das sind keine „Schichten“, keine „Architektur“, sondern einfach Gehirnhygiene.

Dabei ist es wichtig, eine Sache nicht aus den Augen zu verlieren, die bereits in der Antwort des externen Dienstes sichtbar wurde: Sie besteht nicht aus einer einzigen Zeile. Die Antwort hat Status, Header und Body. Selbst in der einfachsten eingebauten Variante halten wir zumindest den Status und den Content-Type fest, während wir den Body nur als Vorschau anzeigen. Raw JSON ist hier eine temporäre Vereinfachung, damit der Anwendungsbefehl ausführbar wird.

Das lässt sich so darstellen:

flowchart LR
    CLI["./gradlew run --args='catalog search clean code'"] --> App["ReadLaterApplication"]
    App --> Client["CatalogHttpClient"]
    Client --> API["Externer Buchkatalog"]
    API --> Client
    Client --> App
    App --> Out["Konsolenausgabe: Status + Content-Type + JSON-Vorschau"]

Um nicht den Überblick zu verlieren, wer wofür zuständig ist, hilft eine kleine Tabelle (ja, sie ist langweilig, aber sie bewahrt vor Chaos):

Teil Was tut es Was tut es nicht
ReadLaterApplication Liest args, wählt Szenario (catalog search), gibt Ergebnis aus Sollte keine Details von HttpRequest.Builder und BodyHandlers kennen
CatalogHttpClient Führt HTTP-Aufruf aus, erfasst minimale Diagnose und gibt Raw JSON zurück Sollte sich nicht um die Analyse von Kommandozeilenargumenten kümmern

Beachten Sie die Feinheit: Wir versuchen nicht, jetzt schon die Verantwortlichkeiten perfekt zu verteilen und den Provider „für immer zu verstecken“. In diesem Schritt ist es in Ordnung, wenn die URL einfach und stellenweise sogar hartcodiert ist. Hauptsache, der Aufruf ist eingebaut und reproduzierbar.

3. CatalogHttpClient schreiben: eine einfache HTTP-Klasse

Wenn Sie zum ersten Mal eine Klasse für einen HTTP-Aufruf auslagern, flüstert das Gehirn: „Lass es uns universell machen! Es soll GET/POST/PUT können, Retrys, Timeouts, Logging, Metriken und Kaffee kochen.“ Das ist ein gefährlicher Moment. Wir brauchen jetzt keine „Plattform“, sondern eine einzige ehrliche „Leitung“ nach draußen. Deshalb halten wir die Klasse so einfach wie möglich: innen ein HttpClient, außen eine Methode, die Raw JSON als String zurückgibt. Ja, genau Raw: das ist eine bewusste temporäre Vereinfachung für den ersten funktionierenden Start, nicht die neue Norm für das gesamte Projekt.

Beginnen wir mit dem Grundgerüst. Wichtig: HttpClient sollte einmal erstellt und wiederverwendet werden (ja, selbst in einem Lehrbeispiel). So vermeiden Sie „einen neuen Client pro Anfrage“, als ob Sie für jeden Anruf ein neues Telefon kaufen würden.

import java.net.http.HttpClient;

public class CatalogHttpClient {
    // Ein HttpClient pro Client-Instanz: wiederverwenden, nicht pro Anfrage neu erstellen
    private final HttpClient client = HttpClient.newHttpClient();
}

Jetzt fügen wir eine Methode hinzu, die eine URL als String entgegennimmt. Ja, als String. Ja, das ist nicht ideal. Aber jetzt ist es maximal transparent: Sie sehen, wohin der Aufruf genau geht, und verstecken sich nicht hinter Abstraktionen. Für den ersten Schritt ist das ein Plus.

HttpRequest zusammenbauen

import java.net.URI;
import java.net.http.HttpRequest;

// GET-Anfrage an eine konkrete URL zusammenbauen und explizit sagen: "Ich erwarte JSON"
HttpRequest request = HttpRequest.newBuilder(URI.create(url))
        .header("Accept", "application/json") // damit der Server das benötigte Antwortformat versteht
        .GET() // HTTP-Methode explizit festlegen
        .build();

Hier ist es hilfreich, laut zu sagen (ja, auch flüsternd, niemand verurteilt Sie): Wir erstellen eine Anfrage, geben die Adresse an, sagen dem Server „Ich erwarte JSON“ und wählen die Methode GET. Genau so beginnt Ihr Code, den HTTP-Vertrag abzubilden, den Sie bereits in Postman gesehen haben.

Anfrage senden und Raw JSON holen

import java.net.http.HttpResponse;

// Anfrage synchron senden (der Thread wartet auf die Antwort) und den Body als String lesen
HttpResponse<String> response = client.send(
        request,
        HttpResponse.BodyHandlers.ofString() // wandelt Body in String (praktisch für "rohes" JSON am Anfang)
);

return response.body();

Wir verwenden BodyHandlers.ofString(), daher ist response.body() ein String. Das ist ideal für den aktuellen Schritt: Wir diskutieren nicht mit JSON, mappen es nicht in DTOs, machen kein „intelligentes Parsen“. Wir geben einfach den rohen Fakt zurück: „Hier ist der Antwort-Body.“

Minimale Diagnose im Client

Jetzt ist es nützlich, zumindest den Status und den Content-Type zu sehen. Das ist das Minimum, das verhindert, dass body() sich als der einzig wichtige Teil der Antwort ausgibt. Da die Methode immer noch einen String zurückgibt, ist es praktisch, diese Diagnose direkt neben dem Aufruf zu platzieren.

int status = response.statusCode();
System.out.println("Catalog HTTP status = " + status); // minimale Diagnose: 200/404/500 usw.

String contentType = response.headers()
        .firstValue("Content-Type")
        .orElse("<no Content-Type>");
System.out.println("Catalog Content-Type = " + contentType);

Und jetzt haben wir eine komplette Methode. Ich lasse sie bewusst „etwas länger als 10 Zeilen“, sonst springen Sie mit den Augen in der Datei hin und her. Aber beachten Sie: Sie ist immer noch linear lesbar und ohne „Magie“.

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class CatalogHttpClient {
    // Client wiederverwenden: schneller und einfacher, als bei jedem Aufruf einen neuen zu erstellen
    private final HttpClient client = HttpClient.newHttpClient();

    public String getRawJson(String url) throws Exception {
        // HTTP-Anfrage zusammenbauen (in diesem Schritt kommt die URL als String – maximal transparent)
        HttpRequest request = HttpRequest.newBuilder(URI.create(url))
                .header("Accept", "application/json") // JSON anfordern
                .GET()
                .build();

        // Echten externen Aufruf durchführen und Antwort als String lesen
        HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());

        // Das Wesentliche nicht vergessen: Status und Inhaltstyp sind auch Teil der Antwort
        System.out.println("Catalog HTTP status = " + response.statusCode()); // Catalog HTTP status = 200
        System.out.println("Catalog Content-Type = " +
                response.headers().firstValue("Content-Type").orElse("<no Content-Type>"));

        // "Rohes" JSON zurückgeben: Parsing kommt später, wenn es wirklich nötig ist
        return response.body();
    }
}

throws Exception ist hier eine bewusste „didaktische Nachsicht“. Wir bauen noch kein Fehlerbehandlungssystem; es ist wichtig, nicht in try/catch zu versinken und die Mechanik der Anfrage nicht zu verlieren.

4. Aufruf in ReadLaterApplication: catalog search

Jetzt das Wichtigste: Unser HTTP-Code darf nicht länger eine „Klasse sein, die im Projekt existiert, aber niemand ruft sie auf“. Der ideale Punkt für den Aufruf ist der Zweig der Startargumente. Wir erwarten einen Start wie:

./gradlew run --args="catalog search clean code"

Gradle übergibt dies an main() so, dass args[0] = "catalog", args[1] = "search", und danach die Suchwörter.

Befehl sorgfältig erkennen

Der häufigste Anfängerfehler ist, sofort args[0] zu schreiben, ohne die Länge des Arrays zu prüfen. Das ist, als würde man den Kühlschrank öffnen und sofort das „erste Fach“ nehmen, ohne sicherzustellen, dass der Kühlschrank überhaupt existiert (in der Programmierung endet das meist mit einer ArrayIndexOutOfBoundsException).

// Zuerst die Länge von args prüfen, dann erst args[0], args[1] usw. lesen
boolean isCatalogSearch = args.length >= 3
        && "catalog".equals(args[0])
        && "search".equals(args[1]);

Warum >= 3? Weil wir mindestens ein Such-Token nach catalog search benötigen. Sonst würde es heißen „such irgendwas… was genau – finde es selbst raus“.

Anfrage aus den Argumenten zusammensetzen

Jetzt müssen wir aus ["clean","code"] den String "clean code" machen. Der kürzeste Weg ist Arrays.copyOfRange(args, 2, args.length) + String.join(" ", ...). Ja, das klingt wie ein Zauberspruch, aber im Grunde heißt es: „Nimm das Ende des Arrays und füge es mit Leerzeichen zusammen“.

import java.util.Arrays;

// Alle Argumente nach "catalog search" nehmen und zu einem Suchstring zusammenfügen
String query = String.join(" ", Arrays.copyOfRange(args, 2, args.length));

Jetzt ein kleiner Hack für den ersten Tag: Ersetze Leerzeichen durch %20. Das ist keine vollwertige Kodierung, aber für den Anfang funktioniert es und führt uns nicht in ein separates Thema.

// URL minimal "leerzeichenfrei" machen, damit URI.create(...) nicht meckert
String encodedQuery = query.replace(" ", "%20");

URL und Aufruf von CatalogHttpClient

In diesem Schritt kann die Basisadresse des Katalogs als Konstante gehalten werden. Auch wenn sie vorerst fest im Code steht, vervielfältigen Sie die Zeichenkette nicht im gesamten Projekt. Damit der Aufruf in der Anwendung derselbe bleibt, verwenden wir denselben Such-Endpunkt, der bereits beim rohen GET funktioniert hat.

String baseUrl = "https://openlibrary.org";
String url = baseUrl + "/search.json?q=" + encodedQuery; // in diesem Schritt reicht einfaches Zusammenfügen

Und nun – der eigentliche Aufruf. Client erstellen (ein Objekt), Anfrage stellen, JSON als String erhalten.

CatalogHttpClient catalogClient = new CatalogHttpClient(); // separate Klasse, damit main nicht zum Chaos wird

String rawJson = catalogClient.getRawJson(url);
System.out.println(rawJson);

Wenn man das alles zu einem lesbaren Stück innerhalb von main() zusammenfügt, sieht es ungefähr so aus:

import com.example.readlater.catalog.CatalogHttpClient;
import java.util.Arrays;

if (isCatalogSearch) {
    String query = String.join(" ", Arrays.copyOfRange(args, 2, args.length));
    String url = "https://openlibrary.org/search.json?q=" + query.replace(" ", "%20");

    CatalogHttpClient catalogClient = new CatalogHttpClient();
    String rawJson = catalogClient.getRawJson(url);

    System.out.println(rawJson);
}

Ja, die URL wird noch direkt hier gebaut, und ja, das ist noch keine „Schönheit“. Aber das hat einen großen Vorteil: Sie sehen das gesamte Szenario auf einen Blick und verstehen, was genau gestartet wird, woher die Eingabedaten kommen und wohin das Ergebnis geht.

5. Benutzerfreundliche Ausgabe: JSON-Vorschau

Wenn der externe Dienst ein großes JSON zurückgibt, werden Sie schnell merken, dass „alles ausgeben“ bedeutet, die Konsole in ein modernes Kunstwerk zu verwandeln. Manchmal ist es nützlicher, die ersten 200 Zeichen zu sehen, sich zu vergewissern, dass es sich tatsächlich um JSON handelt, und dann weiterzumachen. Deshalb fügen wir eine kleine Vorschau-Funktion hinzu.

// Hilfsmittel für "sichere" Vorschau: nicht bei null abstürzen und die Konsole nicht mit riesigen Antworten zumüllen
static String preview(String text, int limit) {
    if (text == null) return "<null>";
    return text.length() <= limit ? text : text.substring(0, limit) + "...";
}

Und verwenden sie:

String rawJson = catalogClient.getRawJson(url);
System.out.println(preview(rawJson, 200)); // {"numFound":123,"docs":[... (und der Rest abgeschnitten)

Wenn Sie die Ausgabe etwas informativer gestalten möchten, können Sie auch die Anfrage (URL) ausgeben. Nur sollten Sie es nicht in einen geschwätzigen Papagei verwandeln, der alles wiederholt. Eine Zeile reicht, um zu verstehen, „wohin es ging“.

System.out.println("GET " + url); // GET https://openlibrary.org/search.json?q=clean%20code

So erzielen Sie einen sehr angenehmen Lerneffekt: Sie starten den Befehl, sehen die URL, sehen den Status und den Content-Type (die werden von CatalogHttpClient ausgegeben) und sehen eine kurze Vorschau des Antwortkörpers. Das ähnelt bereits einem kleinen Backend-Tool und nicht nur einer zufälligen Methode.

6. Start über Gradle prüfen

Man drückt gerne „Run“ in der IDE und gibt sich damit zufrieden. Aber das Kursziel ist, dass das Projekt reproduzierbar lebt, also starten wir über Gradle. Dazu genügt der Befehl:

./gradlew run --args="catalog search clean code"

Warum sind die Anführungszeichen wichtig? Weil --args= einen einzelnen String erhält und Gradle ihn dann in einzelne Argumente aufteilt. Ohne Anführungszeichen könnten manche Terminals oder IDE-Startkonfigurationen die Leerzeichen anders interpretieren, und Sie bekommen nicht das, was Sie erwarten.

Zum Debuggen können Sie vorübergehend args ausgeben:

System.out.println(Arrays.toString(args)); // [catalog, search, clean, code]

Wenn alles funktioniert, entfernen Sie diese Ausgabe besser wieder, sonst haben Sie für immer Rauschen in der Konsole. Aber als „Röntgenbild“ für ein paar Minuten ist es ein hervorragendes Werkzeug: Sie sehen sofort, in welcher Form Ihr Befehl tatsächlich bei main() angekommen ist.

Und noch ein wichtiges Detail: Der Aufruf geht jetzt ins Netz. Wenn Sie versehentlich eine falsche Domain angegeben haben, der externe Dienst nicht erreichbar ist oder Sie gar kein Internet haben, kann die Anwendung mit einer Exception abstürzen. Das ist vorerst in Ordnung: Wir bauen noch keine komfortable Fehlerbehandlung mit schönen Meldungen. Aber damit der Student psychologisch ruhiger ist, kann man den Aufruf in ein minimales try/catch packen und zumindest auf menschliche Weise sagen, was passiert ist.

try {
    String rawJson = catalogClient.getRawJson(url);
    System.out.println(preview(rawJson, 200));
} catch (Exception e) {
    System.out.println("Catalog request failed: " + e.getMessage());
}

Das ist kein „Fehlersystem“, es bedeutet nur: „Erschrecke den Benutzer nicht mit Stacktraces über den ganzen Bildschirm.“ Das Minimum.

An diesem Punkt ist die Basislinie grob, aber ehrlich: Die URL wird noch manuell zusammengebaut, Leerzeichen werden durch replace(" ", "%20") kodiert, und nach außen geht eine rohe JSON-Vorschau. Das ist normal. Jetzt ist wichtiger, dass der Befehl bereits reproduzierbar den gesamten Weg durchläuft: Startargumente → externer GET → Status und Content-Type → kurze Vorschau des Bodys.

7. Typische Fehler bei externen Aufrufen

Fehler Nr. 1: Der gesamte HTTP-Code bleibt innerhalb von main() und verwandelt den Einstiegspunkt in ein Chaos.
Das sieht so aus: Sie fügen HttpClient.newHttpClient(), HttpRequest.newBuilder(...), send(...) und die Antwortausgabe direkt in main() ein, und nach ein paar Änderungen wissen Sie nicht mehr, wo der Startbefehl und wo die HTTP-Mechanik ist. Die Behandlung ist sehr einfach: Lagern Sie eine kleine Klasse (CatalogHttpClient) und eine Methode aus, die den Aufruf durchführt.

Fehler Nr. 2: Zugriff auf args[0] und args[1] ohne Prüfung der Array-Länge.
Sie starten die Anwendung ohne Argumente – und sie stürzt ab. Sie starten sie als catalog search ohne Suchtext – und sie stürzt wieder ab. In main() sollten Sie immer zuerst args.length prüfen. Das ist banal, aber genau an solchen „Banalitäten“ stolpern Anfänger am häufigsten und denken dann: „Gradle ist kaputt“, „Java ist kaputt“, „der Computer ist gegen mich“.

Fehler Nr. 3: Leerzeichen in der URL und „warum meckert URI“.
URI.create("...q=clean code") ist eine schlechte Idee. Ein Leerzeichen in der URL ist kein „einfaches Zeichen“, es muss kodiert werden. In dieser Vorlesung haben wir die minimale Variante replace(" ", "%20") verwendet, um nicht in ein separates Thema abzudriften. Wenn Sie selbst das vergessen, kann URI eine Exception werfen oder die Anfrage ist einfach nicht korrekt.

Fehler Nr. 4: Das gesamte JSON auszugeben und sich zu wundern, dass die Konsole „sehr lang“ geworden ist.
Große Antworten sind bei externen APIs normal. Wenn Sie alles wahllos ausgeben, verlieren Sie die nützliche Information im Rauschen. Machen Sie eine Vorschau mit 200–300 Zeichen, und geben Sie das vollständige JSON nur dann aus, wenn es wirklich nötig ist und Sie wissen, warum. Die Konsole ist kein Archivierungsprogramm und keine Datenbank.

Fehler Nr. 5: „Ich habe den Status nicht angesehen, aber JSON ist ja da, also ist alles gut.“
Manchmal gibt der Dienst auch bei einem Fehler JSON zurück, nur eben ein Fehler-JSON. Wenn Sie den statusCode() ignorieren, können Sie fröhlich einen Fehler als erfolgreiche Antwort betrachten. Auch wenn Sie noch keine korrekte Statusbehandlung implementieren, geben Sie zumindest den HTTP 200/404/500 aus, damit sich der Kopf daran gewöhnt: Body ohne Status ist nur die Hälfte des Bildes.

1
Aufgabe
Java Server, Level 14, Lektion 4
Gesperrt
`ReadLaterApplication` mit dem Befehl `catalog search-demo`
`ReadLaterApplication` mit dem Befehl `catalog search-demo`
1
Aufgabe
Java Server, Level 14, Lektion 4
Gesperrt
`ReadLaterApplication` mit dem Befehl `catalog search `
`ReadLaterApplication` mit dem Befehl `catalog search `
1
Umfrage/Quiz
HTTP-Client, Level 14, Lektion 4
Nicht verfügbar
HTTP-Client
Arbeiten mit Anfragen
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION