package network import ( "context" "fmt" "net/http" "sync/atomic" "github.com/gorilla/websocket" "a301_game_server/config" "a301_game_server/pkg/logger" ) // Server listens for WebSocket connections and creates Connection objects. type Server struct { cfg *config.Config upgrader websocket.Upgrader handler PacketHandler nextID atomic.Uint64 srv *http.Server } // NewServer creates a new WebSocket server. func NewServer(cfg *config.Config, handler PacketHandler) *Server { return &Server{ cfg: cfg, handler: handler, upgrader: websocket.Upgrader{ ReadBufferSize: cfg.Network.ReadBufferSize, WriteBufferSize: cfg.Network.WriteBufferSize, CheckOrigin: func(r *http.Request) bool { return true }, }, } } // Start begins listening for connections. Blocks until the context is cancelled. func (s *Server) Start(ctx context.Context) error { mux := http.NewServeMux() mux.HandleFunc("/ws", s.handleWebSocket) mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) _, _ = w.Write([]byte("ok")) }) addr := s.cfg.Server.Address() s.srv = &http.Server{ Addr: addr, Handler: mux, } errCh := make(chan error, 1) go func() { logger.Info("websocket server starting", "address", addr) if err := s.srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { errCh <- fmt.Errorf("listen: %w", err) } }() select { case err := <-errCh: return err case <-ctx.Done(): logger.Info("shutting down websocket server") return s.srv.Shutdown(context.Background()) } } func (s *Server) handleWebSocket(w http.ResponseWriter, r *http.Request) { ws, err := s.upgrader.Upgrade(w, r, nil) if err != nil { logger.Error("websocket upgrade failed", "error", err, "remote", r.RemoteAddr) return } connID := s.nextID.Add(1) conn := NewConnection( connID, ws, s.handler, s.cfg.Network.SendChannelSize, s.cfg.Network.MaxMessageSize, s.cfg.Network.HeartbeatInterval, s.cfg.Network.HeartbeatTimeout, ) logger.Info("client connected", "connID", connID, "remote", r.RemoteAddr) conn.Start() }