internal/health: wiederverwendbare Registry fuer benannte Checks (DB, Queue) — nicht Core-spezifisch, sondern von jedem registrierten Modul (API-02) gleichermassen einsetzbar. LivenessHandler prueft bewusst KEINE externen Abhaengigkeiten (Akzeptanzkriterium 2: Liveness/Readiness getrennt) — ein DB-Ausfall soll den Prozess nicht faelschlich als "tot" markieren und einen grundlosen Neustart ausloesen. ReadinessHandler fuehrt alle registrierten Checks NEBENLAEUFIG mit je eigenem Timeout aus (DefaultCheckTimeout=2s) und liefert 503, sobald irgendeine Abhaengigkeit fehlschlaegt (Akzeptanz- kriterium 1 + 3) — echte Pruefung von DB (Ping) und Job-Queue statt nur Prozessstatus. Pruefungen (ausgefuehrt auf root@192.168.1.131, go build/vet/test PASS): 1. Simulierter Datenbankausfall fuehrt zu "nicht bereit" — TestReadinessHandler_ReportsNotReadyOnDatabaseFailure: geschlossener Pool, 503 mit "database" im Checks-Ergebnis. PASS. 2. Health-Endpunkt antwortet auch bei haengendem Check innerhalb definierter Zeit — TestReadinessHandler_RespondsWithinTimeoutEvenWithHangingCheck: ein 10s blockierender Check wird durch 50ms-Timeout begrenzt, Handler antwortet deutlich unter 1s. PASS. 3. Readiness- und Liveness-Antwort unterscheiden sich nachweislich in mindestens einem Fehlerfall — TestLivenessAndReadiness_DifferOnDatabaseFailure: bei DB-Ausfall liefert Liveness weiterhin 200, Readiness 503. PASS. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
50 lines
1.5 KiB
Go
50 lines
1.5 KiB
Go
package health
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
)
|
|
|
|
// LivenessHandler beantwortet IMMER "lebt", solange der Prozess ueberhaupt
|
|
// HTTP-Anfragen verarbeiten kann — prueft bewusst KEINE externen
|
|
// Abhaengigkeiten (Akzeptanzkriterium 2: Liveness und Readiness getrennt).
|
|
// Ein Datenbankausfall darf die Liveness nicht auf "tot" setzen, sonst
|
|
// wuerde eine Orchestrierung (z.B. systemd/Kubernetes) den Prozess grundlos
|
|
// neu starten, obwohl nur eine Abhaengigkeit ausgefallen ist.
|
|
func LivenessHandler(w http.ResponseWriter, r *http.Request) {
|
|
writeStatus(w, http.StatusOK, map[string]any{"status": "alive"})
|
|
}
|
|
|
|
// ReadinessHandler prueft ALLE registrierten Abhaengigkeiten
|
|
// (Akzeptanzkriterium 1) und liefert 503, sobald eine davon fehlschlaegt
|
|
// (Akzeptanzkriterium 3) — unterscheidet sich damit nachweislich von
|
|
// LivenessHandler im Fehlerfall (Akzeptanzkriterium 2 / Pruefung 3).
|
|
func (r *Registry) ReadinessHandler() http.HandlerFunc {
|
|
return func(w http.ResponseWriter, req *http.Request) {
|
|
ready, results := r.CheckAll(req.Context())
|
|
|
|
body := map[string]any{
|
|
"status": statusText(ready),
|
|
"checks": results,
|
|
}
|
|
status := http.StatusOK
|
|
if !ready {
|
|
status = http.StatusServiceUnavailable
|
|
}
|
|
writeStatus(w, status, body)
|
|
}
|
|
}
|
|
|
|
func statusText(ready bool) string {
|
|
if ready {
|
|
return "ready"
|
|
}
|
|
return "not_ready"
|
|
}
|
|
|
|
func writeStatus(w http.ResponseWriter, status int, body map[string]any) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(status)
|
|
_ = json.NewEncoder(w).Encode(body)
|
|
}
|