1
votes

I'm trying to implement a websocket client within the actix framework, but so far I'm having a hard time make this work.

I started with awc websocket client

    use actix::{Actor, AsyncContext, Context, Running, StreamHandler, WrapFuture};
use actix_web_actors::ws::{Frame, ProtocolError};
use awc::ws::{Codec, Message};
use awc::BoxedSocket;
use futures::{FutureExt, SinkExt, StreamExt};
use log::info;
use openssl::ssl::SslConnector;
use serde_json::json;
use tokio::net::TcpSocket;

pub struct RosClient {
    pub connection: Option<TcpSocket>,
}

impl RosClient {
    pub fn new() -> Self {
        Self { connection: None }
    }
}

impl Actor for RosClient {
    type Context = Context<Self>;

    fn started(&mut self, ctx: &mut Self::Context) {
        let handle = async move {
            let ssl = {
                let mut ssl = SslConnector::builder(openssl::ssl::SslMethod::tls()).unwrap();
                let _ = ssl.set_alpn_protos(b"\x08http/1.1");
                ssl.build()
            };
            let connector = awc::Connector::new().ssl(ssl).finish();
            let ws = awc::ClientBuilder::new()
                .connector(connector)
                .finish()
                .ws("ws://127.0.0.1:9090")
                .set_header("Host", "0.0.0.0:9090");

            let (resp, mut connection) = ws
                .connect()
                .await
                .map_err(|e| {
                    println!("{:?}", e);
                    e
                })
                .unwrap();
            println!("{:?}", resp);
            let message = json!({
                "op": "subscribe",
                "topic": "/client_count"
            })
            .to_string();
            println!("Is write ready {}", connection.is_write_ready());
            let t = connection.send(Message::Text(message)).await.unwrap();
        };
        ctx.spawn(handle.into_actor(self));
        // ctx.add_stream(handle.into_stream());
    }

    fn stopping(&mut self, ctx: &mut Self::Context) -> Running {
        Running::Stop
    }
}

impl StreamHandler<Result<Frame, ProtocolError>> for RosClient {
    fn handle(&mut self, item: Result<Frame, ProtocolError>, ctx: &mut Self::Context) {
        match item.unwrap() {
            Frame::Text(text_bytes) => {
                println!("Text {:?}", std::str::from_utf8(&text_bytes));
            }
            Frame::Binary(_) => {}
            Frame::Continuation(_) => {}
            Frame::Ping(bin) => {
                println!("Ping {:?}", std::str::from_utf8(&bin))
            }
            Frame::Pong(_) => {}
            Frame::Close(_) => {}
        }
    }
}

But there are some things I'm still not able to do. The code above is obviously incomplete, but I felt that it would at least provide some starting grounds for the discussion.

  • First: How can I implement the add_stream properly?

Right now, I create the future, which allows me to consume from the connection or to send messages from the connection, but I need to add a stream to the context.

  • Second: The connection result is a Framed<BoxedSocket, Codec>. I need to keep a reference to the connection somewhere, so that I can send messages to the server. Right now, I can't just do self.connection = connection, because I can't clone it.

So, what would be the right way of implementing that?

Thank you, Fabricio