Using gorilla/websocket
I dial the binance websocket endpoint, which succeeds without error. After setting the pong handler on the connection, I write a ping control message and wait for a pong to arrive at the pong handler, which never seems to happen. I use a channel, a context with timeout and a select
block to check if the pong arrived.
The code:
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/gorilla/websocket"
)
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
conn, resp, err := websocket.DefaultDialer.DialContext(ctx, "wss://stream.binance.com:9443/ws", nil)
if resp != nil {
log.Printf("status: %s", resp.Status)
}
if err != nil {
panic(err)
}
pong := make(chan struct{})
conn.SetPongHandler(func(appData string) error {
log.Println(appData)
pong <- struct{}{}
return nil
})
if err := conn.WriteControl(websocket.PingMessage, []byte("Hello, world!"), time.Now().Add(5*time.Second)); err != nil {
panic(err)
}
select {
case <-ctx.Done():
panic(fmt.Errorf("pong wait: %w", ctx.Err()))
case <-pong:
}
}
Output:
$ go run ./cmd/ping
2022/02/07 20:01:23 status: 101 Switching Protocols
panic: pong wait: context deadline exceeded
goroutine 1 [running]:
main.main()
/workspaces/yatgo/cmd/ping/ping.go:39 0x2ba
exit status 2
As per rfc6455, section-5.5.2:
5.5.2. Ping
The Ping frame contains an opcode of 0x9.
A Ping frame MAY include "Application data".
Upon receipt of a Ping frame, an endpoint MUST send a Pong frame in response, unless it already received a Close frame. It SHOULD
respond with Pong frame as soon as is practical. Pong frames are
discussed in Section 5.5.3.An endpoint MAY send a Ping frame any time after the connection is established and before the connection is closed.
NOTE: A Ping frame may serve either as a keepalive or as a means to verify that the remote endpoint is still responsive.
I kind off expected this to work. Binance websocket API limits doc does mentions ping messages:
WebSocket connections have a limit of 5 incoming messages per second. A message is considered:
- A PING frame
So I wonder:
- Is something wrong with my code?
- Or is binance not respecting RFC6455?
CodePudding user response:
The Gorilla Websocket documentation says:
The application must read the connection to process close, ping and pong messages sent from the peer. If the application is not otherwise interested in messages from the peer, then the application should start a goroutine to read and discard messages from the peer.
Fix the application by starting a goroutine to read the connection before the select
statement:
go func() {
defer cancel()
for {
if _, _, err := conn.NextReader(); err != nil {
fmt.Println(err)
return
}
}
}()
select {
⋮
This is a fix for the application shown in the question. If your actual application reads data from the connection in a loop, then you should not add the goroutine shown here. The application should use one read loop to handle control and data messages.