go - Sec-Websocket-Protocol issues
问题描述
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
}
解决方案
如果客户端请求子协议并且服务器不同意这些子协议之一,则客户端需要关闭连接。客户端使用 Sec-Websocket-Protocol 标头来请求一个或多个子协议。服务器使用 Sec-Websocket-Protocol 响应头来同意协议。有关此主题的更多信息,请参阅RFC。
通过同意客户端请求的协议之一来解决问题。有几种方法可以做到这一点。
首先是使用内置的协议协商功能:
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
Subprotocols: []string{ "hey" }, // <-- add this line
CheckOrigin: func(r *http.Request) bool {
return true
},
}
第二种是在调用 Upgrade 之前协商应用程序代码中的协议。调用websocket.Subprotocols以获取请求的协议,选择其中一种协议并在 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)
与此问题不同,应用程序应该defer conn.Close()
在成功升级后。
此外,可以简化错误处理逻辑。应用程序应在 ReadMessage 返回的任何错误上退出读取循环。在连接错误之后写消息是没有意义的。ReadMessage 方法在成功时返回非零消息。
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))
}
推荐阅读
- ionic-framework - 在浏览器中输入网址时,Ionic PWA 应用程序非常慢
- bash - 针对目录或文件运行脚本
- python - 字符串中的索引定位器
- javascript - 三 JS - 移动对象时奇怪的光线投射行为
- javascript - 通过多个函数运行一个值(Javascript)
- angular5 - Ag 网格 (v18.0.1) 过滤器/排序在 Visual Studio (2017) 中不起作用
- python - np.where 索引大于某个值
- ruby - 在 Ruby 中定义嵌套的 GraphQL 对象
- java - 在单元测试期间无法在骆驼 xml 路由中设置属性值
- amazon-web-services - 如何在 terraform 中解密 Windows 管理员密码?