Get WebSocket error close sent when I navigate to other page - go

I am getting back error websocket: close sent when I try to send data from my server side to client side(dashboard page). The error happen when I navigate to home page and back to dashboard page. Everything works fine initially with the Dashboard Page
My dashboard page code
let socket = new ReconnectingWebSocket("ws://127.0.0.1:8004/wsendpoint");
console.log("Attempting Connection...");
socket.onopen = () => {
console.log("Successfully Connected");
};
socket.onclose = event => {
console.log("Socket Closed Connection: ", event);
};
socket.onerror = error => {
console.log("Socket Error: ", error);
};
socket.onmessage = function (event) {
console.log("message received: " + event.data);
}
My Server side code (writer and reader
func wsEndpoint(w http.ResponseWriter, r *http.Request) {
upgrader.CheckOrigin = func(r *http.Request) bool { return true }
// upgrade this connection to a WebSocket connection
ws, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Println("upgrade error %s", err)
}
defer ws.Close()
go writer(ws)
go reader(ws)
}
Writer, get data from a channel when new data come in from database
func writer(conn *websocket.Conn) {
for {
singleset := <-singleorder // get data from channel
jsonString, err := json.Marshal(singleset )
err = conn.WriteMessage(1, []byte(jsonString))
if err != nil {
log.Println(err)
}
}
Reader, read data from client side
func reader(conn *websocket.Conn) {
for {
_, p, err := conn.ReadMessage() //what is message type?
if err != nil {
log.Println("there is errors%s", err)
return
}
}
}
I also got error WebSocket is closed before the connection is established. and reconnecting-websocket.min.js:1 WebSocket connection to 'ws://127.0.0.1:8004/wsendpoint' failed: WebSocket is closed before the connection is established.
As you can see all the code are very simple because I just start to learn websocket and follow simple tutorial. I tried to search on web but webworker seems little bit complex for me and saw about the ping pong method but I am not sure if its still valid if I navigate. Can I establish websocket on home page so the connection is not closed ? Since I only have two pages on client side.
Thanks in advance for any guidance on how to deal with these situation!

The basic strategy for dealing with navigation away from the page, page close or errors is this: The client creates the websocket connection on page load. The server expects client connections to come and go and cleans up resources when the client connection errors.
Here's why you get the error 'websocket: close sent': When the user navigates away from the page, the browser sends a close message to the server as part of the websocket closing handshake. The Gorilla package responds to the close message by sending another close message back to the client. From that point on, the server cannot send messages on the connection. The connection returns an error from write methods after the close message is sent.
Close messages are returned as errors from the websocket read methods. To fix the problem, modify the code to handle errors in general. There's no need to handle the closing handshake specifically.
Here's the updated code. The wsEndpoint function creates a channel for the reader to signal the writer that the reader is done. The defer ws.Close() statement is removed because the reader and writer goroutines will take responsibility for closing the connection.
func wsEndpoint(w http.ResponseWriter, r *http.Request) {
upgrader.CheckOrigin = func(r *http.Request) bool { return true }
// upgrade this connection to a WebSocket connection
ws, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Println("upgrade error %s", err)
}
done := make(chan struct{})
go writer(ws, done)
go reader(ws, done)
}
The reader closes the connection on return. It also closes the done channel to signal the writer that the reader is done.
func reader(conn *websocket.Conn, done chan struct{}) {
defer conn.Close()
defer close(done)
for {
_, p, err := conn.ReadMessage() //what is message type?
if err != nil {
log.Println("there is errors%s", err)
return
}
}
}
The writer also closes the connection on return. When the connection is closed, read on the connection immediately returns an error causing the read goroutine to complete. The writer waits on the done channel. Receive on the channel yields the zero value when the channel is closed by the reader. The writer returns on write errors instead of looping forever as in the question.
func writer(conn *websocket.Conn, done chan struct{}) {
defer conn.Close()
for {
select {
case <-done:
// the reader is done, so return
return
case singleset := <-singleorder: // get data from channel
jsonString, err := json.Marshal(singleset )
err = conn.WriteMessage(1, []byte(jsonString))
if err != nil {
log.Println(err)
return
}
}
}
}
An application should expect the connection to be closed for any number of reasons including the user navigating away from the page.
It's likely that the code that sends to channel singleorder needs to know that the connection was closed, but we cannot see that code here. I'll leave it to you to figure out how to handle that.

Related

How to communicate between multiple goroutines with for loops with blocking function calls inside one of them

