CodeGym /Kurse /Java Server /Server-Modus und HttpServer

Server-Modus und HttpServer

Java Server
Level 22, Lektion 0
Verfügbar

1. Kontext: Client und Server

Betrachtet man das Projekt von oben, so ist der heutige Stand der Moment, in dem ReadLater Starter aufhört, eine „Konsolenanwendung mit HTTP-Client“ zu sein, und beginnt, sich wie ein kleiner Backend zu verhalten. Es ist nicht „nur ein weiterer Modus pro forma“, sondern ein Rollenwechsel: Früher waren wir derjenige, der an die API eines anderen klopft, jetzt sind wir derjenige, an den Postman, der Browser oder ein anderer Client klopfen wird. Und hier passiert etwas Magisches (eigentlich Ingenieurstechnisches): Die Anwendung macht nicht mehr „die Arbeit und geht“, sondern sie „postiert sich“ und wartet auf eingehende Anfragen.

Eine einfache Vergleichstabelle hilft. Sie geht nicht darum, „wer cooler ist“, sondern darum, dass sich der Kontext im Kopf des Entwicklers ändert:

Merkmal Client-Modus (catalog ...) Server-Modus (server)
Wer initiiert die Aktion unsere Anwendung externer Client (Postman/Browser/cURL)
HTTP-Richtung ausgehende Anfragen eingehende Anfragen
Lebenszyklus ausgeführt → angezeigt → beendet gestartet → hört auf Port → läuft lange
Hauptkriterium „funktioniert“ wir haben die erwartete Antwort vom Anbieter erhalten der Client hat eine korrekte Antwort von uns erhalten
Testwerkzeug Logs + Konsolenausgabe Postman/Browser + Logs

Darin liegt die entscheidende Veränderung: Wir lernen, nicht mehr zu denken „ich habe eine Methode aufgerufen und ein Ergebnis erhalten“, sondern „jemand von außen hat eine Anfrage gestellt, und ich bin verpflichtet, eine korrekte HTTP-Antwort zurückzugeben“.

Aber die Idee eines Server-Modus ist nur so lange nützlich, bis man anfängt zu fragen: Wer hört auf dem Port? Wer bindet den Pfad? Und über welches Objekt wird überhaupt die Antwort erzeugt? Sobald diese Rollen klar sind, wirkt der Server nicht mehr wie Magie, sondern wird zu einem normalen ingenieurtechnischen Mechanismus.

2. Der server-Modus in der Anwendung

Am Anfang möchte man es schnell machen: „Lass‘ uns einfach in main() ein if einbauen und den Server starten“. Das ist in etwa so, als würde man ein Auto mit Isolierband reparieren: Manchmal funktioniert es, aber später schämt man sich sogar vor sich selbst (und manchmal qualmt es auch). Wir führen einen expliziten Launch-Modus SERVER ein, weil das Projekt ein einheitliches Artefakt mit vorhersagbarem Verhalten bleiben soll. Wir haben bereits vereinbart, dass die Anwendung die Modi CATALOG_SEARCH, CATALOG_DETAILS hat, und nun kommt ein dritter hinzu – SERVER. Das macht den Start lesbar, das Logging verständlich und die Konfiguration ohne Hokuspokus anwendbar.

Noch wichtiger: Im Server-Modus hat die Anwendung einen anderen Lebenszyklus. Wenn der Client-Modus wie „einmal zum Bäcker gehen“ ist (hingehen, kaufen, zurückkommen), dann ist der Server-Modus wie „einen Laden eröffnen“ (ein Schild aufstellen, die Kasse einschalten und den ganzen Tag dastehen). Und wenn man diese beiden Szenarien in einem undefinierten Zweig vermischt, bekommt man schnell eine Anwendung, die schwer zu starten, schwer zu erklären und noch schwerer zu warten ist.

Eine minimale „Fixierung“ des Modus sieht so aus – ohne magische Zeichenketten, ohne versteckte if-Abfragen:

public enum LaunchMode {
    // Client-Modus: ausgehende Anfrage für die Suche stellen
    CATALOG_SEARCH,

    // Client-Modus: ausgehende Anfrage für Details stellen
    CATALOG_DETAILS,

    // Server-Modus: HTTP-Server hochfahren und auf eingehende Anfragen warten
    SERVER
}

Und im Einstiegspunkt wählen wir explizit den Verhaltenszweig aus. Selbst wenn es später viele Details gibt, muss die Verzweigung einfach und lesbar sein:

import static com.example.readlater.app.LaunchMode.*;

// launchMode wird aus Argumenten/Konfiguration vor dieser Stelle berechnet
switch (launchMode) {
    case CATALOG_SEARCH -> runCatalogSearch(args);
    case CATALOG_DETAILS -> runCatalogDetails(args);

    // Wichtig: hier beginnt der langlebige Modus der Anwendung
    case SERVER -> startServer(appConfig);
}

