believesInSanta believesInSanta - 8 days ago 7
Javascript Question

websocket client onmessage broken after refreshing browser

I have a

Websocket
server written in
go
with package
github.com/gorilla/websocket
.

The server has 2 loops for receiving and sending messages. The implementation of the http handler looks like the following:

upgrader, errChan := websocket.Upgrader{}, make(chan error)

ws, err := upgrader.Upgrade(w, r, nil)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}

// incoming messages

go func() {
for {
message := Message{}
if err := ws.ReadJSON(&message); err != nil {
errChan <- err
}
handleMessage(message)
}
}()

// outgoing messages

go func() {
for {
message := <-global.outgoingMessage
if err = ws.WriteMessage(websocket.TextMessage, message); err != nil {
errChan <- err
}
}
}()

err = <- errChan
ws.Close()


The client application uses React and the app uses the message to as a state. When the app is launched, the websocket connection is established and the onopen callback fires a request for the initial data. The response is caught by onmessage which is used for the app state. The implementation looks like the following:

var ws = new WebSocket("ws://localhost:8080/app")

ws.onerror = function(err) {
alert(err)
}
ws.onmessage = function(m) {
var state = JSON.parse(m.data)
global.app.setState(state)
}
ws.onopen = function() {
ws.send('{"type":"init"}')
}


With this setup, what I've been noticing is the first time I fire up the app and browse to the page everything works fine. Once I refresh the browser, it no longer works. I can call
ws.send('{"type":"init"}')
and see the response sent by the server but the
onmessage
callback doesn't get fired. After several more tries of calling
ws.send('{"type":"init"}')
the
onmessage
eventually gets called once and the app state gets loaded. If I kill and restart the app, the same behaviour occurs.

Thoughts?

Answer

It looks like all output goroutines receive from the single channel global.outgoingMessage and that insufficient messages are sent to this channel to satisfy all running output goroutines.

If the intent of the application is to broadcast to all connected clients, then I suggest following the hub pattern in the Gorilla chat example.

Aside: The application leaks goroutines. See this question for an explanation.