I'm writing a Go app which accepts a websocket connection, then starts:
listen goroutine which listens on the connection for client messages and sends response for the client based on the received message via channel to updateClient.
updateClient goroutine which writes to the connection.
processExternalData goroutine which receives data from message queue, sends the data to updateClient via a channel so that updateClient can update the client with the data.
I'm using gorilla library for websocket connections, and its read call is blocking. In addition, both its write and read methods don't support concurrent calls, which is the main reason I have the updateClient goroutine which is the single routine which calls write method.
The problem arises when I need to close the connection which can happen at least in 2 cases:
The client closed the connection or error occurred during read.
processExternalData finished, there's no more data to update the client and the connection should be closed.
So updateClient needs to somehow notify listen to quit and vice versa listen needs to somehow notify updateClient to quit. updateClient has a quit channel inside select but listen can't have select because it already has a for loop with blocking read call inside.
So what I did is I added isJobFinished field on the connection type which is a condition for for loop to work:
type WsConnection struct {
connection *websocket.Conn
writeChan chan messageWithCb
quitChan chan bool
isJobFinished bool
userID string
}
func processExternalData() {
// receive data from message queue
// send it to client via writeChan
}
func (conn *WsConnection) listen() {
defer func() {
conn.connection.Close()
conn.quitChan <- true
}()
// keep the loop for communication with client
for !conn.isJobFinished {
_, message, err := conn.connection.ReadMessage()
if err != nil {
log.Println("read:", err)
break
}
// convert message to type messageWithCb
switch clientMessage.MessageType {
case userNotFound:
conn.writeChan <- messageWithCb{
message: map[string]interface{}{
"type": user,
"payload": false,
},
}
default:
log.Printf("Unknown message type received: %v", clientMessage)
}
}
log.Println("end of listen")
}
func updateClient(w http.ResponseWriter, req *http.Request) {
upgrader.CheckOrigin = func(req *http.Request) bool {
return true
}
connection, err := upgrader.Upgrade(w, req, nil)
if err != nil {
log.Print("upgrade:", err)
return
}
wsConn := &WsConnection{
connection: connection,
writeChan: make(chan messageWithCb),
quitChan: make(chan bool),
}
go wsConn.listen()
for {
select {
case msg := <-wsConn.writeChan:
err := connection.WriteJSON(msg.message)
if err != nil {
log.Println("connection.WriteJSON error: ", err)
}
if wsConn.isJobFinished {
if msg.callback != nil {
msg.callback() // sends on `wsConn.quitChan` from a goroutine
}
}
case <-wsConn.quitChan:
// clean up
wsConn.connection.Close()
close(wsConn.writeChan)
return
}
}
}
I'm wondering if a better pattern exists in Go for such cases. Specifically, I'd like to be able to have a quit channel inside listen as well so updateClient can notify it to quit instead of maintaining isJobFinished field. Also in this case there's no danger of not protecting isJobFinished field because only one method writes to it but if the logic gets more complicated then having to protect the field inside the for loop in listen will probably negatively impact the performance.
Also I can't close the quiteChan because both listen and updateClient use it and there's no way to know for them when it's closed by another one.
Close the connection to break the listen goroutine out of the blocking read call.
In updateClient, add a defer statement to close the connection and clean up other resources. Return from the function on any error or a notification from the quit channel:
updateClient(w http.ResponseWriter, req *http.Request) {
upgrader.CheckOrigin = func(req *http.Request) bool {
return true
}
connection, err := upgrader.Upgrade(w, req, nil)
if err != nil {
log.Print("upgrade:", err)
return
}
defer connection.Close() // <--- Add this line
wsConn := &WsConnection{
connection: connection,
writeChan: make(chan messageWithCb),
quitChan: make(chan bool),
}
defer close(writeChan) // <-- cleanup moved out of loop below.
go wsConn.listen()
for {
select {
case msg := <-wsConn.writeChan:
err := connection.WriteJSON(msg.message)
if err != nil {
log.Println("connection.WriteJSON error: ", err)
return
}
case <-wsConn.quitChan:
return
}
}
}
In the listen function, loop until error reading the connection. Read on the connection returns immediately with an error when updateClient closes the connection.
To prevent listen from blocking forever in the case where updateClient returns first, close the quit channel instead of sending a value.
func (conn *WsConnection) listen() {
defer func() {
conn.connection.Close()
close(conn.quitChan) // <-- close instead of sending value
}()
// keep the loop for communication with client
for {
_, message, err := conn.connection.ReadMessage()
if err != nil {
log.Println("read:", err)
break
}
// convert message to type messageWithCb
switch clientMessage.MessageType {
case userNotFound:
conn.writeChan <- messageWithCb{
message: map[string]interface{}{
"type": user,
"payload": false,
},
}
default:
log.Printf("Unknown message type received: %v", clientMessage)
}
}
log.Println("end of listen")
}
The field isJobFinished is not needed.
One problem with the code in the question and in this answer is that close of writeChan is not coordinated with sends to the channel. I cannot comment on a solution to this problem without seeing the processExternalData function.
It may make sense to use a mutex instead of a goroutine to limit write concurrency. Again, the code in the processExternalData function is required to comment further on this topic.

