Implement WebSocket API

This commit is contained in:
2024-02-28 11:13:02 +01:00
parent dd665d9443
commit aee617cc88
3 changed files with 145 additions and 12 deletions

130
api.go
View File

@ -1,12 +1,22 @@
package main package main
import ( import (
"encoding/json"
"fmt" "fmt"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/google/uuid" "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() { func initApi() {
gin.SetMode(gin.ReleaseMode) gin.SetMode(gin.ReleaseMode)
router := gin.Default() router := gin.Default()
@ -26,6 +36,8 @@ func initApi() {
router.PUT("/api/routes", handleSetRoute) router.PUT("/api/routes", handleSetRoute)
router.DELETE("/api/routes", handleDeleteRoute) router.DELETE("/api/routes", handleDeleteRoute)
router.GET("/ws", handleWs)
router.Run(fmt.Sprintf("%s:%d", config.Web.Bind, config.Web.Port)) 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) portConfig.Output = append(portConfig.Output, newPort)
} }
saveConfig("ports", &portConfig) saveConfig("ports", &portConfig)
onPortListChange()
return gin.H{"success": true, "UUID": id}, nil return gin.H{"success": true, "UUID": id}, nil
} }
@ -139,6 +152,7 @@ func deletePort(id string) gin.H {
unregisterPort(port) unregisterPort(port)
saveConfig("ports", &portConfig) saveConfig("ports", &portConfig)
onPortListChange()
return nil return nil
} }
@ -199,6 +213,7 @@ func setState(id string, body SetPortRequest) (gin.H, gin.H) {
} }
saveConfig("ports", &portConfig) saveConfig("ports", &portConfig)
onPortChange(port)
return gin.H{"success": true, "state": port.State}, nil 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}) from.Route = append(from.Route, PortRoute{ToUUID: body.To, Mute: true, Volume: 0, Balance: 0})
saveConfig("ports", &portConfig) saveConfig("ports", &portConfig)
onPortChange(from)
return nil return nil
} }
@ -285,6 +301,7 @@ func setRoute(fromId string, toId string, body SetRouteRequest) (gin.H, gin.H) {
} }
saveConfig("ports", &portConfig) saveConfig("ports", &portConfig)
onPortChange(from)
return gin.H{"success": true, "state": from.Route[i]}, nil 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:]...) from.Route = append(from.Route[:i], from.Route[i+1:]...)
saveConfig("ports", &portConfig) saveConfig("ports", &portConfig)
onPortListChange()
return nil return nil
} }
return gin.H{"success": false, "error": "No such route exists!"} 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)
}
}

9
go.mod
View File

@ -5,6 +5,7 @@ go 1.21
require ( require (
github.com/gin-gonic/gin v1.9.1 github.com/gin-gonic/gin v1.9.1
github.com/google/uuid v1.6.0 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/pelletier/go-toml/v2 v2.1.1
github.com/xthexder/go-jack v0.0.0-20220805234212-bc8604043aba 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/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.11 // indirect github.com/ugorji/go/codec v1.2.11 // indirect
golang.org/x/arch v0.3.0 // indirect golang.org/x/arch v0.3.0 // indirect
golang.org/x/crypto v0.9.0 // indirect golang.org/x/crypto v0.14.0 // indirect
golang.org/x/net v0.10.0 // indirect golang.org/x/net v0.17.0 // indirect
golang.org/x/sys v0.8.0 // indirect golang.org/x/sys v0.13.0 // indirect
golang.org/x/text v0.9.0 // indirect golang.org/x/text v0.13.0 // indirect
google.golang.org/protobuf v1.30.0 // indirect google.golang.org/protobuf v1.30.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )

18
go.sum
View File

@ -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/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 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 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= 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.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 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 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.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= 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.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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 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 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 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= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=