正确的 WebSocket 连接关闭

huangapple go评论120阅读模式
英文:

correct websocket connection closure

问题

我写了一个连接关闭函数。它发送一个关闭帧,并期望得到相同的响应。

  1. func TryCloseNormally(wsConn *websocket.Conn) error {
  2. closeNormalClosure := websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")
  3. defer wsConn.Close()
  4. if err := wsConn.WriteControl(websocket.CloseMessage, closeNormalClosure, time.Now().Add(time.Second)); err != nil {
  5. return err
  6. }
  7. if err := wsConn.SetReadDeadline(time.Now().Add(time.Second)); err != nil {
  8. return err
  9. }
  10. _, _, err := wsConn.ReadMessage()
  11. if websocket.IsCloseError(err, websocket.CloseNormalClosure) {
  12. return nil
  13. } else {
  14. return errors.New("Websocket doesn't send a close frame in response")
  15. }
  16. }

我为这个函数编写了一个测试。

  1. func TestTryCloseNormally(t *testing.T) {
  2. done := make(chan struct{})
  3. exit := make(chan struct{})
  4. ctx := context.Background()
  5. ln, err := net.Listen("tcp", "localhost:")
  6. require.Nil(t, err)
  7. handler := HandlerFunc(func(conn *websocket.Conn) {
  8. for {
  9. _, _, err := conn.ReadMessage()
  10. if err != nil {
  11. require.True(t, websocket.IsCloseError(err, websocket.CloseNormalClosure), err.Error())
  12. return
  13. }
  14. }
  15. })
  16. s, err := makeServer(ctx, handler)
  17. require.Nil(t, err)
  18. go func() {
  19. require.Nil(t, s.Run(ctx, exit, ln))
  20. close(done)
  21. }()
  22. wsConn, _, err := websocket.DefaultDialer.Dial(addr+strconv.Itoa(ln.Addr().(*net.TCPAddr).Port), nil) //nolint:bodyclose
  23. require.Nil(t, err)
  24. require.Nil(t, wsConn.WriteMessage(websocket.BinaryMessage, []byte{'o', 'k'}))
  25. require.Nil(t, TryCloseNormally(wsConn))
  26. close(exit)
  27. <-done
  28. }

令我惊讶的是,它正常工作。ReadMessage() 读取了关闭帧。但在测试中,我没有写任何内容。
1)这是在 gorilla/websocket 层面上发生的吗?
2)我是否正确编写了这个函数?也许读取响应帧也是在 gorilla 层面上发生的。

英文:

I wrote a connection closure function. It sends a closing frame and expects the same in response.

  1. func TryCloseNormally(wsConn *websocket.Conn) error {
  2. closeNormalClosure := websocket.FormatCloseMessage(websocket.CloseNormalClosure, &quot;&quot;)
  3. defer wsConn.Close()
  4. if err := wsConn.WriteControl(websocket.CloseMessage, closeNormalClosure, time.Now().Add(time.Second)); err != nil {
  5. return err
  6. }
  7. if err := wsConn.SetReadDeadline(time.Now().Add(time.Second)); err != nil {
  8. return err
  9. }
  10. _, _, err := wsConn.ReadMessage()
  11. if websocket.IsCloseError(err, websocket.CloseNormalClosure) {
  12. return nil
  13. } else {
  14. return errors.New(&quot;Websocket doesn&#39;t send a close frame in response&quot;)
  15. }
  16. }

I wrote a test for this function.

  1. func TestTryCloseNormally(t *testing.T) {
  2. done := make(chan struct{})
  3. exit := make(chan struct{})
  4. ctx := context.Background()
  5. ln, err := net.Listen(&quot;tcp&quot;, &quot;localhost:&quot;)
  6. require.Nil(t, err)
  7. handler := HandlerFunc(func(conn *websocket.Conn) {
  8. for {
  9. _, _, err := conn.ReadMessage()
  10. if err != nil {
  11. require.True(t, websocket.IsCloseError(err, websocket.CloseNormalClosure), err.Error())
  12. return
  13. }
  14. }
  15. })
  16. s, err := makeServer(ctx, handler)
  17. require.Nil(t, err)
  18. go func() {
  19. require.Nil(t, s.Run(ctx, exit, ln))
  20. close(done)
  21. }()
  22. wsConn, _, err := websocket.DefaultDialer.Dial(addr+strconv.Itoa(ln.Addr().(*net.TCPAddr).Port), nil) //nolint:bodyclose
  23. require.Nil(t, err)
  24. require.Nil(t, wsConn.WriteMessage(websocket.BinaryMessage, []byte{&#39;o&#39;, &#39;k&#39;}))
  25. require.Nil(t, TryCloseNormally(wsConn))
  26. close(exit)
  27. &lt;-done
  28. }

To my surprise, it works correctly. Readmessage() reads the closing frame. But in the test, I don't write anything.

  1. Is this happening at the gorilla/websocket level?
  2. Did I write the function correctly? Maybe reading the response frame also happens at the gorilla level.

答案1

得分: 2

这个函数大部分是正确的。

Websocket端点会在端点自己发送关闭消息之前,回显关闭消息。有关更多详细信息,请参阅Websocket RFC中的Closing Handshake

在正常关闭的情况下,应用程序应该在发送关闭消息后期望接收到关闭消息。

为了处理对等方在发送关闭消息之前发送数据消息的情况,可以读取并丢弃数据消息,直到返回错误为止。

  1. func TryCloseNormally(wsConn *websocket.Conn) error {
  2. defer wsConn.Close()
  3. closeNormalClosure := websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")
  4. if err := wsConn.WriteControl(websocket.CloseMessage, closeNormalClosure, time.Now().Add(time.Second)); err != nil {
  5. return err
  6. }
  7. if err := wsConn.SetReadDeadline(time.Now().Add(time.Second)); err != nil {
  8. return err
  9. }
  10. for {
  11. _, _, err := wsConn.ReadMessage()
  12. if websocket.IsCloseError(err, websocket.CloseNormalClosure) {
  13. return nil
  14. }
  15. if err != nil {
  16. return err
  17. }
  18. }
  19. }

希望对你有帮助!

英文:

The function is mostly correct.

Websocket endpoints echo close messages unless the endpoint has already send a close message on its own. See Closing Handshake in the Websocket RFC for more details.

In the normal close scenario, an application should expect to receive a close message after sending a close message.

To handle the case where the peer sent a data message before the sending the close message, read and discard data messages until an error is returned.

  1. func TryCloseNormally(wsConn *websocket.Conn) error {
  2. defer wsConn.Close()
  3. closeNormalClosure := websocket.FormatCloseMessage(websocket.CloseNormalClosure, &quot;&quot;)
  4. if err := wsConn.WriteControl(websocket.CloseMessage, closeNormalClosure, time.Now().Add(time.Second)); err != nil {
  5. return err
  6. }
  7. if err := wsConn.SetReadDeadline(time.Now().Add(time.Second)); err != nil {
  8. return err
  9. }
  10. for {
  11. _, _, err := wsConn.ReadMessage()
  12. if websocket.IsCloseError(err, websocket.CloseNormalClosure) {
  13. return nil
  14. }
  15. if err != nil {
  16. return err
  17. }
  18. }
  19. }

huangapple
  • 本文由 发表于 2021年10月6日 00:58:40
  • 转载请务必保留本文链接:https://go.coder-hub.com/69454526.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定