Connection getting closed inside goroutine

I am writing a simple go program that connects with a socket server.
I am using a goroutine to continuously look for the incoming messages.
But inside the goroutine connection gets closed. Can someone suggest me what is happening?
Below is my test client
package main
import (
"fmt"
"net"
"os"
"encoding/json"
"bufio"
)
type test_user struct {
ID string
Conn net.Conn
Messages chan interface{}
}
func CreateTestUser(id string) test_user {
return test_user{id, nil, nil}
}
/*
1. Try connecting
2. Wait for connected packet
3. Send first packet (Init packet)
*/
func (user *test_user) Connect() {
conn, err := net.Dial(CONN_TYPE, CONN_HOST+":"+CONN_PORT)
if err != nil {
fmt.Println("Could not connect", err.Error())
os.Exit(1)
}
user.Conn = conn
user.Messages = make(chan interface{})
reader := bufio.NewReader(conn)
message, _ := reader.ReadBytes(DELIMITER)
user.SendMessage(InitPacket{TYPE_USER,user.ID})
//message, _ = reader.ReadBytes(DELIMITER)
//message, _ = reader.ReadBytes(DELIMITER) // If I uncomment these lines it will wait for new messages
//message, _ = reader.ReadBytes(DELIMITER)
go func() {
for {
var readErr error
message, readErr = reader.ReadBytes(DELIMITER) // this always returns error "Error on read read tcp 127.0.0.1:54694->127.0.0.1:3333: use of closed network connection"/
if readErr == nil {
var packet interface{}
error := json.Unmarshal(message, &packet)
if error != nil {
fmt.Println("Unable to parse message", string(message), error.Error())
}
user.Messages <- packet
} else {
fmt.Println("Error on read", readErr.Error())
}
}
}()
}
Can someone please help me with this. Also I am new to golang to please suggests improvements as well.
It'd be helpful to see a bit more of your code outside Connect.
Connect launches a new goroutine, but it doesn't wait for it - it returns immediately. Could the connection be closed after Connect returns?
Generally when you launch a goroutine to handle a connection, your "main" goroutine would keep waiting for other things - new connections and/or some handshake with the per-connection goroutines. I suspect this is not what's happening here.

Shutting down a Go server on browser close

I wrote a small checkbook ledger in Go as a server that runs in localhost and opens the default browser to a small web app front-end (https://bitbucket.org/grkuntzmd/checks-and-balances).
To automatically shut down the server when the browser tab is closed, I have the browser call a "heartbeat" URL every few seconds. If that heartbeat does not arrive, the server uses (*Server) Shutdown to stop running.
Is there any way to do the same thing using contexts (https://golang.org/pkg/context/)? It is my understanding from watching this episode of JustForFunc that a context passed down to a handler will be notified if the client cancels a request.
Instead of sending a "heartbeat" request every so often, you could take advantage of server-sent events.
Server-sent events is a web technology where the browser makes a HTTP request and keeps the connection open to receive events from the server. This could replace your need for repeated heartbeat requests by having the server shutdown when the connection to the event source is closed.
Here's a basic server implementation in Go:
package main
import (
"fmt"
"log"
"net/http"
"time"
)
func main() {
http.HandleFunc("/heartbeat", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/event-stream")
w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Connection", "keep-alive")
flusher, ok := w.(http.Flusher)
if !ok {
http.Error(w, "your browser doesn't support server-sent events")
return
}
// Send a comment every second to prevent connection timeout.
for {
_, err := fmt.Fprint(w, ": ping")
if err != nil {
log.Fatal("client is gone, shutting down")
return
}
flusher.Flush()
time.Sleep(time.Second)
}
})
fmt.Println(http.ListenAndServe(":1323", nil))
}
See using server-sent events for a guide on the client side.
Open a websocket connection from the page. On the server, exit when the websocket connection is closed:
func wsHandler(w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Println(err)
return
}
defer c.Close()
// Shutdown on exit from handler
defer server.Shutdown(context.Background())
// Read messages from client. NextReader returns an error
// when the client shuts down.
for {
if _, _, err := c.NextReader(); err != nil {
break
}
}
}
Add this line of code to the client application:
var conn = new WebSocket("ws://" + document.location.host + "/ws");
Ensure that conn has lifetime of the page.
This is somewhat similar to the SSE solution suggested in another answer, but has the advantage of working in more browsers.
There's no need to send a heartbeat because this is on local host.

