tokio-tungstenite icon indicating copy to clipboard operation
tokio-tungstenite copied to clipboard

Simpler client example

Open kwinz opened this issue 3 years ago • 16 comments

Hi, the current examples/client.rs does redirect stdin and stdout to the WebSocket. Splitting the Socket into send and receive and pining it in place and then handing one pair off via channel to a new Tokio Task that separately processes copying input to output.

I have been programming Rust less than two days and I wanted to do a simple WebSocket project. I find this example completely unapproachable. Moreover I think even unmodified this example buffers too much and it does not immediately send data on newline which makes it hard for me to understand if what I modified off the example is working or if it's a problem with buffering.

Can you please add a simpler Text based (non binary) example where you:

  1. Send a simple string ending in a newline.
  2. Flush this line immediately over the network.
  3. And then in a loop receive the response line by line and print it immediately until the program terminates.

Once I understand that I can do something more involved.

kwinz avatar Nov 23 '20 08:11 kwinz

This is what I have so far:

use tokio::io::{AsyncWriteExt, Result};
use tokio_tungstenite::{connect_async, tungstenite::protocol::Message};
use futures_util::{StreamExt, SinkExt};

#[tokio::main]
pub async fn main() -> Result<()> {
    println!("Hello, tokio-tungstenite!");

    let url = url::Url::parse("wss://ws.kraken.com").unwrap();

    let (ws_stream, _response) = connect_async(url).await.expect("Failed to connect");
    println!("WebSocket handshake has been successfully completed");

    let (mut write, read) = ws_stream.split();

    println!("sending");

    write.send(Message::Text(r#"{
        "event": "ping",
        "reqid": 42
      }"#.to_string()+"\n")).await.unwrap();

    println!("sent");

    let read_future = read.for_each(|message| async {
        println!("receiving...");
         let data = message.unwrap().into_data();
         tokio::io::stdout().write(&data).await.unwrap();
         println!("received...");
    });

    read_future.await;

    Ok(())
}

I could have posted this in chat, but I really think that this repository would benefit from a simpler example, that's why I created an issue.

kwinz avatar Nov 23 '20 08:11 kwinz

Hm, the only noticeable difference that I can spot is that the code that you propose to add to the examples, would send one single message and then only read the messages from the websocket and print them to the stdout. Was that the intention?

daniel-abramov avatar Nov 23 '20 12:11 daniel-abramov

The intention is to have a simplified example with "less moving parts", no extra functions, no closure, no extra Task, no channel. Just a minimal example for beginners like me that are just starting to learn Rust, want to experiment with Tokio and that see tokio-tungstenite for the first time. I think the other difference besides the one that you mentioned is using Strings and one off read.next() and write.send(Message...) invocations instead of Stream and Sink magic. In the mean time I discovered the ordinary tungstenite-rs crate covers exactly that beginner example that I had in mind: https://github.com/snapview/tungstenite-rs/blob/master/examples/client.rs One single message and then only read the messages from the websocket and print them to stdout. I am not asking you to replace the example. The existing example shows how Futures allow elegant composition and concurrency. Just amend another one.

kwinz avatar Nov 23 '20 17:11 kwinz

PS: Where would be an appropriate place to ask beginner questions about this crate? I can't quite figure out how to pass the returns of ws_stream.split() to fns as parameters. Here https://discord.gg/tokio ? Thanks in advance!

Tutorials that I read so far:

  • https://tokio.rs/tokio/tutorial
  • https://doc.rust-lang.org/book/
  • https://rust-lang.github.io/async-book/
  • https://users.rust-lang.org/t/a-year-in-rust-and-i-still-cant-read-the-docs/46872

kwinz avatar Nov 23 '20 17:11 kwinz

Sorry for the late reply, yeah, you can post it to tokio, I mean such things are not strictly speaking about tokio-tungstenite, they are rather Tokio related, so I think if you post it on Tokio's discord (or StackOverflow), there would be someone who can help.

daniel-abramov avatar Nov 30 '20 16:11 daniel-abramov

@kwinz I like your example and it actually helped me figure out what I was missing, regarding sending messages, when using this library. Do you think you can have your example serialize to JSON? I know it would add a bit of complexity but I feel like new users will probably want to do something like that. It seems like your example is doing it manually. I'm not sure if it is meant to be a raw string that represents JSON.

arthmis avatar Mar 26 '21 22:03 arthmis

The examples are pretty poor. Send examples aren't very good. A couple are writing to stdout, which isn't' useful at all as a teaching tool. And none of them are good at concentrating on a small pieces and showing it clearly.

jnordwick avatar Apr 13 '22 09:04 jnordwick

Just to add my two cents: I agree that simpler examples would help. Being relatively new to tokio, async, rust in general, it was pretty difficult for me to disentangle the whole stdin/stdout, channels, and websocket logic in the client.rs example.

For someone who understands all of these in detail and has experience using them, the above example might seem overly simplistic, but to a newcomer it nicely isolates the websockets from everything else.

Thanks @kwinz for making a simpler example!

black-puppydog avatar Jan 16 '23 07:01 black-puppydog

@kwinz Your code example really helped me a lot. Thanks

Fanisus avatar Jun 25 '23 06:06 Fanisus

3 years later and a significant (for github) amount of thumbs up, and they still haven't addressed this, when it's very low-hanging fruit to help a lot of beginners.

Utterly bizarre.

use tokio_tungstenite::tungstenite::protocol::Message;
use tokio_tungstenite::connect_async;
use futures::{ SinkExt, StreamExt };

#[tokio::main]
async fn main() {
    let url = "ws://localhost:3000/socket";

    // connect to socket
    if let Ok((mut socket, _)) = connect_async(url).await {
        println!("Successfully connected to the WebSocket");
        
        // create message
        let message = Message::from("message");

        // send message
        if let Err(e) = socket.send(message).await {
            eprintln!("Error sending message: {:?}", e);
        }

        // recieve response
        if let Some(Ok(response)) = socket.next().await {
            println!("{response}");
        }
    } else {
        eprintln!("Failed to connect to the WebSocket");
    }
}

tesioai avatar Nov 11 '23 02:11 tesioai

@tesioai Why don't you or someone else make a PR with such an example then? You'd need to document how to run it and what the other side of the socket has to be though.

sdroege avatar Nov 11 '23 08:11 sdroege

I dont see why the other side of the socket is relevant in a client example, users looking to connect to a socket will generally have a url for that socket. The current client example seems to take a url as an argument.

tesioai avatar Nov 11 '23 18:11 tesioai

It would be good to update the example because read_stdin is deprecated

ekkaiasmith avatar Dec 05 '23 15:12 ekkaiasmith

thank you tesioai

aurkaxi avatar Dec 27 '23 21:12 aurkaxi

thanks a lot for this, @tesioai

datadius avatar Feb 03 '24 16:02 datadius

thank you !! @tesioai

hiroyuki-fujii-td avatar Feb 09 '24 09:02 hiroyuki-fujii-td