snapview / tungstenite-rs

Lightweight stream-based WebSocket implementation for Rust.
Apache License 2.0
1.84k stars 213 forks source link

User issued pong messages should probably be kept separate from replies to pings. #78

Open najamelan opened 4 years ago

najamelan commented 4 years ago

The RFC describes two uses for pong messages:

Tungstenite automatically replies to pings, and since the RFC defines that the application data should be identical to the ping, so there's not much the user can add to that. For that reason afaict a user should never manually respond to ping.

However the user can send pongs as unidirectional heartbeats. The problem is that right now by storing these on self.pong, they can override replies to ping, and potentially have different application data inside, so not constitute a valid response to ping.

It's pretty unwieldy for client code to avoid this problem. If using tungstenite directly it can be avoided by following the reception of a ping by calling write_pending until it doesn't return wouldblock to be sure the ping has been answered and only then send a manual pong.

If using through tokio-tungstenite or other code that splits reader and writer tasks, things become complicated, because now you have to communicate from your reader that received the ping that your writer should call write_pending before sending the next pong, and through tokio-tungstenite write_pending isn't exactly accessible.

I feel it would be better to document that a user should never respond to ping manually and consider user created pong messages like any other message to be queued. It doesn't need to jump the queue. The RFC only says replies to pings should be sent as fast as practical.

That would avoid this problem all together.

daniel-abramov commented 4 years ago

Makes sense.

Indeed, previously we "did not notice" this problem as first versions of tungstenite-rs did not allow to construct ping and pong messages directly.

cong-or commented 2 years ago

@application-developer-DA are there any examples of how to manually construct a pong response? My use case requires that a pong is sent back with an internal message. From my understanding, this lib automatically returns an empty pong? Can it be overridden? thanks

modestmicha commented 2 years ago

@mycorrhizas

To send a custom pong message instead of the default pong message as a reply to a ping you can simply:

match socket.read_message()? {
    Message::Ping(payload) => {
        socket.write_message(Message::Pong(your_custom_pong_payload_vec_u8))?;
    }
    _ => {}
}

However, if you wish to keep sending the default pong message and send an additional pong you'd have to:

match socket.read_message()? {
    Message::Ping(payload) => {
        // Make sure default pong messages are not overwritten.
        socket.write_pending()?;
        // Additional pong here or anywhere else.
        socket.write_message(Message::Pong(your_custom_pong_payload_vec_u8))?;
    }
    _ => {}
}