one to one chat in golang

I want to create one to one chat in revel framework but it gives error. Firstly work in revel chat according to demo but refreshing page did not work so I tried this method and dont know how to handle single chat.
Here is an error:
app server.go:2848: http: panic serving 127.0.0.1:50420: interface conversion: interface is nil, not io.Writer goroutine 166 [running]: net/http.(*conn).serve.func1(0xc4201d03c0)
my go code is where I handle ws root,single user chat need to db connection to. I'm using posgres for it
package main
import (
"log"
"net/http"
"github.com/gorilla/websocket"
)
var clients = make(map[*websocket.Conn]bool) // connected clients
var broadcast = make(chan Message) // broadcast channel
// Configure the upgrader
var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return true
},
}
// Define our message object
type Message struct {
Email string `json:"email"`
Username string `json:"username"`
Message string `json:"message"`
Created string `json:"created"`
}
func main() {
// Create a simple file server
fs := http.FileServer(http.Dir("public"))
http.Handle("/", fs)
// Configure websocket route
http.HandleFunc("/ws", handleConnections)
// Start listening for incoming chat messages
go handleMessages()
// Start the server on localhost port 8000 and log any errors
log.Println("http server started on :8090")
err := http.ListenAndServe(":8090", nil)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
func handleConnections(w http.ResponseWriter, r *http.Request) {
// Upgrade initial GET request to a websocket
ws, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Fatal(err)
}
// Make sure we close the connection when the function returns
defer ws.Close()
// Register our new client
clients[ws] = true
for {
var msg Message
// Read in a new message as JSON and map it to a Message object
err := ws.ReadJSON(&msg)
if err != nil {
log.Printf("error: %v", err)
delete(clients, ws)
break
}
// Send the newly received message to the broadcast channel
broadcast <- msg
}
}
func handleMessages() {
for {
// Grab the next message from the broadcast channel
msg := <-broadcast
// Send it out to every client that is currently connected
for client := range clients {
err := client.WriteJSON(msg)
if err != nil {
log.Printf("error: %v", err)
client.Close()
delete(clients, client)
}
}
}
}
I think you use gorilla/websocket API incorrectly. You copied the echo example which, being a basic demo, is expected to handle a single ws connection only. Start with the chat example. Particularly pay attention to the fact that serveWs is a non-blocking call while your handleConnections is blocking, i.e. it never returns. Take a look here for a full-featured example of gorilla/websocket API use:
https://github.com/tinode/chat/blob/master/server/wshandler.go
As correctly pointed out by Cerise L, you most certainly have a race on your clients although I think it's unlikely to produce a panic. I think the most likely source of panic is a call to Upgrade on a closed http connection. It's impossible to say exactly because you did not post the full output of the panic.

GO Websocket send all clients a message

Everything works fine with this code (shortened it for better reading).
When Client1 sends a request to the Server, the Server responses to him instantly. But, the other clients can not see the response message.
So I want to make it go further: When a client sends a request to the server, the server will response to all clients so that all clients can see the message.
How can I do that? Any examples or nice tutorials for beginners?
Thanks in advance!
Server:
import (
"github.com/gorilla/websocket"
)
func main() {
http.Handle("/server", websocket.Handler(echoHandler))
}
func echoHandler(ws *websocket.Conn) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
return
}
for {
messageType, p, err := conn.ReadMessage()
if err != nil {
return
}
print_binary(p) // simple print of the message
err = conn.WriteMessage(messageType, p);
if err != nil {
return
}
}
}
You have to use connection pool to broadcast messages to all connections.
You can use that as tutorial/sample http://gary.burd.info/go-websocket-chat
Simplifying:
Connection pool is a collection of registered connections. See hub.connections:
type connection struct {
// The websocket connection.
ws *websocket.Conn
// Buffered channel of outbound messages.
send chan []byte
// The hub.
h *hub
}
type hub struct {
// Registered connections. That's a connection pool
connections map[*connection]bool
...
}
To broadcast message for all clients, we iterate over connection pool like this:
case m := <-h.broadcast:
for c := range h.connections {
select {
case c.send <- m:
default:
delete(h.connections, c)
close(c.send)
}
}
}
h.broadcast in that example is a channel with messages we need to broadcast.
We use default section of the select statement to delete connections with full or blocked send channels. See What is the benefit of sending to a channel by using select in Go?

Resources