From aee617cc886d90df6501a5b16185be93411f4563 Mon Sep 17 00:00:00 2001 From: minie4 Date: Wed, 28 Feb 2024 11:13:02 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Implement=20WebSocket=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api.go | 130 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ go.mod | 9 ++-- go.sum | 18 ++++---- 3 files changed, 145 insertions(+), 12 deletions(-) diff --git a/api.go b/api.go index f2403d9..3f78737 100644 --- a/api.go +++ b/api.go @@ -1,12 +1,22 @@ package main import ( + "encoding/json" "fmt" "github.com/gin-gonic/gin" "github.com/google/uuid" + "github.com/gorilla/websocket" ) +var upgrader = websocket.Upgrader{} + +type WsClient struct { + SendUpdates bool +} + +var wsClients = make(map[*websocket.Conn]WsClient) + func initApi() { gin.SetMode(gin.ReleaseMode) router := gin.Default() @@ -26,6 +36,8 @@ func initApi() { router.PUT("/api/routes", handleSetRoute) router.DELETE("/api/routes", handleDeleteRoute) + router.GET("/ws", handleWs) + router.Run(fmt.Sprintf("%s:%d", config.Web.Bind, config.Web.Port)) } @@ -108,6 +120,7 @@ func createPort(body CreatePortRequest) (gin.H, gin.H) { portConfig.Output = append(portConfig.Output, newPort) } saveConfig("ports", &portConfig) + onPortListChange() return gin.H{"success": true, "UUID": id}, nil } @@ -139,6 +152,7 @@ func deletePort(id string) gin.H { unregisterPort(port) saveConfig("ports", &portConfig) + onPortListChange() return nil } @@ -199,6 +213,7 @@ func setState(id string, body SetPortRequest) (gin.H, gin.H) { } saveConfig("ports", &portConfig) + onPortChange(port) return gin.H{"success": true, "state": port.State}, nil } @@ -236,6 +251,7 @@ func createRoute(id string, body CreateRouteRequest) gin.H { from.Route = append(from.Route, PortRoute{ToUUID: body.To, Mute: true, Volume: 0, Balance: 0}) saveConfig("ports", &portConfig) + onPortChange(from) return nil } @@ -285,6 +301,7 @@ func setRoute(fromId string, toId string, body SetRouteRequest) (gin.H, gin.H) { } saveConfig("ports", &portConfig) + onPortChange(from) return gin.H{"success": true, "state": from.Route[i]}, nil } @@ -315,8 +332,121 @@ func deleteRoute(fromId string, toId string) gin.H { from.Route = append(from.Route[:i], from.Route[i+1:]...) saveConfig("ports", &portConfig) + onPortListChange() return nil } return gin.H{"success": false, "error": "No such route exists!"} } + +type WebsocketMessage struct { + RequestId string `json:"requestId"` + Method string `json:"method"` + UUID string `json:"UUID"` + ToUUID string `json:"toUUID"` + PortData CreatePortRequest `json:"portData"` + StateData SetPortRequest `json:"stateData"` + RouteData CreateRouteRequest `json:"routeData"` + RouteStateData SetRouteRequest `json:"routeStateData"` +} + +type WebsocketResponse struct { + Type string `json:"type"` + RequestId string `json:"requestId,omitempty"` + RequestMethod string `json:"requestMethod,omitempty"` + ResponseData interface{} `json:"responseData"` +} + +func wsSendRes(conn *websocket.Conn, res gin.H, err gin.H, request *WebsocketMessage) { + if err != nil { + data, _ := json.Marshal(WebsocketResponse{ + Type: "error", + RequestId: request.RequestId, + RequestMethod: request.Method, + ResponseData: err, + }) + conn.WriteMessage(1, data) + return + } + + response := WebsocketResponse{ + Type: "response", + RequestId: request.RequestId, + RequestMethod: request.Method, + ResponseData: gin.H{"success": true}, + } + if res != nil { + response.ResponseData = res + } + + data, _ := json.Marshal(response) + conn.WriteMessage(1, data) +} + +func handleWs(c *gin.Context) { + conn, err := upgrader.Upgrade(c.Writer, c.Request, nil) + if err != nil { + fmt.Println("WARN! Handling websocket connection failed!") + c.JSON(500, gin.H{"success": false, "error": "Handling websocket connection failed!"}) + } + + wsClients[conn] = WsClient{SendUpdates: false} + + for { + var content WebsocketMessage + jsonErr := conn.ReadJSON(&content) + if jsonErr != nil { + delete(wsClients, conn) + conn.Close() + break + } + var res, err gin.H = nil, nil + switch content.Method { + case "listPorts": + res = gin.H{"inputs": portConfig.Input, "outputs": portConfig.Output} + case "createPort": + res, err = createPort(content.PortData) + case "setPortState": + res, err = setState(content.UUID, content.StateData) + case "deletePort": + err = deletePort(content.UUID) + case "createRoute": + err = createRoute(content.UUID, content.RouteData) + case "setRouteState": + res, err = setRoute(content.UUID, content.ToUUID, content.RouteStateData) + case "deleteRoute": + err = deleteRoute(content.UUID, content.ToUUID) + case "enableUpdates": + if clientOpts, ok := wsClients[conn]; ok { + clientOpts.SendUpdates = true + wsClients[conn] = clientOpts + } + default: + err = gin.H{"success": false, "error": "Method is not implemented"} + } + wsSendRes(conn, res, err, &content) + } +} + +func onPortChange(port *Port) { + for conn := range wsClients { + if !wsClients[conn].SendUpdates { + continue + } + jsonData, _ := json.Marshal(WebsocketResponse{Type: "portChange", ResponseData: port}) + conn.WriteMessage(1, jsonData) + } +} + +func onPortListChange() { + for conn := range wsClients { + if !wsClients[conn].SendUpdates { + continue + } + jsonData, _ := json.Marshal(WebsocketResponse{ + Type: "postListChange", + ResponseData: gin.H{"inputs": portConfig.Input, "outputs": portConfig.Output}, + }) + conn.WriteMessage(1, jsonData) + } +} diff --git a/go.mod b/go.mod index 6692187..554fa2d 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.21 require ( github.com/gin-gonic/gin v1.9.1 github.com/google/uuid v1.6.0 + github.com/gorilla/websocket v1.5.1 github.com/pelletier/go-toml/v2 v2.1.1 github.com/xthexder/go-jack v0.0.0-20220805234212-bc8604043aba ) @@ -27,10 +28,10 @@ require ( github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.11 // indirect golang.org/x/arch v0.3.0 // indirect - golang.org/x/crypto v0.9.0 // indirect - golang.org/x/net v0.10.0 // indirect - golang.org/x/sys v0.8.0 // indirect - golang.org/x/text v0.9.0 // indirect + golang.org/x/crypto v0.14.0 // indirect + golang.org/x/net v0.17.0 // indirect + golang.org/x/sys v0.13.0 // indirect + golang.org/x/text v0.13.0 // indirect google.golang.org/protobuf v1.30.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 7e653b1..0721fbf 100644 --- a/go.sum +++ b/go.sum @@ -29,6 +29,8 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= +github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= @@ -67,16 +69,16 @@ github.com/xthexder/go-jack v0.0.0-20220805234212-bc8604043aba/go.mod h1:T6DswVP golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= -golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= -golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=