gomcp/internal/transport/http/server.go

104 lines
2.9 KiB
Go
Raw Normal View History

// Package httpserver provides an HTTP API transport for GoMCP SOC dashboard.
//
// Zero CGO: Uses ONLY Go stdlib net/http (supports HTTP/2 natively).
// Backward compatible: disabled by default (--http-port 0).
package httpserver
import (
"context"
"encoding/json"
"fmt"
"log"
"net/http"
"time"
appsoc "github.com/sentinel-community/gomcp/internal/application/soc"
)
// Server provides HTTP API endpoints for SOC monitoring.
type Server struct {
socSvc *appsoc.Service
threatIntel *appsoc.ThreatIntelStore
port int
srv *http.Server
}
// New creates an HTTP server bound to the given port.
func New(socSvc *appsoc.Service, port int) *Server {
return &Server{
socSvc: socSvc,
port: port,
}
}
// SetThreatIntel sets the threat intel store for API access.
func (s *Server) SetThreatIntel(store *appsoc.ThreatIntelStore) {
s.threatIntel = store
}
// Start begins listening on the configured port. Blocks until ctx is cancelled.
func (s *Server) Start(ctx context.Context) error {
mux := http.NewServeMux()
// SOC API routes
mux.HandleFunc("GET /api/soc/dashboard", s.handleDashboard)
mux.HandleFunc("GET /api/soc/events", s.handleEvents)
mux.HandleFunc("GET /api/soc/incidents", s.handleIncidents)
mux.HandleFunc("GET /api/soc/sensors", s.handleSensors)
mux.HandleFunc("GET /api/soc/threat-intel", s.handleThreatIntel)
mux.HandleFunc("GET /api/soc/webhook-stats", s.handleWebhookStats)
mux.HandleFunc("GET /api/soc/analytics", s.handleAnalytics)
// Health check
mux.HandleFunc("GET /health", s.handleHealth)
// Wrap with CORS middleware
handler := corsMiddleware(mux)
s.srv = &http.Server{
Addr: fmt.Sprintf(":%d", s.port),
Handler: handler,
ReadHeaderTimeout: 10 * time.Second,
WriteTimeout: 30 * time.Second,
IdleTimeout: 60 * time.Second,
}
// Graceful shutdown on context cancellation
go func() {
<-ctx.Done()
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := s.srv.Shutdown(shutdownCtx); err != nil {
log.Printf("HTTP server shutdown error: %v", err)
}
}()
log.Printf("HTTP API listening on :%d", s.port)
if err := s.srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
return fmt.Errorf("http server: %w", err)
}
return nil
}
// Stop gracefully shuts down the HTTP server.
func (s *Server) Stop(ctx context.Context) error {
if s.srv == nil {
return nil
}
return s.srv.Shutdown(ctx)
}
// writeJSON writes a JSON response with the given status code.
func writeJSON(w http.ResponseWriter, status int, v any) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
if err := json.NewEncoder(w).Encode(v); err != nil {
log.Printf("HTTP: failed to encode response: %v", err)
}
}
// writeError writes a JSON error response.
func writeError(w http.ResponseWriter, status int, msg string) {
writeJSON(w, status, map[string]string{"error": msg})
}