fetch_ml/internal/api/server.go
Jeremie Fraeys 420de879ff
feat(api): integrate scheduler protocol and WebSocket enhancements
Update API layer for scheduler integration:
- WebSocket handlers with scheduler protocol support
- Jobs WebSocket endpoint with priority queue integration
- Validation middleware for scheduler messages
- Server configuration with security hardening
- Protocol definitions for worker-scheduler communication
- Dataset handlers with tenant isolation checks
- Response helpers with audit context
- OpenAPI spec updates for new endpoints
2026-02-26 12:05:57 -05:00

149 lines
3.6 KiB
Go

package api
import (
"context"
"net/http"
"os"
"os/signal"
"syscall"
"time"
apimiddleware "github.com/jfraeys/fetch_ml/internal/api/middleware"
"github.com/jfraeys/fetch_ml/internal/audit"
"github.com/jfraeys/fetch_ml/internal/experiment"
"github.com/jfraeys/fetch_ml/internal/jupyter"
"github.com/jfraeys/fetch_ml/internal/logging"
"github.com/jfraeys/fetch_ml/internal/middleware"
"github.com/jfraeys/fetch_ml/internal/prommetrics"
"github.com/jfraeys/fetch_ml/internal/queue"
"github.com/jfraeys/fetch_ml/internal/storage"
)
// Server represents the API server
type Server struct {
taskQueue queue.Backend
config *ServerConfig
httpServer *http.Server
logger *logging.Logger
expManager *experiment.Manager
db *storage.DB
sec *middleware.SecurityMiddleware
jupyterServiceMgr *jupyter.ServiceManager
auditLogger *audit.Logger
promMetrics *prommetrics.Metrics
validationMiddleware *apimiddleware.ValidationMiddleware
cleanupFuncs []func()
}
// NewServer creates a new API server
func NewServer(configPath string) (*Server, error) {
// Load configuration
cfg, err := LoadServerConfig(configPath)
if err != nil {
return nil, err
}
if err := cfg.Validate(); err != nil {
return nil, err
}
server := &Server{
config: cfg,
}
// Initialize components
if err := server.initializeComponents(); err != nil {
return nil, err
}
// Setup HTTP server
server.setupHTTPServer()
return server, nil
}
// setupHTTPServer sets up the HTTP server and routes
func (s *Server) setupHTTPServer() {
mux := http.NewServeMux()
// Register all routes
s.registerRoutes(mux)
// Wrap with middleware
finalHandler := s.wrapWithMiddleware(mux)
finalHandler = s.wrapWithMetrics(finalHandler)
s.httpServer = &http.Server{
Addr: s.config.Server.Address,
Handler: finalHandler,
ReadTimeout: 30 * time.Second,
WriteTimeout: 30 * time.Second,
IdleTimeout: 120 * time.Second,
}
}
// Start starts the server
func (s *Server) Start() error {
if !s.config.Server.TLS.Enabled {
s.logger.Warn(
"TLS disabled for API server; do not use this configuration in production",
"address", s.config.Server.Address,
)
}
go func() {
var err error
if s.config.Server.TLS.Enabled {
s.logger.Info("starting HTTPS server", "address", s.config.Server.Address)
err = s.httpServer.ListenAndServeTLS(
s.config.Server.TLS.CertFile,
s.config.Server.TLS.KeyFile,
)
} else {
s.logger.Info("starting HTTP server", "address", s.config.Server.Address)
err = s.httpServer.ListenAndServe()
}
if err != nil && err != http.ErrServerClosed {
s.logger.Error("server failed", "error", err)
}
os.Exit(1)
}()
return nil
}
// WaitForShutdown waits for shutdown signals and gracefully shuts down the server
func (s *Server) WaitForShutdown() {
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
sig := <-sigChan
s.logger.Info("received shutdown signal", "signal", sig)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
s.logger.Info("shutting down http server...")
if err := s.httpServer.Shutdown(ctx); err != nil {
s.logger.Error("server shutdown error", "error", err)
} else {
s.logger.Info("http server shutdown complete")
}
// Run cleanup functions
for _, cleanup := range s.cleanupFuncs {
cleanup()
}
s.logger.Info("api server stopped")
}
// Close cleans up server resources
func (s *Server) Close() error {
// Run all cleanup functions
for _, cleanup := range s.cleanupFuncs {
cleanup()
}
return nil
}