Проблемы Sec-Websocket-Protocol - PullRequest
       31

Проблемы Sec-Websocket-Protocol

0 голосов
/ 16 октября 2019

У меня проблема с Go при использовании протокола WebSockets. Если я подключаюсь к своему API, все работает нормально. Если я добавлю «протокол», такой как «Привет», он начнет циклически повторяться несколько раз и завершится с ошибкой *github.com/gorilla/websocket.CloseError: «Код 1006, неожиданный текст 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

}

1 Ответ

1 голос
/ 16 октября 2019

Если клиент запрашивает субпротоколы, а сервер не соглашается с одним из этих субпротоколов, то клиент должен закрыть соединение. Клиент использует заголовок 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
    },
}

Второй - согласовать протокол в коде приложения до того, какпозвоните, чтобы обновить. Вызовите 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 возвращает сообщение, отличное от 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))
}
...