Sec-Websocket-协议问题

Sec-Websocket-Protocol issues

我在使用 WebSockets 协议时遇到了 Go 问题。如果我连接到我的 API,一切正常。如果我添加 "protocol" 例如 "Hey",它会开始多次循环并以错误 *github.com/gorilla/websocket.CloseError: "Code 1006, Text Unexpected EOF".

结束

我绝对不明白为什么当我在连接中发送 Sec-Websocket-Protocol 时它会这样。

有我的代码:

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 header 来请求一个或多个子协议。服务器使用 Sec-Websocket-Protocol 响应 header 来同意协议。有关此主题的更多信息,请参阅 RFC

通过同意客户端请求的协议之一解决问题。有几种方法可以做到这一点。

首先是使用built-in协议协商功能:

var upgrader = websocket.Upgrader{
    ReadBufferSize:  1024,
    WriteBufferSize: 1024,
    Subprotocols: []string{ "hey" },  // <-- add this line
    CheckOrigin: func(r *http.Request) bool {
        return true
    },
}

第二个是在调用升级之前在应用程序代码中协商协议。调用 websocket.Subprotocols 获取请求的协议,select 其中一个协议,并在升级的 header 参数中指定该协议。

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 方法 returns 一条 non-nil 成功消息。

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))
}