299 lines
8.2 KiB
Go

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)
}