diff --git a/internal/server/server.go b/internal/server/server.go index 76e89a8..2197f85 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -190,6 +190,9 @@ func (s *Server) Stop() { func (s *Server) setupRoutes() http.Handler { router := mux.NewRouter() + // Health check endpoint for load balancers/monitoring + router.HandleFunc("/health", s.handleHealthCheck).Methods("GET") + // API routes api := router.PathPrefix("/api").Subrouter() api.HandleFunc("/aircraft", s.handleGetAircraft).Methods("GET") @@ -240,6 +243,63 @@ func (s *Server) isAircraftUseful(aircraft *merger.AircraftState) bool { return hasValidPosition || hasCallsign || hasAltitude || hasSquawk } +// handleHealthCheck serves the /health endpoint for monitoring and load balancers. +// Returns a simple health status with basic service information. +// +// Response includes: +// - status: "healthy" or "degraded" +// - uptime: server uptime in seconds +// - sources: number of active sources and their connection status +// - aircraft: current aircraft count +// +// The endpoint returns: +// - 200 OK when the service is healthy +// - 503 Service Unavailable when the service is degraded (no active sources) +func (s *Server) handleHealthCheck(w http.ResponseWriter, r *http.Request) { + sources := s.merger.GetSources() + stats := s.merger.GetStatistics() + aircraft := s.merger.GetAircraft() + + // Check if we have any active sources + activeSources := 0 + for _, source := range sources { + if source.Active { + activeSources++ + } + } + + // Determine health status + status := "healthy" + statusCode := http.StatusOK + if activeSources == 0 && len(sources) > 0 { + status = "degraded" + statusCode = http.StatusServiceUnavailable + } + + response := map[string]interface{}{ + "status": status, + "timestamp": time.Now().Unix(), + "sources": map[string]interface{}{ + "total": len(sources), + "active": activeSources, + }, + "aircraft": map[string]interface{}{ + "count": len(aircraft), + }, + } + + // Add statistics if available + if stats != nil { + if totalMessages, ok := stats["total_messages"]; ok { + response["messages"] = totalMessages + } + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(statusCode) + json.NewEncoder(w).Encode(response) +} + // handleGetAircraft serves the /api/aircraft endpoint. // Returns all currently tracked aircraft with their latest state information. //