skyview/internal/server/server.go

210 lines
5 KiB
Go
Raw Normal View History

package server
import (
"context"
"embed"
"encoding/json"
"log"
"net/http"
"sync"
"github.com/gorilla/mux"
"github.com/gorilla/websocket"
"skyview/internal/client"
"skyview/internal/config"
"skyview/internal/parser"
)
type Server struct {
config *config.Config
staticFiles embed.FS
upgrader websocket.Upgrader
wsClients map[*websocket.Conn]bool
wsClientsMux sync.RWMutex
dump1090 *client.Dump1090Client
ctx context.Context
}
type WebSocketMessage struct {
Type string `json:"type"`
Data interface{} `json:"data"`
}
func New(cfg *config.Config, staticFiles embed.FS, ctx context.Context) http.Handler {
s := &Server{
config: cfg,
staticFiles: staticFiles,
upgrader: websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return true
},
},
wsClients: make(map[*websocket.Conn]bool),
dump1090: client.NewDump1090Client(cfg),
ctx: ctx,
}
if err := s.dump1090.Start(ctx); err != nil {
log.Printf("Failed to start dump1090 client: %v", err)
}
go s.subscribeToAircraftUpdates()
router := mux.NewRouter()
router.HandleFunc("/", s.serveIndex).Methods("GET")
router.HandleFunc("/ws", s.handleWebSocket).Methods("GET")
apiRouter := router.PathPrefix("/api").Subrouter()
apiRouter.HandleFunc("/aircraft", s.getAircraft).Methods("GET")
apiRouter.HandleFunc("/stats", s.getStats).Methods("GET")
apiRouter.HandleFunc("/config", s.getConfig).Methods("GET")
router.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.FS(s.staticFiles))))
return s.enableCORS(router)
}
func (s *Server) serveIndex(w http.ResponseWriter, r *http.Request) {
data, err := s.staticFiles.ReadFile("static/index.html")
if err != nil {
http.Error(w, "Failed to read index.html", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "text/html")
w.Write(data)
}
func (s *Server) getAircraft(w http.ResponseWriter, r *http.Request) {
data := s.dump1090.GetAircraftData()
response := map[string]interface{}{
"now": data.Now,
"messages": data.Messages,
"aircraft": s.aircraftMapToSlice(data.Aircraft),
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
}
func (s *Server) getStats(w http.ResponseWriter, r *http.Request) {
data := s.dump1090.GetAircraftData()
stats := map[string]interface{}{
"total": map[string]interface{}{
"aircraft": len(data.Aircraft),
"messages": map[string]interface{}{
"total": data.Messages,
"last1min": data.Messages,
},
},
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(stats)
}
func (s *Server) getConfig(w http.ResponseWriter, r *http.Request) {
configData := map[string]interface{}{
"origin": map[string]interface{}{
"latitude": s.config.Origin.Latitude,
"longitude": s.config.Origin.Longitude,
"name": s.config.Origin.Name,
},
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(configData)
}
func (s *Server) aircraftMapToSlice(aircraftMap map[string]parser.Aircraft) []parser.Aircraft {
aircraft := make([]parser.Aircraft, 0, len(aircraftMap))
for _, a := range aircraftMap {
aircraft = append(aircraft, a)
}
return aircraft
}
func (s *Server) subscribeToAircraftUpdates() {
updates := s.dump1090.Subscribe()
for data := range updates {
message := WebSocketMessage{
Type: "aircraft_update",
Data: map[string]interface{}{
"now": data.Now,
"messages": data.Messages,
"aircraft": s.aircraftMapToSlice(data.Aircraft),
},
}
s.broadcastToWebSocketClients(message)
}
}
func (s *Server) handleWebSocket(w http.ResponseWriter, r *http.Request) {
conn, err := s.upgrader.Upgrade(w, r, nil)
if err != nil {
log.Printf("WebSocket upgrade error: %v", err)
return
}
defer conn.Close()
s.wsClientsMux.Lock()
s.wsClients[conn] = true
s.wsClientsMux.Unlock()
defer func() {
s.wsClientsMux.Lock()
delete(s.wsClients, conn)
s.wsClientsMux.Unlock()
}()
data := s.dump1090.GetAircraftData()
initialMessage := WebSocketMessage{
Type: "aircraft_update",
Data: map[string]interface{}{
"now": data.Now,
"messages": data.Messages,
"aircraft": s.aircraftMapToSlice(data.Aircraft),
},
}
conn.WriteJSON(initialMessage)
for {
_, _, err := conn.ReadMessage()
if err != nil {
break
}
}
}
func (s *Server) broadcastToWebSocketClients(message WebSocketMessage) {
s.wsClientsMux.RLock()
defer s.wsClientsMux.RUnlock()
for client := range s.wsClients {
if err := client.WriteJSON(message); err != nil {
client.Close()
delete(s.wsClients, client)
}
}
}
func (s *Server) enableCORS(handler 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", "Content-Type, Authorization")
if r.Method == "OPTIONS" {
w.WriteHeader(http.StatusOK)
return
}
handler.ServeHTTP(w, r)
})
}