I am facing an issue with Go while using the WebSockets protocol. If I connect to my API, everything works fine. If I add "protocol" such as "Hey", it begins to loop multiple times and finishes by getting an error *github.com/gorilla/websocket.CloseError
: "Code 1006, Text Unexpected EOF".
I definitely don't get why it acts like this when I send Sec-Websocket-Protocol
among the connection.
There is my code:
main.go
package main
import (
"fmt"
"github.com/golang/glog"
"github.com/grpc-ecosystem/grpc-gateway/runtime"
stacktracer "gitlab.com/eyes-eyes/internals-stacktracer"
"gitlab.com/eyesbank/go-web-sockets-server/handlers"
"net/http"
)
const webSocketsAddr = "0.0.0.0:8082"
// main is the starting point of the current micro service.
func main() {
// Setting the service name
stacktracer.SetServiceName("Hello 'X' (Web Sockets)")
// Initializing the HTTP errors handling
runtime.HTTPError = stacktracer.DefaultHTTPError
if err := RunWebSocketsServer(); err != nil {
glog.Fatal(err)
}
}
//
// WebSocket
//
func RunWebSocketsServer() error {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
handlers.HandleUserSocket(w, r)
})
fmt.Println(webSocketsAddr)
return http.ListenAndServe(webSocketsAddr, nil)
}
func RunWebSocketsTLSServer() error {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
handlers.HandleUserSocket(w, r)
})
fmt.Println(webSocketsAddr)
return http.ListenAndServeTLS(webSocketsAddr, "server.crt", "server.key", nil)
}
handler.go
package handlers
import (
"fmt"
"github.com/gorilla/websocket"
stacktracer "gitlab.com/eyes-eyes/internals-stacktracer"
"gitlab.com/eyes-eyes/internals-stacktracer/structs"
"go.mongodb.org/mongo-driver/bson/primitive"
"log"
"net/http"
)
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
CheckOrigin: func(r *http.Request) bool {
return true
},
}
func HandleUserSocket(w http.ResponseWriter, r *http.Request) {
var userID = primitive.NewObjectID()
conn, err := upgrader.Upgrade(w, r, nil) // error ignored for sake of simplicity
if err != nil {
log.Fatalf("failed to listen: %v", err)
} else {
WriteOutgoingMessage(conn, "Welcome "+userID.Hex())
}
fmt.Println(r.Header["Sec-Websocket-Protocol"])
if len(r.Header["Sec-Websocket-Protocol"]) > 0 {
WriteOutgoingMessage(conn, userID.Hex() + " " + string(r.Header["Sec-Websocket-Protocol"][0]))
}
for {
// Read message from browser
_, msg, err := conn.ReadMessage()
if err != nil {
if err != nil {
switch err.(type) {
case *websocket.CloseError:
fmt.Println("disconnected")
return
default:
_ = conn.WriteMessage(websocket.TextMessage, []byte(err.Error()))
fmt.Println(err.Error())
fmt.Println("disconnected")
return
}
}
}
if msg != nil {
WriteOutgoingMessage(conn, userID.Hex() + " " + string(msg))
}
}
}
func WriteOutgoingMessage(conn *websocket.Conn, message string) *structs.StackTrace {
// Write message back to browser
if err := conn.WriteMessage(websocket.TextMessage, []byte("Got: \""+message+"\"")); err != nil {
err = conn.WriteMessage(websocket.TextMessage, []byte(err.Error()))
if err != nil {
return stacktracer.NewStackTrace(500, err.Error(), nil)
}
}
return nil
}
If a client requests subprotocols and the server does not agree to one of those subprotocols, then the client is required to close the connection. A client uses the Sec-Websocket-Protocol header to request one or more subprotocols. A server uses the Sec-Websocket-Protocol response header to agree to a protocol. the See the RFC for more on this topic.
Fix the problem by agreeing to one of the protocols requested by the client. There are a couple of ways to do this.
The first is to use the built-in protocol negotiation feature:
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
Subprotocols: []string{ "hey" }, // <-- add this line
CheckOrigin: func(r *http.Request) bool {
return true
},
}
The second is to negotiate the protocol in application code before the call to Upgrade. Call websocket.Subprotocols to get the requested protocols, select one of the protocols and specify that protocol in the header argument to Upgrade.
h := http.Header{}
for _, sub := range websocket.Subprotocols(req) {
if sub == "hey" {
h.Set("Sec-Websocket-Protocol", "hey")
break
}
}
conn, err := upgrader.Upgrade(w, r, h)
Separate from this issue, the application should defer conn.Close()
after successful upgrade.
Also, the error handling logic can be simplified. The application should exit the read loop on any error returned from ReadMessage. There's no point in writing a message after the connection errors. The ReadMessage method returns a non-nil message on success.
for {
// Read message from browser
_, msg, err := conn.ReadMessage()
if err != nil {
fmt.Println(err.Error())
fmt.Println("disconnected")
return
}
WriteOutgoingMessage(conn, userID.Hex() + " " + string(msg))
}