rustwebsocket

How can I make a websocket client?


I've tried using different libraries and different implementations but I haven't been able to get a working WebSocket client/listener in Rust.

I tried writing a handler:

extern crate ws;

use ws::{connect, listen, Handler, Sender, Handshake, Result, Message, CloseCode};

struct Client {
    out: Sender,
}

impl Handler for Client {
    fn on_open(&mut self, _: Handshake) -> Result<()> {
        self.out.send(r#"{"action": "authenticate","data": {"key_id": "<API_KEY>","secret_key": "<API_SECRET>"}}"#);
        self.out.send(r#"{"action": "listen","data": {"streams": ["AM.SPY"]}}"#)
    }

    fn on_message(&mut self, msg: Message) -> Result<()> {
        println!("message: {}", msg);
        Ok(())
    }
}

fn main() {
    if let Err(error) = listen("wss://data.alpaca.markets/stream", |out| {
        Client { out: out }
    }) {
        println!("Failed to create WebSocket due to: {:?}", error);
    }
}

And I tried this too:

extern crate ws;

use ws::{connect, CloseCode};

fn main() {
    if let Err(error) = connect("wss://data.alpaca.markets/stream", |out| {
        if out.send(r#"{"action": "authenticate","data": {"key_id": "<API_KEY>","secret_key": "<API_SECRET>"}}"#).is_err() {
            println!("Websocket couldn't queue an initial message.")
        } else {
            println!("Client sent message 'Hello WebSocket'. ")
        };

        if out.send(r#"{"action": "listen","data": {"streams": ["AM.SPY"]}}"#).is_err() {
            println!("Websocket couldn't queue an initial message.")
        } else {
            println!("Client sent message 'Hello WebSocket'. ")
        };

        move |msg| {
            println!("message: '{}'. ", msg);

            Ok(())
        }
    }) {
        println!("Failed to create WebSocket due to: {:?}", error);
    }
}

To make sure that the connection I was trying to connect to wasn't the problem I wrote the same code in JS. This does work.

const ws = require("ws");

const stream = new ws("wss://data.alpaca.markets/stream");

stream.on("open", () => {
    stream.send('{"action": "authenticate","data": {"key_id": "<API_KEY>","secret_key": "API_SECRET"}}');
    stream.send('{"action": "listen","data": {"streams": ["AM.SPY"]}}');
});

stream.on("message", (bar) => {
    process.stdout.write(`${bar}\n`);
});

In both instances of the rust code the code compiles and runs but the on_open function and the lambda function is never called.


Solution

  • To anyone who is facing this same issue I would recommend using tungstenite and for async websockets tokio-tungstenite

    This is the code that ended up working for me:

    use url::Url;
    use tungstenite::{connect, Message};
    
    let (mut socket, response) = connect(
        Url::parse("wss://data.alpaca.markets/stream").unwrap()
    ).expect("Can't connect");
    
    socket.write_message(Message::Text(r#"{
        "action": "authenticate",
        "data": {
            "key_id": "API-KEY",
            "secret_key": "SECRET-KEY"
        }
    }"#.into()));
    
    socket.write_message(Message::Text(r#"{
        "action": "listen",
        "data": {
            "streams": ["AM.SPY"]
        }
    }"#.into()));
    
    loop {
        let msg = socket.read_message().expect("Error reading message");
        println!("Received: {}", msg);
    }
    

    And this in the Cargo.toml:

    [dependencies]
    tungstenite = {version = "0.16.0", features = ["native-tls"]}
    url = "2.2.2"
    

    The problem I was facing was that the methods I was using were not meant for TLS streams but instead TCP streams. With tungstenite if you enable the native-tls feature both TCP and TLS streams are handles properly by the connect method.