package router import ( "context" "time" "github.com/gin-gonic/gin" "github.com/goccy/go-json" ws "github.com/gorilla/websocket" "github.com/pterodactyl/wings/router/middleware" "github.com/pterodactyl/wings/router/websocket" ) var expectedCloseCodes = []int{ ws.CloseGoingAway, ws.CloseAbnormalClosure, ws.CloseNormalClosure, ws.CloseNoStatusReceived, ws.CloseServiceRestart, } // Upgrades a connection to a websocket and passes events along between. func getServerWebsocket(c *gin.Context) { manager := middleware.ExtractManager(c) s, _ := manager.Get(c.Param("server")) // Create a context that can be canceled when the user disconnects from this // socket that will also cancel listeners running in separate threads. If the // connection itself is terminated listeners using this context will also be // closed. ctx, cancel := context.WithCancel(c.Request.Context()) defer cancel() handler, err := websocket.GetHandler(s, c.Writer, c.Request, c) if err != nil { middleware.CaptureAndAbort(c, err) return } defer handler.Connection.Close() // Track this open connection on the server so that we can close them all programmatically // if the server is deleted. s.Websockets().Push(handler.Uuid(), &cancel) handler.Logger().Debug("opening connection to server websocket") defer func() { s.Websockets().Remove(handler.Uuid()) handler.Logger().Debug("closing connection to server websocket") }() // If the server is deleted we need to send a close message to the connected client // so that they disconnect since there will be no more events sent along. Listen for // the request context being closed to break this loop, otherwise this routine will // be left hanging in the background. go func() { select { case <-ctx.Done(): break case <-s.Context().Done(): _ = handler.Connection.WriteControl(ws.CloseMessage, ws.FormatCloseMessage(ws.CloseGoingAway, "server deleted"), time.Now().Add(time.Second*5)) break } }() for { j := websocket.Message{} _, p, err := handler.Connection.ReadMessage() if err != nil { if ws.IsUnexpectedCloseError(err, expectedCloseCodes...) { handler.Logger().WithField("error", err).Warn("error handling websocket message for server") } break } // Discard and JSON parse errors into the void and don't continue processing this // specific socket request. If we did a break here the client would get disconnected // from the socket, which is NOT what we want to do. if err := json.Unmarshal(p, &j); err != nil { continue } go func(msg websocket.Message) { if err := handler.HandleInbound(ctx, msg); err != nil { _ = handler.SendErrorJson(msg, err) } }(j) } }