snapview / tokio-tungstenite

Future-based Tungstenite for Tokio. Lightweight stream-based WebSocket implementation
MIT License
1.86k stars 234 forks source link

Simpler client example #137

Open kwinz opened 3 years ago

kwinz commented 3 years ago

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 commented 3 years ago

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.

daniel-abramov commented 3 years ago

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?

kwinz commented 3 years ago

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 commented 3 years ago

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:

daniel-abramov commented 3 years ago

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.

arthmis commented 3 years ago

@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.

jnordwick commented 2 years ago

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.

black-puppydog commented 1 year ago

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!

Fanisus commented 1 year ago

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

formbook commented 12 months ago

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");
    }
}
sdroege commented 12 months ago

@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.

formbook commented 12 months ago

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.

ekkaiasmith commented 11 months ago

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

aurkaxi commented 10 months ago

thank you tesioai

datadius commented 9 months ago

thanks a lot for this, @tesioai

hiroyuki-fujii-td commented 9 months ago

thank you !! @tesioai