axum-typed-websockets icon indicating copy to clipboard operation
axum-typed-websockets copied to clipboard

JSON data always sent as binary

Open jhelwig opened this issue 3 years ago • 6 comments

In trying to convert over an existing websocket API I had over to use axum-typed-websockets, I was having trouble figuring out why the JavaScript side was always seeing a Blob instead of the JSON string that I was seeing when I would use something like websocat to check if everything was fine. Until I realized that JsonCodec is serializing the JSON as a Vec of u8, instead of as a String. It would be really handy if there were a way to get things to send as a string, so you don't have to jump through any of the "how to read a Blob as a string" hoops (Eg: How to read Blob data from a WebSocket...).

jhelwig avatar Dec 14 '21 07:12 jhelwig

As you mention this is necessary because Codec always returns binary data. I'm not sure optionally converting to a text message is the bytes happens to be valid utf-8 is worth the overhead. I don't think having to do something like this is that bad.

davidpdrsn avatar Dec 14 '21 11:12 davidpdrsn

Given that the underlying axum::extract::ws is capable of returning text data over the websocket, and that the majority of WebSocket APIs I've interacted with return text data, instead of binary data by default, this seems like a pretty big limitation of the library. This means that axum-typed-websockets cannot be used as a drop-in replacement in order to get type-safety on the server-side, and requires updating the client side as well.

jhelwig avatar Dec 14 '21 16:12 jhelwig

This means that axum-typed-websockets cannot be used as a drop-in replacement in order to get type-safety on the server-side, and requires updating the client side as well.

That's a pretty good point actually 🤔 I hadn't thought of it that way.

Might be worth thinking about a design that allows both binary and string data. I think requiring strings all the time would be quite limiting. Not sure what that would look like and won't have time to work on it until next week.

Do you have any suggestions?

davidpdrsn avatar Dec 14 '21 18:12 davidpdrsn

To be clear: I wasn't trying to suggest that it be all strings all the time. I would expect to be able to return both, similar to how axum::extract::ws::Message allows saying whether it's string or binary data. I haven't looked closely enough at the implementation of this to have any real thoughts on whether a similar approach would be viable for what you're going for here.

jhelwig avatar Dec 14 '21 18:12 jhelwig

I'm but confused i have the Codec set to JsonCodec, but doesn't work as expected, i can serialize on the From JS -> Rust and it gets detected into the enum, but no matter what the JS receives binary, i tested with Stefan99353/axum-typed-websockets and his change does give a lot of flexibility i think you might want to consider folding in as being able to specify text or binary to be sent to the client side is nice.

cchance27 avatar May 11 '23 02:05 cchance27

I also just stumbled upon this behavior in my own code and was confused why the responses sent by my simple echo ws handler ended up as binary in my leptos frontend:

type ServerMsg = String;
type ClientMsg = String;

#[cfg(feature = "ssr")]
async fn ws_handler(
    // Upgrade the request to a WebSocket connection where the server sends
    // messages of type `ServerMsg` and the clients sends `ClientMsg`
    ws: WebSocketUpgrade<ServerMsg, ClientMsg, axum_typed_websockets::JsonCodec>,
) -> impl IntoResponse {
    // Send a ping and measure how long time it takes to get a pong back
    async fn ping_pong_socket(mut socket: WebSocket<ServerMsg, ClientMsg>) {
        // Echo back the message
        while let Some(msg) = socket.recv().await {
            match msg {
                Ok(Message::Item(msg)) => {
                    socket.send(Message::Item(msg)).await.ok();
                }
                Ok(msg) => {
                    eprintln!("unexpected message: {:?}", msg);
                }
                Err(err) => {
                    eprintln!("got error: {}", err);
                }
            }
        }
    }

    ws.on_upgrade(ping_pong_socket)
}

In the frontend I'm doing

let UseWebSocketReturn { ready_state, message, send, open, close, .. } = use_websocket::<String, String, JsonSerdeCodec>("/ws");

And it looked like I was doing everything correctly, so I was confused that I got binary responses:

image

Because of this line: https://github.com/davidpdrsn/axum-typed-websockets/blob/759b20f246d510b2e0f9190ed12c7d0d580104e6/src/lib.rs#L347

In my use case, I want to use string payloads in debug and binary in release builds.


Another issue is that this crate doesn't seem to support binary codec? (Only JsonCodec impls Codec.)

My suggestion: Switch to using the codee crate that leptos-use's use_websocket uses, which would not only solve this issue but it would also make it easy to support different string and binary codecs! 😊

What do you think?

Boscop avatar Oct 09 '24 21:10 Boscop