package main import ( "context" "encoding/json" "fmt" "net/http" "time" ) // setupWebServer initializes the HTTP server for admin interface func (w *World) setupWebServer() error { if w.config.WebPort == 0 { return nil // Web server disabled } mux := http.NewServeMux() // API endpoints mux.HandleFunc("/api/status", w.handleStatus) mux.HandleFunc("/api/clients", w.handleClients) mux.HandleFunc("/api/zones", w.handleZones) mux.HandleFunc("/api/stats", w.handleStats) mux.HandleFunc("/api/time", w.handleWorldTime) mux.HandleFunc("/api/shutdown", w.handleShutdown) // Administrative endpoints mux.HandleFunc("/api/admin/reload", w.handleReload) mux.HandleFunc("/api/admin/broadcast", w.handleBroadcast) mux.HandleFunc("/api/admin/kick", w.handleKickClient) // Peer management endpoints mux.HandleFunc("/api/peers", w.handlePeers) mux.HandleFunc("/api/peers/sync", w.handlePeerSync) // Console command interface mux.HandleFunc("/api/console", w.handleConsoleCommand) // Static health check mux.HandleFunc("/health", w.handleHealth) // @TODO: Add authentication middleware // @TODO: Add rate limiting middleware // @TODO: Add CORS middleware for browser access // @TODO: Add TLS support with cert/key files addr := fmt.Sprintf("%s:%d", w.config.WebAddr, w.config.WebPort) w.webServer = &http.Server{ Addr: addr, Handler: mux, ReadTimeout: 10 * time.Second, WriteTimeout: 10 * time.Second, } return nil } // Core API handlers // handleHealth provides a simple health check endpoint func (w *World) handleHealth(rw http.ResponseWriter, r *http.Request) { rw.Header().Set("Content-Type", "application/json") json.NewEncoder(rw).Encode(map[string]string{"status": "ok"}) } // handleStatus returns comprehensive server status information func (w *World) handleStatus(rw http.ResponseWriter, r *http.Request) { rw.Header().Set("Content-Type", "application/json") status := map[string]any{ "status": "running", "uptime": time.Since(w.stats.StartTime).Seconds(), "version": Version, "locked": w.config.WorldLocked, "primary": w.config.IsPrimary, "threaded": w.config.ThreadedLoad, "data_loaded": w.isDataLoaded(), "world_time": w.getWorldTime(), } json.NewEncoder(rw).Encode(status) } // handleClients returns list of connected clients func (w *World) handleClients(rw http.ResponseWriter, r *http.Request) { rw.Header().Set("Content-Type", "application/json") w.clientsMux.RLock() clients := make([]*ClientInfo, 0, len(w.clients)) for _, client := range w.clients { clients = append(clients, client) } w.clientsMux.RUnlock() json.NewEncoder(rw).Encode(map[string]any{ "count": len(clients), "clients": clients, }) } // handleZones returns list of zone servers func (w *World) handleZones(rw http.ResponseWriter, r *http.Request) { rw.Header().Set("Content-Type", "application/json") w.zonesMux.RLock() zones := make([]*ZoneInfo, 0, len(w.zones)) for _, zone := range w.zones { zones = append(zones, zone) } w.zonesMux.RUnlock() json.NewEncoder(rw).Encode(map[string]any{ "count": len(zones), "zones": zones, }) } // handleStats returns detailed server statistics func (w *World) handleStats(rw http.ResponseWriter, r *http.Request) { rw.Header().Set("Content-Type", "application/json") w.statsMux.RLock() stats := w.stats w.statsMux.RUnlock() // Add UDP server stats if available if w.udpServer != nil { serverStats := w.udpServer.GetStats() stats.TotalConnections = int64(serverStats.ConnectionCount) } json.NewEncoder(rw).Encode(stats) } // handleWorldTime returns current game world time func (w *World) handleWorldTime(rw http.ResponseWriter, r *http.Request) { rw.Header().Set("Content-Type", "application/json") json.NewEncoder(rw).Encode(w.getWorldTime()) } // Administrative handlers // handleShutdown initiates graceful server shutdown func (w *World) handleShutdown(rw http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(rw, "Method not allowed", http.StatusMethodNotAllowed) return } // @TODO: Add authentication check // @TODO: Add confirmation parameter // @TODO: Add delay parameter rw.Header().Set("Content-Type", "application/json") json.NewEncoder(rw).Encode(map[string]string{"status": "shutdown initiated"}) go func() { time.Sleep(time.Second) // Allow response to be sent w.Shutdown() }() } // handleReload reloads game data func (w *World) handleReload(rw http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(rw, "Method not allowed", http.StatusMethodNotAllowed) return } // @TODO: Add authentication check // @TODO: Implement selective reloading (items, spells, quests, etc.) // @TODO: Add progress reporting rw.Header().Set("Content-Type", "application/json") json.NewEncoder(rw).Encode(map[string]string{"status": "reload not implemented"}) } // handleBroadcast sends server-wide message func (w *World) handleBroadcast(rw http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(rw, "Method not allowed", http.StatusMethodNotAllowed) return } // @TODO: Add authentication check // @TODO: Parse message from request body // @TODO: Validate message content // @TODO: Send to all connected clients rw.Header().Set("Content-Type", "application/json") json.NewEncoder(rw).Encode(map[string]string{"status": "broadcast not implemented"}) } // handleKickClient disconnects a specific client func (w *World) handleKickClient(rw http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(rw, "Method not allowed", http.StatusMethodNotAllowed) return } // @TODO: Add authentication check // @TODO: Parse client ID from request // @TODO: Find and disconnect client // @TODO: Log kick action rw.Header().Set("Content-Type", "application/json") json.NewEncoder(rw).Encode(map[string]string{"status": "kick not implemented"}) } // Peer management handlers // handlePeers returns list of peer servers func (w *World) handlePeers(rw http.ResponseWriter, r *http.Request) { rw.Header().Set("Content-Type", "application/json") peers := make([]map[string]any, 0) for _, peer := range w.config.PeerServers { peerInfo := map[string]any{ "address": peer.Address, "port": peer.Port, "status": "unknown", // @TODO: Implement peer status checking } peers = append(peers, peerInfo) } json.NewEncoder(rw).Encode(map[string]any{ "count": len(peers), "peers": peers, }) } // handlePeerSync synchronizes data with peer servers func (w *World) handlePeerSync(rw http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(rw, "Method not allowed", http.StatusMethodNotAllowed) return } // @TODO: Add authentication check // @TODO: Implement peer synchronization // @TODO: Return sync status and results rw.Header().Set("Content-Type", "application/json") json.NewEncoder(rw).Encode(map[string]string{"status": "peer sync not implemented"}) } // Console command handler // handleConsoleCommand executes administrative commands func (w *World) handleConsoleCommand(rw http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(rw, "Method not allowed", http.StatusMethodNotAllowed) return } // @TODO: Add authentication check // @TODO: Parse command from request body // @TODO: Validate command permissions // @TODO: Execute command and return results // @TODO: Log command execution rw.Header().Set("Content-Type", "application/json") json.NewEncoder(rw).Encode(map[string]string{"status": "console commands not implemented"}) } // Helper methods for web handlers // getWorldTime returns thread-safe copy of world time func (w *World) getWorldTime() WorldTime { w.worldTimeMux.RLock() defer w.worldTimeMux.RUnlock() return w.worldTime } // startWebServer starts the web server in a goroutine func (w *World) startWebServer() { if w.webServer == nil { return } go func() { if err := w.webServer.ListenAndServe(); err != nil && err != http.ErrServerClosed { fmt.Printf("Web server error: %v\n", err) } }() } // stopWebServer gracefully stops the web server func (w *World) stopWebServer() error { if w.webServer == nil { return nil } ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() return w.webServer.Shutdown(ctx) }