299 lines
8.2 KiB
Go
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)
|
|
}
|