This morning I found the forum had noticed that it was the first anniversary of my signing up here and I saw slices of cake everywhere. Which was nice as it's also my birthday.
As it happened I was struggling to make the most simple, first experiment, creating a web socket client with tokio-tungsten. All I wanted to do was see how I would connect to my ws server, send an auth message and then print the endless stream of data I expect back. How simple could it be?
This took me about 4 hours from first light this morning! Grrrr.... my birthday is not starting well...
Why did I have the difficulty? I'm not sure.
It all starts with the tokio_tungstenite docs : tokio_tungstenite - Rust
There I find we have a "connect_async" function tokio_tungstenite::connect_async - Rust great, just what I need. But there is no clue as to how to use it or what to do with the connection one gets.
Getting desperate I look in the source and find they have considerately included a client example: tokio-tungstenite 0.10.1 - Docs.rs
That is nice but it is full of stuff I don't need, at least not for now, and uses mysterious things like 'futures' and 'pin' and futures::channel::mpsc:. I don't need any channels for this, and why does it not use tokio channels anyway?
The example builds and runs but I delete all the stuff I don't need and try to make what I want.
Oops, nothing builds. No error messages make any sense. Nothing I find in any docs after an hour or so helps. The rust analyzer in VS Code has nothing useful to say.
After some hours and much frustration I'm seemingly down to one problem. I can't send anything. Not even a simple string. Grrr.
write.send(msg_string).await.unwrap();
Does not work. Errors tell me I need to send a 'tokio_tungstenite::tungstenite::Message'. But I can't make one of those because it is a private enum or whatever.
Almost time to give up.
Then I remembered something alice said somewhere in response to some unrelated question "Use .into()'"
write.send(msg.into().await.unwrap();
BINGO! It works! It writes, it authenticates, it reads. I have no idea why!
So simple. But how on Earth would anyone ever be expected to find that out from the docs. I'm sure it's in there somewhere. Everything is. But how does anyone connect the dots in a case like this? This is the most recent of many such tribulations I have had with the docs. Which are usually resolved only by asking here.
Anyway, it works, it's a happy birthday for me after all, I'm going to celebrate with some London Pride
Thanks alice and everyone here over the last year. It's been a blast.
Oh, and here is my code in case any other lost soul has the same problems:
#![feature(duration_constants)]
#![feature(or_patterns)]
#![warn(rust_2018_idioms)]
#[macro_use]
extern crate log;
use clap::{App, Arg, SubCommand};
use futures::{SinkExt, StreamExt};
use tokio::io::AsyncWriteExt;
use tokio_tungstenite::connect_async;
use url::Url;
use hmac::{Hmac, NewMac};
use jwt::SignWithKey;
use sha2::Sha256;
use std::collections::BTreeMap;
use serde::{Deserialize, Serialize};
use serde_json::Result;
...
// Some message definitions
....
#[tokio::main]
async fn main() {
...
// Some command line arg processing with clap to get url
...
// Connect to web socket server at given URL.
let url = matches.value_of("url").unwrap();
let url = Url::parse(url).unwrap();
let (ws_stream, _) = connect_async(url).await.expect("Failed to connect");
info!("WebSocket handshake has been successfully completed");
let (mut write, read) = ws_stream.split();
// Create a JWT.
let key: Hmac<Sha256> = Hmac::new_varkey(b"some-secret").unwrap();
let mut claims = BTreeMap::new();
claims.insert("some claim", "claim something");
let token_str = claims.sign_with_key(&key).unwrap();
// Build an authentication message in JSON from the JWT
let msg = AuthenticateMsg::make("zicog".to_string(), token_str.clone());
let msg = serde_json::to_string(&msg).unwrap();
// Send authentication message to server
write.send(msg.into().await.unwrap();
// Endlessly read what we get back from the server.
let ws_to_stdout = {
read.for_each( |message| async {
let data = message.unwrap().into_data();
tokio::io::stdout().write_all(&data).await.unwrap();
})
};
ws_to_stdout.await;
}
Cheers!