Beachten Sie, wie „unscheinbar-einfach“ das ist: Wir versuchen nicht, schlauer zu sein als nötig. Jetzt ist es wichtiger, dass der Student das Verhalten der Anwendung mit den Augen lesen und verstehen kann: „Aha, hier ist der Server-Modus, hier sind die Client-Modi“.

3. Das Ziel des Server-Modus für heute

Wenn man „Server-Modus“ hört, malt das Gehirn sofort etwas Großes: „Endpunkte, Datenbanken, Authentifizierung, hübsche JSON-Antworten, wo bleibt Swagger“. Ganz ruhig. Wir halten den Fokus bewusst eng: Heute geht es darum, die Tatsache einer eingehenden HTTP-Anfrage und die Tatsache einer korrekten Antwort zu sehen. Alles andere ist die nächste Komplexitätsschicht, und wenn man sofort hineinspringt, wird die Server-Phase zu einem Chaos aus Details.

In einem normalen Backend-Projekt gibt es einen technischen, langweiligen, aber unglaublich nützlichen Endpunkt – den Health Check. Er beantwortet eine einfache Frage: „Lebt der Dienst überhaupt?“. Nicht „alle Funktionen arbeiten“, nicht „die Geschäftslogik ist perfekt“, sondern einfach „der Prozess ist hochgefahren und kann über das Netzwerk antworten“. Wir beginnen genau damit, weil es der ehrlichste minimale Vertrag zwischen Client und Server ist.

Und hier ist ein wichtiger Gedanke: Das Ergebnis des Server-Modus bemisst sich nicht daran, dass Sie in der Konsole „Server started“ gesehen haben, sondern daran, dass ein externer Client die erwartete HTTP-Antwort erhalten hat. Das Erfolgskriterium verschiebt sich also von der Welt „ich habe etwas gestartet“ in die Welt „jemand anderes hat ein Ergebnis erhalten“.

Um diesen Gedanken zu festigen, ist es hilfreich, ein einfaches Schema zu sehen:

sequenceDiagram
    participant C as "Client: Postman/Browser"
    participant A as "ReadLater Starter server-mode"

    C->>A: "GET /health"
    A->>C: "200 OK + JSON"

In diesem Schema gibt es keine „Geschäftslogik“, und das ist wunderbar. Uns geht es jetzt um die Mechanik: Die Anfrage ist angekommen, der Handler hat gearbeitet, die Antwort wurde gesendet.

4. HttpServer aus dem JDK als Lernbrücke

Man kann leicht in zwei Extreme verfallen. Das erste Extrem: „Da es im JDK einen HttpServer gibt, kann man Produktivcode damit schreiben“. Das zweite: „Wenn es nicht Spring Boot ist, ist es völlig sinnlos“. Beide Extreme sind schädlich, also setzen wir einen sauberen Rahmen.

Der HttpServer aus dem JDK (Modul jdk.httpserver, Paket com.sun.net.httpserver) ist eine minimale integrierte Server-API, mit der man einen HTTP-Server ohne externe Abhängigkeiten und ohne Frameworks hochziehen kann. Er versucht nicht, ein „vollwertiger Web-Stack“ zu sein, und das kommt uns jetzt gerade recht: Wir können den Code vollständig lesen und sehen, wo die Server-Magie beginnt und wo sie endet. Es wird also keine Magie geben – es wird „handgemacht“.

Warum ist das vor Spring nützlich? Weil Spring MVC später einen großen Teil der Routine übernimmt: Routing, JSON-Bindung zu DTOs, Setzen von Headern, Fehlerbehandlung, korrekte Statuscodes und vieles mehr. Aber wenn Sie noch nie gesehen haben, wie diese Arbeit von Hand aussieht, wird Spring wie „Zaubersprüche mit Annotationen“ wirken. Unser Ziel ist, dass der Student nach diesem Kurs denkt: „Aha, ich weiß, was da ungefähr intern passiert, es ist nur automatisiert“.

Dabei sei ehrlich: Der HttpServer im JDK ist nicht das Werkzeug, das man normalerweise für Produktiv-Backends wählt. Es gibt Einschränkungen im Ökosystem, bei der Benutzerfreundlichkeit und Erweiterbarkeit. In realen Projekten leben häufiger Server auf Basis von Spring Boot (Tomcat/Jetty/Netty), Quarkus, Micronaut usw. Aber heute wählen wir nicht „die beste Waffe für die Schlacht“, sondern nehmen einen „transparenten Trainingsgerät“, um die Mechanik zu verstehen.

5. Erfolgskriterium: /health

Eine sehr menschliche Anfängerfalle ist zu glauben: Wenn die Anwendung nicht „abgestürzt“ ist und im Terminal „hängt“, dann funktioniert sie. Im Server-Modus ist das nicht so. Der Server kann starten, aber auf dem falschen Port lauschen, er kann auf dem falschen Interface lauschen, er kann mit dem falschen Content-Type antworten, er kann schon bei der ersten Anfrage abstürzen. Und von außen sieht das alles gleich aus: „irgendwas antwortet nicht“.

