I previously posted a question about how to Add awc websocket client to add_stream in actix, which focused on how to add a stream to the actor from the AWC Client. I have solved that issue, but I still need to be able to communicate with the server by sending messages.
So, let's start with some context. Here is my actor:
use actix_web_actors::ws::{Frame, ProtocolError};
use awc::BoxedSocket;
use awc::ws::Codec;
use futures::StreamExt;
use log::info;
use openssl::ssl::SslConnector;
pub struct RosClient {
pub address: String,
pub connection: Option<Framed<BoxedSocket, Codec>>,
pub hb: Instant,
}
impl RosClient {
pub fn new(address: &str) -> Self {
Self {
address: address.to_string(),
connection: None,
hb: Instant::now(),
}
}
}
impl Actor for RosClient {
type Context = Context<Self>;
fn started(&mut self, ctx: &mut Self::Context) {
info!("Connecting to ROS client on {}", &self.address);
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(&self.address)
.set_header("Host", "0.0.0.0:9090");
let _message = serde_json::json!({
"op": "subscribe",
"topic": "/client_count"
});
ws.connect()
.into_actor(self)
.map(|res, _act, ctx| match res {
Ok((client_response, frame)) => {
info!("Response: {:?}", client_response);
let (_r, w) = frame.split();
let _ = ctx.add_stream(w);
}
Err(err) => {
info!("Websocket Client Actor failed to connect: {:?}", err);
ctx.stop();
}
})
.wait(ctx);
}
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) => {
let text = std::str::from_utf8(text_bytes.as_ref()).unwrap();
info!("Message: {}", text);
}
Frame::Binary(_) => {}
Frame::Continuation(_) => {}
Frame::Ping(_) => {
info!("Ping received!");
}
Frame::Pong(_) => {
self.hb = Instant::now();
}
Frame::Close(_) => {}
}
}
}
How do I keep a reference (or a handle, or a copy, or anything that does the job) to the connection, so that when I implement the message handlers, I can send data to the server via e.g.,
Message::Text(message.to_string())
After tweaking some things around, I got it working. Even from your previous question, the problem is how you are approaching the connection creation. A good reference is in the crate
actix-web-actorswhere the pattern is something like:In your case, this is what I came up with:
I wrote a simple node.js server (without ssl):
and it works perfectly:
You may need to update some of actix-* versions. Here is my
Cargo.tomlfile: