正确的 WebSocket 连接关闭

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

correct websocket connection closure

问题

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

func TryCloseNormally(wsConn *websocket.Conn) error {
    closeNormalClosure := websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")
    defer wsConn.Close()
    if err := wsConn.WriteControl(websocket.CloseMessage, closeNormalClosure, time.Now().Add(time.Second)); err != nil {
        return err
    }
    if err := wsConn.SetReadDeadline(time.Now().Add(time.Second)); err != nil {
        return err
    }
    _, _, err := wsConn.ReadMessage()
    if websocket.IsCloseError(err, websocket.CloseNormalClosure) {
        return nil
    } else {
        return errors.New("Websocket doesn't send a close frame in response")
    }
}

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

func TestTryCloseNormally(t *testing.T) {
    done := make(chan struct{})
    exit := make(chan struct{})
    ctx := context.Background()

    ln, err := net.Listen("tcp", "localhost:")
    require.Nil(t, err)
    handler := HandlerFunc(func(conn *websocket.Conn) {
        for {
            _, _, err := conn.ReadMessage()
            if err != nil {
                require.True(t, websocket.IsCloseError(err, websocket.CloseNormalClosure), err.Error())
                return
            }
        }
    })

    s, err := makeServer(ctx, handler)
    require.Nil(t, err)
    go func() {
        require.Nil(t, s.Run(ctx, exit, ln))
        close(done)
    }()

    wsConn, _, err := websocket.DefaultDialer.Dial(addr+strconv.Itoa(ln.Addr().(*net.TCPAddr).Port), nil) //nolint:bodyclose
    require.Nil(t, err)
    require.Nil(t, wsConn.WriteMessage(websocket.BinaryMessage, []byte{'o', 'k'}))
    require.Nil(t, TryCloseNormally(wsConn))
    close(exit)

    <-done
}

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

英文:

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

func TryCloseNormally(wsConn *websocket.Conn) error {
	closeNormalClosure := websocket.FormatCloseMessage(websocket.CloseNormalClosure, &quot;&quot;)
	defer wsConn.Close()
	if err := wsConn.WriteControl(websocket.CloseMessage, closeNormalClosure, time.Now().Add(time.Second)); err != nil {
		return err
	}
	if err := wsConn.SetReadDeadline(time.Now().Add(time.Second)); err != nil {
		return err
	}
	_, _, err := wsConn.ReadMessage()
	if websocket.IsCloseError(err, websocket.CloseNormalClosure) {
		return nil
	} else {
		return errors.New(&quot;Websocket doesn&#39;t send a close frame in response&quot;)
	}
}

I wrote a test for this function.

func TestTryCloseNormally(t *testing.T) {
	done := make(chan struct{})
	exit := make(chan struct{})
	ctx := context.Background()

	ln, err := net.Listen(&quot;tcp&quot;, &quot;localhost:&quot;)
	require.Nil(t, err)
	handler := HandlerFunc(func(conn *websocket.Conn) {
		for {
			_, _, err := conn.ReadMessage()
			if err != nil {
				require.True(t, websocket.IsCloseError(err, websocket.CloseNormalClosure), err.Error())
				return
			}
		}
	})

	s, err := makeServer(ctx, handler)
	require.Nil(t, err)
	go func() {
		require.Nil(t, s.Run(ctx, exit, ln))
		close(done)
	}()

	wsConn, _, err := websocket.DefaultDialer.Dial(addr+strconv.Itoa(ln.Addr().(*net.TCPAddr).Port), nil) //nolint:bodyclose
	require.Nil(t, err)
	require.Nil(t, wsConn.WriteMessage(websocket.BinaryMessage, []byte{&#39;o&#39;, &#39;k&#39;}))
	require.Nil(t, TryCloseNormally(wsConn))
	close(exit)

	&lt;-done
}

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

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

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

func TryCloseNormally(wsConn *websocket.Conn) error {
    defer wsConn.Close()
    closeNormalClosure := websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")
    if err := wsConn.WriteControl(websocket.CloseMessage, closeNormalClosure, time.Now().Add(time.Second)); err != nil {
        return err
    }
    if err := wsConn.SetReadDeadline(time.Now().Add(time.Second)); err != nil {
        return err
    }
    for {
        _, _, err := wsConn.ReadMessage()
        if websocket.IsCloseError(err, websocket.CloseNormalClosure) {
            return nil
        }
        if err != nil {
            return err
        }
    }
}

希望对你有帮助!

英文:

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.

func TryCloseNormally(wsConn *websocket.Conn) error {
	defer wsConn.Close()
	closeNormalClosure := websocket.FormatCloseMessage(websocket.CloseNormalClosure, &quot;&quot;)
	if err := wsConn.WriteControl(websocket.CloseMessage, closeNormalClosure, time.Now().Add(time.Second)); err != nil {
		return err
	}
	if err := wsConn.SetReadDeadline(time.Now().Add(time.Second)); err != nil {
		return err
	}
	for {
		_, _, err := wsConn.ReadMessage()
		if websocket.IsCloseError(err, websocket.CloseNormalClosure) {
			return nil
		}
		if err != nil {
			return err
		}
	}
}

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:

确定