Deshalb legen wir vorab ein klares und messbares Ergebnis fest. Am Ende des Tages sollten wir einen Endpunkt haben:

Pfad: /health

Methode: GET

Status: 200 OK

Body: ein kleines JSON, zum Beispiel:

{
  "status": "UP",
  "appName": "readlater-starter"
}

Beachten Sie: Diese Antwort hat nichts mit der Buch-Lesedomäne zu tun, nicht mit der Reading-List und nicht mit CRUD. Sie ist technisch. Und genau deshalb ist sie ideal als Ausgangspunkt: Wir trainieren die Mechanik, ohne uns von der Fachdomäne ablenken zu lassen.

Und noch ein wichtiger Gedanke: Der Server-Modus sollte auf dem aufbauen, was wir bereits haben. Host/Port nehmen wir aus der Konfiguration, den Serverstart loggen wir über SLF4J, den Startmodus spiegeln wir explizit in den Logs wider. Das heißt, wir starten nicht „eine neue Anwendung“, sondern entwickeln denselben ReadLater Starter weiter, nur in einer neuen Rolle.

Die Startmechanik selbst ist hier noch nicht das Wichtigste. Zunächst reicht es, das externe Bereitschaftskriterium festzulegen: GET /health muss die erwartete HTTP-Antwort zurückgeben. Wenn der Server nicht antwortet, schaut man zuerst auf die Adresse und den Port, anstatt im Code zu rätseln.

6. Typische Fehler beim Arbeiten mit dem Server-Modus

In dieser Phase sind Fehler fast nie „komplex“, sondern „menschlich“: Man will schnell ein Ergebnis sehen und bricht dabei den sauberen Architekturrahmen, den man in den letzten Tagen selbst aufgebaut hat. Die gute Nachricht: Diese Fehler lassen sich leicht vermeiden, wenn man vorher versteht, wie sie aussehen.

Fehler Nr. 1: Den Server-Modus als separates Projekt oder als separate main()-Klasse auslagern, „damit es nicht stört“.
Das erscheint genau einen Abend lang bequem. Danach beginnt man, Konfiguration, Logging und JSON-Mapping zu duplizieren, und plötzlich hat man zwei Halbprojekte statt einem. Dem Sinn des Kurses nach muss ReadLater Starter ein einheitliches Artefakt bleiben, sonst verliert man die Hauptfähigkeit: das Wachstum der Anwendung zu steuern, ohne in Chaos zu verfallen.

Fehler Nr. 2: Den Serverstart in ein zufälliges if mit einer magischen Zeichenkette verstecken.
Eine Konstruktion wie if ("server".equals(args[0])) { ... } ohne normalen Startmodus verwandelt den Einstiegspunkt schnell in eine Ansammlung von Zufälligkeiten. Nach einer Woche vergessen Sie selbst, wie man die Anwendung richtig startet, und die Startlogik sieht aus wie eine Sammlung von „vorläufigen Lösungen“, die den Autor irgendwie überlebt haben.

Fehler Nr. 3: Sofort viele Pfade und eine „echte API“ hinzufügen, aber das minimale Bereitschaftskriterium vergessen.
Man kann sich leicht verleiten lassen und anfangen, GET /api/v1/reading-list, POST, Validierung usw. zu zeichnen. Aber ohne ein abgestimmtes minimales Server-Skelett erhält man die Situation „nichts funktioniert und man weiß nicht warum“. /health ist genau dafür da: Als erstes zu beweisen, dass der Server überhaupt antwortet und der Antwortvertrag kontrolliert wird.

Fehler Nr. 4: Das Tagesergebnis als „Server gestartet“ betrachten und die HTTP-Antwort nicht mit einem externen Client überprüfen.
Der Log „Server started“ ist eine Behauptung des Servers über seinen eigenen Erfolg. Das ist ihm zwar angenehm, aber uns interessiert die Realität: Was sieht Postman? Solange keine echte HTTP-Anfrage gestellt und eine 200 mit korrektem JSON und Content-Type empfangen wurde, ist es zu früh, die Aufgabe als erledigt zu betrachten.

Fehler Nr. 5: Gerade dann zu System.out.println() zurückzukehren, wenn die Server-Phase beginnt.
Paradoxerweise passiert das oft: Im Client-Modus wurde bereits Logging eingerichtet, aber im Server-Modus „nun, ich werde es mal schnell ausgeben“. Dann sieht man im Log den Modus nicht, die Adresse nicht, die Anfragen nicht, die Statuscodes nicht, und die Diagnose wird zum Kaffeesatzlesen. Wenn wir schon einen Backend-Ansatz lernen, dann sollten der Serverstart und alle wichtigen Ereignisse ordentlich geloggt werden.

1
Aufgabe
Java Server, Level 22, Lektion 0
Gesperrt
Expliziter Startmodus `server`
Expliziter Startmodus `server`
1
Aufgabe
Java Server, Level 22, Lektion 0
Gesperrt
Separater Launcher für den `server`-Modus
Separater Launcher für den `server`-Modus
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION