websocket
websocket copied to clipboard
[question] How to create an onMessage handler on server side?
Describe the problem you're having
A clear and concise description of what the bug is.
I'm writing a server for an app that opens a websocket connection between itself and a given client. It's trivially easy to send messages to the client with this library, but I'm having trouble figuring out how to implement an onMessage()
function or something like that on the server. The client needs to be able to send a sort of "cancel" or "nevermind" message to the server. I suppose it would also work if I could just close the socket on the client side and that would tell the server to do something.
Versions
Go version:
go version
1.18
package version: run
git rev-parse HEAD
inside the repo
af47554f343b4675b30172ac301638d350db34a5
"Show me the code!"
A minimal code snippet can be useful, otherwise we're left guessing! Hint: wrap it with backticks to format it
Simple JavaScript function as the client
function knockSocket() {
url = 'ws://localhost:8080/knock/socket';
ws = new WebSocket(url);
ws.onopen = function(){
console.log("Connected to websocket :)")
resetRequestModal();
openRequestModal();
cancelLink.addEventListener("click", () => {
socketNevermind(ws);
});
}
ws.onmessage = function(msg) {
console.log("Got message!" + msg.data);
data = JSON.parse(msg.data);
if (data.Event === "COUNTDOWN") {
// Apply a 1 second offset to make animation look good.
updateTimeoutBar(data.CurrentTime - 1, data.MaxTime - 1);
} else if (data.Event === "TIMEOUT") {
timeoutCounter.hidden = true;
timeoutBar.hidden = true;
}
}
}
Relevant Go code
func wsTimeoutHandler(w http.ResponseWriter, r *http.Request) {
timeoutEnv, _ := os.LookupEnv("LMI_TIMEOUT")
timeout, _ := strconv.Atoi(timeoutEnv)
fmt.Println("Somebody has knocked. Timeout is ", timeout, "s")
conn, err := wsUpgrader.Upgrade(w, r, nil)
if err != nil {
fmt.Println("Failed to set websocket upgrade: %+v", err)
return
}
// Defer the client to a goroutine.
// 1 second offset to make the timeout modal look better.
go doCountdown(conn, timeout+1)
}
func doCountdown(conn *websocket.Conn, timeout int) {
defer conn.Close()
for i := timeout; i > 0; i-- {
// _, clientMessageBytes, _ := conn.ReadMessage()
// clientMessage := fmt.Sprintf("%s", clientMessageBytes)
// if clientMessage == "NEVERMIND" {
// fmt.Println("Got NEVERMIND from client")
// return
// }
message, _ := json.Marshal(KnockObject{"COUNTDOWN", i, timeout})
conn.WriteMessage(websocket.TextMessage, message) // json go brrr
time.Sleep(1 * time.Second)
}
timeoutMessage, _ := json.Marshal(KnockObject{"TIMEOUT", 0, timeout})
conn.WriteMessage(websocket.TextMessage, timeoutMessage)
}
func knockHandler(c *gin.Context) {
wsTimeoutHandler(c.Writer, c.Request)
}
Repo can be found here: https://github.com/WillNilges/letmein2/tree/willnilges/websocket_timeout
Right now what I'm doing is this:
func watchForNvm(conn *websocket.Conn) {
_, message, err := conn.ReadMessage()
if err != nil {
log.Println("read:", err)
return
}
if string(message) == "NEVERMIND" {
fmt.Println("Got NEVERMIND!")
}
}
Not sure if this is "clean," or "good?" It seems to me like the goroutines I used might not quit if something happens to the websocket externally. Will this gracefully quit if the websocket closes? Will this break doCountdown()
or vice versa?
I think this'll work:
func knockHandler(c *gin.Context) {
wsTimeoutHandler(c.Writer, c.Request)
}
func wsTimeoutHandler(w http.ResponseWriter, r *http.Request) {
timeoutEnv, _ := os.LookupEnv("LMI_TIMEOUT")
timeout, _ := strconv.Atoi(timeoutEnv)
fmt.Println("Somebody has knocked. Timeout is ", timeout, "s")
conn, err := wsUpgrader.Upgrade(w, r, nil)
if err != nil {
fmt.Println("Failed to set websocket upgrade: %+v", err)
return
}
conn.SetReadDeadline(time.Now().Add(time.Duration(timeout+2) * time.Second))
// 1 second offset to make the timeout modal look better.
go knockDoCountdown(conn, timeout+1)
go knockWatchForNvm(conn)
}
func knockDoCountdown(conn *websocket.Conn, timeout int) {
defer conn.Close()
for i := timeout; i > 0; i-- {
message, _ := json.Marshal(KnockObject{"COUNTDOWN", i, timeout})
conn.WriteMessage(websocket.TextMessage, message) // json go brrr
time.Sleep(1 * time.Second)
}
timeoutMessage, _ := json.Marshal(KnockObject{"TIMEOUT", 0, timeout})
conn.WriteMessage(websocket.TextMessage, timeoutMessage)
}
// TODO: Is this returning properly?
func knockWatchForNvm(conn *websocket.Conn) {
_, message, err := conn.ReadMessage()
if err != nil {
log.Println("knockWatchForNvm:", err)
log.Println("Exiting knockWatchForNvm")
return
}
if string(message) == "NEVERMIND" {
fmt.Println("Got NEVERMIND!")
}
}
The knockWatchForNvm
will kill itself when the socket closes. ✅