3fec89be7f
- Add HTTPProber: parallel HEAD+GET on ports 80/8080, extracts Server header - Reduce mDNS timeout from 1s to 100ms using context wrapper around mdns.Query (HomeKit devices respond in 2-10ms, no need to wait 1s) - Add Trassir (F0:23:B9) and ZOSI (00:05:FE) to camera OUI database - Probe response time improved from ~1s to ~110ms for reachable devices
162 lines
4.5 KiB
Go
162 lines
4.5 KiB
Go
package api
|
|
|
|
import (
|
|
"net/http"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
"github.com/go-chi/chi/v5/middleware"
|
|
"github.com/eduard256/Strix/internal/api/handlers"
|
|
"github.com/eduard256/Strix/internal/camera/database"
|
|
"github.com/eduard256/Strix/internal/camera/discovery"
|
|
"github.com/eduard256/Strix/internal/camera/stream"
|
|
"github.com/eduard256/Strix/internal/config"
|
|
"github.com/eduard256/Strix/pkg/sse"
|
|
)
|
|
|
|
// Server represents the API server
|
|
type Server struct {
|
|
router chi.Router
|
|
config *config.Config
|
|
loader *database.Loader
|
|
searchEngine *database.SearchEngine
|
|
scanner *discovery.Scanner
|
|
probeService *discovery.ProbeService
|
|
sseServer *sse.Server
|
|
logger interface{ Debug(string, ...any); Error(string, error, ...any); Info(string, ...any) }
|
|
}
|
|
|
|
// NewServer creates a new API server
|
|
func NewServer(
|
|
cfg *config.Config,
|
|
logger interface{ Debug(string, ...any); Error(string, error, ...any); Info(string, ...any) },
|
|
) (*Server, error) {
|
|
// Initialize database loader
|
|
loader := database.NewLoader(
|
|
cfg.Database.BrandsPath,
|
|
cfg.Database.PatternsPath,
|
|
cfg.Database.ParametersPath,
|
|
logger,
|
|
)
|
|
|
|
// Load query parameters for URL builder
|
|
queryParams, err := loader.LoadQueryParameters()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Initialize search engine
|
|
searchEngine := database.NewSearchEngine(loader, logger)
|
|
|
|
// Initialize stream components
|
|
builder := stream.NewBuilder(queryParams, logger)
|
|
tester := stream.NewTester(cfg.Scanner.FFProbeTimeout, logger)
|
|
|
|
// Initialize ONVIF discovery
|
|
onvif := discovery.NewONVIFDiscovery(logger)
|
|
|
|
// Initialize scanner
|
|
scannerConfig := discovery.ScannerConfig{
|
|
WorkerPoolSize: cfg.Scanner.WorkerPoolSize,
|
|
DefaultTimeout: cfg.Scanner.DefaultTimeout,
|
|
MaxStreams: cfg.Scanner.MaxStreams,
|
|
ModelSearchLimit: cfg.Scanner.ModelSearchLimit,
|
|
FFProbeTimeout: cfg.Scanner.FFProbeTimeout,
|
|
}
|
|
|
|
scanner := discovery.NewScanner(
|
|
loader,
|
|
searchEngine,
|
|
builder,
|
|
tester,
|
|
onvif,
|
|
scannerConfig,
|
|
logger,
|
|
)
|
|
|
|
// Initialize SSE server
|
|
sseServer := sse.NewServer(logger)
|
|
|
|
// Initialize OUI database for vendor identification
|
|
ouiDB := discovery.NewOUIDatabase()
|
|
if err := ouiDB.LoadFromFile(cfg.Database.OUIPath); err != nil {
|
|
logger.Error("failed to load OUI database, vendor lookup will be unavailable", err)
|
|
} else {
|
|
logger.Info("OUI database loaded", "entries", ouiDB.Size())
|
|
}
|
|
|
|
// Initialize ProbeService with all probers
|
|
probers := []discovery.Prober{
|
|
&discovery.DNSProber{},
|
|
discovery.NewARPProber(ouiDB),
|
|
&discovery.MDNSProber{},
|
|
&discovery.HTTPProber{},
|
|
}
|
|
probeService := discovery.NewProbeService(probers, logger)
|
|
|
|
// Create server
|
|
server := &Server{
|
|
router: chi.NewRouter(),
|
|
config: cfg,
|
|
loader: loader,
|
|
searchEngine: searchEngine,
|
|
scanner: scanner,
|
|
probeService: probeService,
|
|
sseServer: sseServer,
|
|
logger: logger,
|
|
}
|
|
|
|
// Setup routes
|
|
server.setupRoutes()
|
|
|
|
return server, nil
|
|
}
|
|
|
|
// setupRoutes configures all routes and middleware
|
|
func (s *Server) setupRoutes() {
|
|
// Global middleware
|
|
s.router.Use(middleware.RequestID)
|
|
s.router.Use(middleware.RealIP)
|
|
s.router.Use(middleware.Logger)
|
|
s.router.Use(middleware.Recoverer)
|
|
// Note: No global timeout middleware - endpoints use context-based timeouts from request parameters
|
|
|
|
// CORS middleware
|
|
s.router.Use(func(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
|
|
w.Header().Set("Access-Control-Allow-Headers", "Accept, Authorization, Content-Type, X-Request-ID")
|
|
w.Header().Set("Access-Control-Max-Age", "3600")
|
|
|
|
if r.Method == "OPTIONS" {
|
|
w.WriteHeader(http.StatusNoContent)
|
|
return
|
|
}
|
|
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
})
|
|
|
|
// API routes (mounted at /api/v1 in main.go)
|
|
// Health check
|
|
s.router.Get("/health", handlers.NewHealthHandler("1.0.0", s.logger).ServeHTTP)
|
|
|
|
// Camera search
|
|
s.router.Post("/cameras/search", handlers.NewSearchHandler(s.searchEngine, s.logger).ServeHTTP)
|
|
|
|
// Stream discovery (SSE)
|
|
s.router.Post("/streams/discover", handlers.NewDiscoverHandler(s.scanner, s.sseServer, s.logger).ServeHTTP)
|
|
|
|
// Device probe (ping + DNS + ARP/OUI + mDNS)
|
|
s.router.Get("/probe", handlers.NewProbeHandler(s.probeService, s.logger).ServeHTTP)
|
|
}
|
|
|
|
// ServeHTTP implements http.Handler
|
|
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
s.router.ServeHTTP(w, r)
|
|
}
|
|
|
|
// GetRouter returns the chi router
|
|
func (s *Server) GetRouter() chi.Router {
|
|
return s.router
|
|
} |