From f5f9e4f619b5d207e885df3c1d45a7da57a2a62e Mon Sep 17 00:00:00 2001 From: alex Date: Thu, 20 Apr 2023 22:34:06 +0200 Subject: [PATCH] added websocket --- go.mod | 2 + go.sum | 4 ++ groupTasks/groups/janex-acryl/index.json | 3 + groupTasks/groups/production1/index.json | 46 +++++++++++++++ groupTasks/groups/roese/index.json | 3 + groupTasks/groups/umbach/index.json | 3 + main.go | 48 ++++++++++++++++ modules/cache/cache.go | 64 +++++++++++++++++++++ modules/structs/socket.go | 73 ++++++++++++++++++++++++ modules/structs/user.go | 4 -- routers/router/api/v1/user/user.go | 2 +- routers/router/router.go | 1 + socketclients/socketclients.go | 23 ++++++++ socketserver/hub.go | 51 +++++++++++++++++ socketserver/server.go | 38 ++++++++++++ 15 files changed, 360 insertions(+), 5 deletions(-) create mode 100644 groupTasks/groups/janex-acryl/index.json create mode 100644 groupTasks/groups/production1/index.json create mode 100644 groupTasks/groups/roese/index.json create mode 100644 groupTasks/groups/umbach/index.json create mode 100644 modules/cache/cache.go create mode 100644 modules/structs/socket.go create mode 100644 socketclients/socketclients.go create mode 100644 socketserver/hub.go create mode 100644 socketserver/server.go diff --git a/go.mod b/go.mod index 2d2576d..85f89ea 100644 --- a/go.mod +++ b/go.mod @@ -4,11 +4,13 @@ go 1.20 require ( github.com/andybalholm/brotli v1.0.5 // indirect + github.com/fasthttp/websocket v1.5.2 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.12.0 // indirect github.com/go-sql-driver/mysql v1.7.0 // indirect github.com/gofiber/fiber/v2 v2.44.0 // indirect + github.com/gofiber/websocket/v2 v2.1.6 // indirect github.com/google/uuid v1.3.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect diff --git a/go.sum b/go.sum index 61e58f4..d652135 100644 --- a/go.sum +++ b/go.sum @@ -3,6 +3,8 @@ github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHG github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fasthttp/websocket v1.5.2 h1:KdCb0EpLpdJpfE3IPA5YLK/aYBO3dhZcvwxz6tXe2LQ= +github.com/fasthttp/websocket v1.5.2/go.mod h1:S0KC1VBlx1SaXGXq7yi1wKz4jMub58qEnHQG9oHuqBw= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= @@ -14,6 +16,8 @@ github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9 github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofiber/fiber/v2 v2.44.0 h1:Z90bEvPcJM5GFJnu1py0E1ojoerkyew3iiNJ78MQCM8= github.com/gofiber/fiber/v2 v2.44.0/go.mod h1:VTMtb/au8g01iqvHyaCzftuM/xmZgKOZCtFzz6CdV9w= +github.com/gofiber/websocket/v2 v2.1.6 h1:k4z+YqzGUwbCQJCIW+mDJF2iCcBfRY7BJGUa2k+VHXo= +github.com/gofiber/websocket/v2 v2.1.6/go.mod h1:o+oXFwHjavIiM2KWo/MNpcIOruS0am16h3efqnjXLis= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= diff --git a/groupTasks/groups/janex-acryl/index.json b/groupTasks/groups/janex-acryl/index.json new file mode 100644 index 0000000..a6b300e --- /dev/null +++ b/groupTasks/groups/janex-acryl/index.json @@ -0,0 +1,3 @@ +{ + "name": "Janex Device Acryl Led Lamp" +} \ No newline at end of file diff --git a/groupTasks/groups/production1/index.json b/groupTasks/groups/production1/index.json new file mode 100644 index 0000000..b8864d0 --- /dev/null +++ b/groupTasks/groups/production1/index.json @@ -0,0 +1,46 @@ +{ + "name": "Produktionstask 1", + "inputs": [ + { + "parameterName": "irgendwas", + "type": "string", + "displayName": "Irgendwas tolles" + }, + { + "parameterName": "kiste", + "type": "number", + "displayName": "Nummer der Kiste" + } + ], + "tasks": [ + { + "name": "Bild zu Label konvertieren", + "onFinish": "pause", + "undoPossible": false, + "scriptPath": "", + "parameter": [ + { + "parameterName": "labelformat", + "type": "string", + "displayName": "Format des Labels", + "global": false + }, + { + "parameterName": "kiste", + "type": "number", + "displayName": "Nummer der Kiste", + "global": true + } + ], + "feedback": "image" + }, + { + "name": "Label drucken", + "onFinish": "nextStep", + "undoPossible": false, + "scriptPath": "", + "parameter": [], + "feedback": "" + } + ] +} diff --git a/groupTasks/groups/roese/index.json b/groupTasks/groups/roese/index.json new file mode 100644 index 0000000..f1a5434 --- /dev/null +++ b/groupTasks/groups/roese/index.json @@ -0,0 +1,3 @@ +{ + "name": "Roese" +} \ No newline at end of file diff --git a/groupTasks/groups/umbach/index.json b/groupTasks/groups/umbach/index.json new file mode 100644 index 0000000..6aa86de --- /dev/null +++ b/groupTasks/groups/umbach/index.json @@ -0,0 +1,3 @@ +{ + "name": "Umbach" +} \ No newline at end of file diff --git a/main.go b/main.go index eca3f91..80b7d81 100644 --- a/main.go +++ b/main.go @@ -4,12 +4,16 @@ import ( "janex/admin-dashboard-backend/modules/config" "janex/admin-dashboard-backend/modules/database" "janex/admin-dashboard-backend/modules/logger" + "janex/admin-dashboard-backend/modules/structs" "janex/admin-dashboard-backend/modules/utils" "janex/admin-dashboard-backend/routers/router" + "janex/admin-dashboard-backend/socketserver" "github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2/middleware/cors" flogger "github.com/gofiber/fiber/v2/middleware/logger" + "github.com/gofiber/websocket/v2" + "github.com/rs/zerolog/log" ) func init() { @@ -32,6 +36,50 @@ func main() { router.SetupRoutes(app) + app.Use("/ws", func(c *fiber.Ctx) error { + // IsWebSocketUpgrade returns true if the client + // requested upgrade to the WebSocket protocol. + if websocket.IsWebSocketUpgrade(c) { + sessionId := c.Query("auth") + + if len(sessionId) != utils.LenHeaderXAuthorization { + return c.SendStatus(fiber.StatusUnauthorized) + } + + // validate ws session + + var userSession structs.UserSession + + database.DB.First(&userSession, "id = ?", sessionId) + + if userSession.Id == "" { + return c.SendStatus(fiber.StatusUnauthorized) + } + + log.Info().Msg("session id: " + userSession.Id + " user: " + userSession.Id) + + var user structs.User + + database.DB.First(&user, "id = ?", userSession.UserId) + + if user.Id == "" { + return c.SendStatus(fiber.StatusInternalServerError) + } + + log.Info().Msg("user " + user.Id + user.Username) + + c.Locals("sessionId", sessionId) + c.Locals("userId", user.Id) + + return c.Next() + } + + return fiber.ErrUpgradeRequired + }) + + go socketserver.RunHub() + go socketserver.WebSocketServer(app) + app.Listen(config.Cfg.Host + ":" + config.Cfg.Port) } diff --git a/modules/cache/cache.go b/modules/cache/cache.go new file mode 100644 index 0000000..4e35859 --- /dev/null +++ b/modules/cache/cache.go @@ -0,0 +1,64 @@ +package cache + +import ( + "janex/admin-dashboard-backend/modules/structs" + "sync" + + "github.com/gofiber/websocket/v2" +) + +var socketClients []*structs.SocketClient +var mu sync.RWMutex + +func AddSocketClient(sessionId string, socketClient *structs.SocketClient) { + mu.Lock() + socketClients = append(socketClients, socketClient) + mu.Unlock() +} + +func DeleteClientByConn(conn *websocket.Conn) { + mu.Lock() + + for i := 0; i < len(socketClients); i++ { + if socketClients[i].Conn == conn { + socketClients = remove(socketClients, i) + break + } + } + + mu.Unlock() +} + +func remove(s []*structs.SocketClient, i int) []*structs.SocketClient { + return append(s[:i], s[i+1:]...) +} + +func GetSocketClients() []*structs.SocketClient { + mu.RLock() + defer mu.RUnlock() + + return socketClients +} + +/* +func GetSocketClient(sessionId string) (socketClient *structs.SocketClient, ok bool) { + mu.RLock() + defer mu.RUnlock() + + client, ok := socketClients[sessionId] + + return client, ok +} + +func GetSocketClientByConn(conn *websocket.Conn) (socketClient *structs.SocketClient, err error) { + mu.RLock() + defer mu.RUnlock() + + for _, client := range socketClients { + if client.Conn == conn { + return client, nil + } + } + + return nil, errors.New("Failed to find socket client by ws conn") +}*/ diff --git a/modules/structs/socket.go b/modules/structs/socket.go new file mode 100644 index 0000000..2fbece1 --- /dev/null +++ b/modules/structs/socket.go @@ -0,0 +1,73 @@ +package structs + +import ( + "encoding/json" + "errors" + "sync" + + "github.com/gofiber/websocket/v2" + "github.com/rs/zerolog/log" +) + +type SocketClient struct { + SessionId string + UserId string + Conn *websocket.Conn + connMu sync.Mutex +} + +type SocketMessage struct { + Conn *websocket.Conn + Msg []byte +} + +type SendSocketMessage struct { + Cmd int + Body any +} + +type ReceivedMessage struct { + Cmd int + Body any +} + +func (socketClient *SocketClient) SendCloseMessage() error { + return socketClient.writeMessage(websocket.CloseMessage, SendSocketMessage{}, true) +} + +func (socketClient *SocketClient) SendMessage(message SendSocketMessage) error { + return socketClient.writeMessage(websocket.TextMessage, message, false) +} + +func (socketClient *SocketClient) writeMessage(messageType int, message SendSocketMessage, closeMessage bool) error { + var marshaledMessage []byte + var err error + + if closeMessage { + //marshaledMessage = websocket.FormatCloseMessage(utils.WsCloseCodeNewConnectionWasMade, "") + } else { + marshaledMessage, err = json.Marshal(message) + + if err != nil { + log.Error().Msgf("Failed to marshal ws message, err: %s", err) + return err + } + } + + socketClient.connMu.Lock() + defer socketClient.connMu.Unlock() + + if socketClient.Conn == nil { + log.Error().Msgf("Failed to ws message because conn is nil") + return errors.New("ws client conn is nil") + } + + err = socketClient.Conn.WriteMessage(messageType, marshaledMessage) + + if err != nil { + log.Error().Msgf("Failed to write ws message, err: %s", err) + return err + } + + return nil +} diff --git a/modules/structs/user.go b/modules/structs/user.go index 661f28b..c30deaa 100644 --- a/modules/structs/user.go +++ b/modules/structs/user.go @@ -2,12 +2,9 @@ package structs import ( "time" - - "gorm.io/gorm" ) type User struct { - gorm.Model Id string Username string Email string @@ -16,7 +13,6 @@ type User struct { } type UserSession struct { - gorm.Model Id string UserId string UserAgent string diff --git a/routers/router/api/v1/user/user.go b/routers/router/api/v1/user/user.go index 3adecc5..fa45779 100644 --- a/routers/router/api/v1/user/user.go +++ b/routers/router/api/v1/user/user.go @@ -8,7 +8,7 @@ import ( "github.com/gofiber/fiber/v2" ) -// Request on web startup +// Requested on web startup func User(c *fiber.Ctx) error { xAuthorization := utils.GetXAuhorizationHeader(c) diff --git a/routers/router/router.go b/routers/router/router.go index 44a90fd..f2af1f2 100644 --- a/routers/router/router.go +++ b/routers/router/router.go @@ -14,6 +14,7 @@ func SetupRoutes(app *fiber.App) { u := v1.Group("/user") u.Post("/auth/login", user.UserLogin) + u.Delete("/auth/logout", user.UserLogout) u.Get("/", userSessionValidation, user.User) } diff --git a/socketclients/socketclients.go b/socketclients/socketclients.go new file mode 100644 index 0000000..aab34b0 --- /dev/null +++ b/socketclients/socketclients.go @@ -0,0 +1,23 @@ +package socketclients + +import ( + "janex/admin-dashboard-backend/modules/cache" + "janex/admin-dashboard-backend/modules/structs" +) + +const ( + SentCmdUpdateConnectedUsers = 1 +) + +func BroadcastMessage(sendSocketMessage structs.SendSocketMessage) { + for _, client := range cache.GetSocketClients() { + client.SendMessage(sendSocketMessage) + } +} + +func UpdateConnectedUsers() { + BroadcastMessage(structs.SendSocketMessage{ + Cmd: SentCmdUpdateConnectedUsers, + Body: len(cache.GetSocketClients()), + }) +} diff --git a/socketserver/hub.go b/socketserver/hub.go new file mode 100644 index 0000000..0d31067 --- /dev/null +++ b/socketserver/hub.go @@ -0,0 +1,51 @@ +package socketserver + +import ( + "encoding/json" + "fmt" + "janex/admin-dashboard-backend/modules/cache" + "janex/admin-dashboard-backend/modules/structs" + "janex/admin-dashboard-backend/socketclients" + + "github.com/gofiber/websocket/v2" + "github.com/rs/zerolog/log" +) + +var register = make(chan *structs.SocketClient) +var broadcast = make(chan structs.SocketMessage) +var unregister = make(chan *websocket.Conn) + +func RunHub() { + for { + select { + case newSocketClient := <-register: + userId := fmt.Sprintf("%v", newSocketClient.Conn.Locals("userId")) + sessionId := fmt.Sprintf("%v", newSocketClient.Conn.Locals("sessionId")) + + newSocketClient.SessionId = sessionId + newSocketClient.UserId = userId + + cache.AddSocketClient(sessionId, newSocketClient) + + log.Debug().Msgf("clients: %d", len(cache.GetSocketClients())) + log.Debug().Msgf("REGISTER CLIENT: %s", sessionId) + + socketclients.UpdateConnectedUsers() + + case data := <-broadcast: + var receivedMessage structs.ReceivedMessage + + if err := json.Unmarshal(data.Msg, &receivedMessage); err != nil { + log.Error().Msgf("Failed to unmarshal received msg, err: %s", err) + continue + } + + log.Info().Msgf("Received message: %s", receivedMessage) + + case connection := <-unregister: + cache.DeleteClientByConn(connection) + + socketclients.UpdateConnectedUsers() + } + } +} diff --git a/socketserver/server.go b/socketserver/server.go new file mode 100644 index 0000000..8c1bfa1 --- /dev/null +++ b/socketserver/server.go @@ -0,0 +1,38 @@ +package socketserver + +import ( + "janex/admin-dashboard-backend/modules/structs" + + "github.com/gofiber/fiber/v2" + "github.com/gofiber/websocket/v2" + "github.com/rs/zerolog/log" +) + +func WebSocketServer(app *fiber.App) { + app.Get("/ws", websocket.New(func(c *websocket.Conn) { + defer func() { + unregister <- c + c.Close() + }() + + register <- &structs.SocketClient{Conn: c} + + for { + messageType, msg, err := c.ReadMessage() + + if err != nil { + if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) { + log.Error().Msgf("Read err: %s", err) + } + + return + } + + if messageType == websocket.TextMessage { + broadcast <- structs.SocketMessage{Conn: c, Msg: msg} + } else { + log.Error().Msgf("websocket message received of type %s", messageType) + } + } + })) +}