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-actors where the pattern is something like:
pub fn start_with_addr<A, T>(
actor: A,
req: &HttpRequest,
stream: T
) -> Result<(Addr<A>, HttpResponse), Error>
In your case, this is what I came up with:
use actix::io::SinkWrite;
use actix::prelude::*;
use actix_codec::Framed;
use awc::{error::WsProtocolError, ws, BoxedSocket, Client};
use futures::stream::{SplitSink, SplitStream};
use futures_util::stream::StreamExt;
use log::{error, info};
use openssl::ssl::SslConnector;
type WsFramedSink = SplitSink<Framed<BoxedSocket, ws::Codec>, ws::Message>;
type WsFramedStream = SplitStream<Framed<BoxedSocket, ws::Codec>>;
struct RosClient {
sink: SinkWrite<ws::Message, WsFramedSink>,
}
impl RosClient {
pub fn start(sink: WsFramedSink, stream: WsFramedStream) -> Addr<Self> {
RosClient::create(|ctx| {
ctx.add_stream(stream);
RosClient {
sink: SinkWrite::new(sink, ctx),
}
})
}
}
impl Actor for RosClient {
type Context = Context<Self>;
fn started(&mut self, _ctx: &mut Context<Self>) {
info!("RosClient started");
}
}
impl actix::io::WriteHandler<WsProtocolError> for RosClient {}
#[derive(Message, Debug)]
#[rtype(result = "()")]
struct Event {
op: String,
topic: String,
}
impl Handler<Event> for RosClient {
type Result = ();
fn handle(&mut self, msg: Event, _ctx: &mut Self::Context) {
info!("Pushing Message {:?}", msg);
if let Some(error) = self
.sink
.write(ws::Message::Text(format!("{:?}", msg).into()))
{
error!("Error RosClient {:?}", error);
}
}
}
impl StreamHandler<Result<ws::Frame, WsProtocolError>> for RosClient {
fn handle(&mut self, item: Result<ws::Frame, WsProtocolError>, _ctx: &mut Self::Context) {
use ws::Frame;
match item.unwrap() {
Frame::Text(text_bytes) => {
let text = std::str::from_utf8(text_bytes.as_ref()).unwrap();
info!("Receiving Message: {}", text);
}
Frame::Binary(_) => {}
Frame::Continuation(_) => {}
Frame::Ping(_) => {
info!("Ping received!");
}
Frame::Pong(_) => {
//self.hb = Instant::now();
}
Frame::Close(_) => {}
}
}
}
#[actix_rt::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
std::env::set_var("RUST_LOG", "info");
env_logger::init();
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 (_, framed) = Client::default()
.ws("http://localhost:8080")
.connect()
.await?;
let (sink, stream): (WsFramedSink, WsFramedStream) = framed.split();
let addr = RosClient::start(sink, stream);
let _res = addr
.send(Event {
op: format!("subscribe"),
topic: "/client_count".to_string(),
})
.await
.unwrap();
let _ = actix_rt::signal::ctrl_c().await?;
Ok(())
}
I wrote a simple node.js server (without ssl):
const { WebSocketServer } = require('ws');
const wss = new WebSocketServer({ port: 8080 });
wss.on('connection', function connection(ws) {
ws.on('message', function message(data) {
console.log('received: %s', data);
});
ws.send('something');
});
and it works perfectly:
[2021-12-01T07:08:03Z INFO actix-wc-client] RosClient started
[2021-12-01T07:08:03Z INFO actix-wc-client] Pushing Message Event { op: "subscribe", topic: "/client_count" }
[2021-12-01T07:08:03Z INFO actix-wc-client] Receiving Message: something
You may need to update some of actix-* versions. Here is my Cargo.toml file:
[package]
name = "actix-wc-client"
version = "0.1.0"
edition = "2018"
[dependencies]
awc = "3.0.0-beta.9"
openssl = { version = "0.10" }
log = { version = "0.4" }
futures = "0.3"
actix = "0.11"
actix-web = "4.0.0-beta.10"
serde = "1"
serde_json = "1"
actix-codec = "0.4"
actix-rt = "2.5"
futures-util = "0.3"
actix-http = "3.0.0-beta.11"
env_logger = "0.7"
Related
I'm quite new to rust and actix, but I tried to build a technology prototype where a server sends protobuf messages to clients via websockets. The protobuf part works and is no problem, but I struggle with the websocket part.
I've tried to modify the official example of Actix-Websockets with the Actix-Broker (Chat-Broker-Example), but I'm having a hard time debugging it (beeing not familiar with VSCode, but thats another story).
The effect is, that the broker instance is not started and does not receive any messages. If I start the server manually via a supervisor (the example does not need to do that) than it still won't receive any messages.
Question:
Has anybody any idea why the broker wont start atomaticly or doesnt receive the messages?
Do I have any fundamential missunderstandings?
Has anybody any idea how to make the programm work?
The code is uploaded publicly at github (GitHub Repository).
For your convinience I'll add the ws_client, ws_server and main.rs files below.
The changes I've done compared to the example are:
Removed #[derive(Default)] from WsChatServer and implemented it myself
Wrapped WsChatServer rooms in Arc and RwLock to ensure memory safty. (Needs to be overhauled)
Removed Message ListRooms and corresponding functions
I'd highly appreciate any help, tips or suggestions!
ws_server.rs:
use std::{collections::HashMap, sync::{Arc, RwLock}};
use actix::prelude::*;
use actix_broker::BrokerSubscribe;
use crate::{messages::{ChatMessage, JoinRoom, LeaveRoom, SendMessage}};
type Client = Recipient<ChatMessage>;
type Room = HashMap<usize, Client>;
#[derive(Clone)]
pub struct WsChatServer {
rooms: Arc<RwLock<HashMap<String, Room>>>,
}
lazy_static! {
static ref ROOMS: Arc<RwLock<HashMap<String, Room>>> = Arc::new(RwLock::new(Default::default()));
}
impl Default for WsChatServer {
fn default() -> Self {
let ws = WsChatServer { rooms: ROOMS.clone() };
return ws;
}
}
impl SystemService for WsChatServer {}
impl Supervised for WsChatServer {}
impl WsChatServer {
pub fn create_room(room_name: &str) {
let mut rooms = match ROOMS.write() {
Ok(rooms) => rooms,
Err(err) => {
log::debug!("Error while requesting write lock. Error was: {}", err);
return;
},
};
if !rooms.contains_key(room_name) {
let room: HashMap<usize, Client> = HashMap::new();
rooms.insert(room_name.to_string(), room);
}
}
fn take_room(&mut self, room_name: &str) -> Option<Room> {
let mut guard = match self.rooms.write() {
Ok(guard) => guard,
Err(err) => {
log::debug!("Error waiting for write lock. Error was: {}", err);
return None;
},
};
let room = match guard.get_mut(room_name){
Some(room) => room,
None => {
log::debug!("Failed to get mutable reference of RW Guard");
return None;
},
};
let room = std::mem::take(room);
Some(room)
}
fn add_client_to_room(&mut self, room_name: &str, id: Option<usize>, client: Client) -> usize {
log::info!("In add_client_to_room Handler. Adding Client to room: {}", room_name);
let mut id = id.unwrap_or_else(rand::random::<usize>);
if let Some(room) = self.rooms.write().unwrap().get_mut(room_name) {
loop {
if room.contains_key(&id) {
id = rand::random::<usize>();
} else {
break;
}
}
room.insert(id, client);
return id;
}
// Create a new room for the first client
let mut room: Room = HashMap::new();
room.insert(id, client);
self.rooms.write().unwrap().insert(room_name.to_owned(), room);
id
}
pub fn send_chat_message(&mut self, room_name: &str, msg: &str, _src: usize) -> Option<()> {
let mut room = match self.take_room(room_name) {
Some(room) => room,
None => {
log::debug!("Error, could not take room.");
return None;
},
};
for (id, client) in room.drain() {
if client.try_send(ChatMessage(msg.to_owned())).is_ok() {
self.add_client_to_room(room_name, Some(id), client);
}
}
Some(())
}
}
impl Actor for WsChatServer {
type Context = Context<Self>;
fn started(&mut self, ctx: &mut Self::Context) {
log::info!("WsChatServer has started.");
self.subscribe_system_async::<LeaveRoom>(ctx);
self.subscribe_system_async::<SendMessage>(ctx);
}
}
impl Handler<JoinRoom> for WsChatServer {
type Result = MessageResult<JoinRoom>;
fn handle(&mut self, msg: JoinRoom, _ctx: &mut Self::Context) -> Self::Result {
log::info!("In Join Room Handler.");
let JoinRoom(room_name, client) = msg;
let id = self.add_client_to_room(&room_name, None, client);
MessageResult(id)
}
}
impl Handler<LeaveRoom> for WsChatServer {
type Result = ();
fn handle(&mut self, msg: LeaveRoom, _ctx: &mut Self::Context) {
log::info!("Removing ws client from room.");
if let Some(room) = self.rooms.write().unwrap().get_mut(&msg.0) {
room.remove(&msg.1);
}
}
}
impl Handler<SendMessage> for WsChatServer {
type Result = ();
fn handle(&mut self, msg: SendMessage, _ctx: &mut Self::Context) {
let SendMessage(room_name, id, msg) = msg;
self.send_chat_message(&room_name, &msg, id);
}
}
ws_client.rs:
use actix::{Actor, ActorContext, StreamHandler, Handler, SystemService, AsyncContext, WrapFuture, ActorFutureExt, fut, ContextFutureSpawner};
use actix_web_actors::ws;
use actix_broker::BrokerIssue;
use crate::messages::{ChatMessage, LeaveRoom, JoinRoom};
use crate::ws_server::WsChatServer;
pub struct WsConn {
room: String,
id: usize,
}
impl WsConn {
pub fn new(room: &str) -> WsConn {
WsConn {
room: room.to_string(),
id: rand::random::<usize>(),
}
}
pub fn join_room(&mut self, room_name: &str, ctx: &mut ws::WebsocketContext<Self>) {
let room_name = room_name.to_owned();
// First send a leave message for the current room
let leave_msg = LeaveRoom(self.room.clone(), self.id);
// issue_sync comes from having the `BrokerIssue` trait in scope.
self.issue_system_sync(leave_msg, ctx);
log::info!("Ws client sent leave msg.");
// Then send a join message for the new room
let join_msg = JoinRoom(
room_name.to_owned(),
ctx.address().recipient(),
);
WsChatServer::from_registry()
.send(join_msg)
.into_actor(self)
.then(move |id, act, _ctx| {
if let Ok(id) = id {
act.id = id;
act.room = room_name.clone().to_string();
}
fut::ready(())
})
.wait(ctx);
log::info!("Ws client sent join msg.");
}
}
impl Actor for WsConn {
type Context = ws::WebsocketContext<Self>;
fn started(&mut self, ctx: &mut Self::Context) {
log::info!("ws client started.");
self.join_room(self.room.to_owned().as_str(), ctx);
}
fn stopped(&mut self, _ctx: &mut Self::Context) {
log::info!(
"WsConn closed for {} in room {}",
self.id,
self.room
);
}
}
impl Handler<ChatMessage> for WsConn {
type Result = ();
fn handle(&mut self, msg: ChatMessage, ctx: &mut Self::Context) {
ctx.text(msg.0);
}
}
impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for WsConn {
fn handle(&mut self, msg: Result<ws::Message, ws::ProtocolError>, ctx: &mut Self::Context) {
let msg = match msg {
Err(_) => {
ctx.stop();
return;
}
Ok(msg) => msg,
};
log::debug!("WEBSOCKET MESSAGE: {:?}", msg);
match msg {
ws::Message::Text(_) => (),
ws::Message::Close(reason) => {
ctx.close(reason);
ctx.stop();
}
_ => {}
}
}
}
main.rs:
use std::time::Duration;
use std::{env, thread::sleep};
use std::fs::File;
use std::io::Write;
use actix_web::{App, HttpServer, middleware::Logger};
use actix_cors::Cors;
use tokio::task;
#[macro_use]
extern crate lazy_static;
mod protobuf_messages;
mod actions;
mod data;
mod ws_clients;
mod messages;
mod ws_server;
pub async fn write_to_file(buf: &[u8], file_name: &str) -> Result<(), std::io::Error> {
let dir = env::current_dir().unwrap();
let file_handler = dir.join(file_name);
let mut file = File::create(file_handler).unwrap();
file.write_all(buf)
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
std::env::set_var("RUST_LOG", "debug");
env_logger::init();
data::MAIN_CONFIG.version = "1.0".to_string();
data::MAIN_CONFIG.mqtt_broker_address = "test".to_string();
data::MAIN_CONFIG.wildcard = "#".to_string();
data::MAIN_CONFIG.splitting_character = ".".to_string();
data::MAIN_CONFIG.active_configs = [].to_vec();
let ws_chat_server = ws_server::WsChatServer::default();
let mut ws_chat_server_2 = ws_chat_server.clone();
actix::Supervisor::start(|_| ws_chat_server);
ws_server::WsChatServer::create_room("Main");
let msg = serde_json::to_string_pretty(&data::MAIN_CONFIG).expect("Expected parsable string");
task::spawn(async move {
loop {
match ws_chat_server_2.send_chat_message("Main", &msg.clone(), 0) {
Some(()) => (),//log::debug!("Got a result from sending chat message"),
None => (),//log::debug!("Got no result from sending chat message"),
}
sleep(Duration::from_secs(1));
}
});
HttpServer::new(move|| App::new()
.wrap(Logger::default())
.wrap(Cors::default().allow_any_origin().allow_any_header().allow_any_method())
.service(actions::get_json)
.service(actions::get_protobuf)
.service(actions::start_ws_connection)
)
.bind(("127.0.0.1", 3000))?
.workers(2)
.run().await
}
I finally figured a way out to solve the problem.
First of all I could not get the broker example to work.
My issue was, that I tried to externally send a message to all ws clients via the ws server broker. To do that you need to get the Addr of the server. If that is done the same way the Actor clients receive the lobby address with from_registry the result seems to be a different server instance and therfore the messages were not sent to the clients.
I could not find a way to get the same broker addr returned so I switched to the actix websocket example called "chat-tcp". In this example is a WS Server Actor created in the Main function and then added as variable to all requests. In the request handler were the clients set up with the server addr.
To make sure I can send to the same broker I encapsulated the Addr with an Arc and RwLock. Now I needed to dismantel it in the request handler. When I started a tokio green thread that sends messages to the ws server (and moved an Arc copy in the scope) it worked.
Does anyone know why the registered broker addresses where different?
This question already has answers here:
How to fix lifetime error when function returns a serde Deserialize type?
(2 answers)
Why do Rust lifetimes matter when I move values into a spawned Tokio task?
(1 answer)
Closed 1 year ago.
I have this websocket code that uses tokio and serde here:
use async_once::AsyncOnce;
use common_wasm::models::status::{CommandMessage, StatusMessage};
use futures_util::{SinkExt, StreamExt};
use lazy_static::lazy_static;
use std::{collections::VecDeque, net::SocketAddr};
use tokio::{
net::{TcpListener, TcpStream}, sync::{broadcast, mpsc}
};
use tokio_tungstenite::{
accept_async, tungstenite::{Error, Message, Result}
};
use tracing::*;
// https://stackoverflow.com/questions/67650879/rust-lazy-static-with-async-await
lazy_static! {
pub static ref STATUS_REPORTER: AsyncOnce<StatusWs> = AsyncOnce::new(async {
info!("Init lazy static WS");
let server = StatusWs::init("ws://localhost:44444").await;
server
});
}
use StatusMessage as SenderType;
use CommandMessage as ReceiveType;
pub struct StatusWs {
buf: VecDeque<ReceiveType>,
rx_client_msg: mpsc::Receiver<ReceiveType>,
tx_server_msg: broadcast::Sender<SenderType>,
}
impl StatusWs {
pub async fn init(addr: &str) -> StatusWs {
info!("Init Status WS on {}", addr);
let listener = TcpListener::bind(&addr).await.expect("Can't listen");
// Clients producting to server, they use the tx to send and server uses the rx to read
let (tx_client_msg, rx_client_msg) = mpsc::channel::<ReceiveType>(32);
// spmc for server to broadcast status to listeners. Server uses tx to send and client uses rx to read
let (tx_server_msg, _rx_server_msg) = broadcast::channel::<SenderType>(10);
let tx_server_2 = tx_server_msg.clone();
tokio::spawn(async move {
while let Ok((stream, peer)) = listener.accept().await {
info!("Peer address connected: {}", peer);
let tx_client = tx_client_msg.clone();
let rx_server = tx_server_msg.subscribe();
tokio::spawn(async move {
accept_connection(peer, stream, tx_client, rx_server).await;
});
}
});
StatusWs { buf: VecDeque::new(), rx_client_msg, tx_server_msg: tx_server_2 }
}
pub async fn reportinfo(&self, msg: &SenderType) {
let my_msg = msg.clone();
match &self.tx_server_msg.send(my_msg) {
Ok(_size) => {
//trace!("Server Sending OK {}", size)
},
Err(_err) => {
//trace!("Server Sending ERR {:?}", err)
},
}
}
pub async fn next(&mut self) -> Result<Option<ReceiveType>> {
loop {
// If buffer contains data, we can directly return it.
if let Some(data) = self.buf.pop_front() {
return Ok(Some(data));
}
// Fetch new response if buffer is empty.
let response = self.next_response().await?;
// Handle the response, possibly adding to the buffer
self.handle_response(response)?;
}
}
async fn next_response(&mut self) -> Result<ReceiveType> {
loop {
tokio::select! { // TODO don't need select if there's only one thing?
Some(msg) = self.rx_client_msg.recv() => {
return Ok(msg)
},
}
}
}
fn handle_response(&mut self, response: ReceiveType) -> Result<()> {
self.buf.push_back(response);
Ok(())
}
}
async fn accept_connection(peer: SocketAddr, stream: TcpStream, tx_client: mpsc::Sender<ReceiveType>, rx_server: broadcast::Receiver<SenderType>) {
info!("Accepting connection from {}", peer);
if let Err(e) = handle_connection(peer, stream, tx_client, rx_server).await {
match e {
Error::ConnectionClosed | Error::Protocol(_) | Error::Utf8 => error!("Connection closed"),
err => error!("Error processing connection: {}", err),
}
}
}
async fn handle_connection(
_peer: SocketAddr, stream: TcpStream, tx_client: mpsc::Sender<ReceiveType>, mut rx_server: broadcast::Receiver<SenderType>,
) -> Result<()> {
let ws_stream = accept_async(stream).await.expect("Failed to accept");
let (mut ws_sender, mut ws_receiver) = ws_stream.split();
loop {
tokio::select! {
remote_msg = ws_receiver.next() => {
match remote_msg {
Some(msg) => {
let msg = msg?;
match msg {
Message::Text(resptxt) => {
match serde_json::from_str::<ReceiveType>(&resptxt) {
Ok(cmd) => { let _ = tx_client.send(cmd).await; },
Err(err) => error!("Error deserializing: {}", err),
}
},
Message::Close(_) => break,
_ => { },
}
}
None => break,
}
}
Ok(msg) = rx_server.recv() => {
match serde_json::to_string(&msg) {
Ok(txt) => ws_sender.send(Message::Text(txt)).await?,
Err(_) => todo!(),
}
}
}
}
Ok(())
}
The sender and receiver types are simple (simple types all the way down):
use std::{collections::BTreeMap, fmt::Debug};
use serde::{Deserialize, Serialize};
#[derive(Default, Clone, Debug, Serialize, Deserialize)]
pub struct StatusMessage {
pub name: String,
pub entries: BTreeMap<i32, GuiEntry>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct CommandMessage {
pub sender: String,
pub entryid: i32,
pub command: GuiValue,
}
Now I want to generalize the code so that I can create a struct that takes some other kind of Sender and Receiver type. Yes, I could just change the aliases, but I want to be able to use the generic type arguments rather than duplicate the whole file. The problem is as I follow the suggestions from the compiler, I end up in a place where I don't know what to do next. It's telling me resptext does not live long enough:
`resptxt` does not live long enough
borrowed value does not live long enoughrust cE0597
status_ws.rs(133, 29): `resptxt` dropped here while still borrowed
status_ws.rs(115, 28): lifetime `'a` defined here
status_ws.rs(129, 39): argument requires that `resptxt` is borrowed for `'a`
Here's what I have thus far:
use async_once::AsyncOnce;
use common_wasm::models::status::{CommandMessage, StatusMessage};
use futures_util::{SinkExt, StreamExt};
use lazy_static::lazy_static;
use serde::{Serialize, Deserialize};
use std::{collections::VecDeque, net::SocketAddr};
use tokio::{
net::{TcpListener, TcpStream}, sync::{broadcast, mpsc}
};
use tokio_tungstenite::{
accept_async, tungstenite::{Error, Message, Result}
};
use tracing::*;
// https://stackoverflow.com/questions/67650879/rust-lazy-static-with-async-await
lazy_static! {
pub static ref STATUS_REPORTER: AsyncOnce<StatusWs<CommandMessage, StatusMessage>> = AsyncOnce::new(async {
info!("Init lazy static WS");
let server = StatusWs::init("ws://localhost:44444").await;
server
});
}
// use StatusMessage as SenderType;
// use CommandMessage as ReceiveType;
pub struct StatusWs<ReceiveType, SenderType> {
buf: VecDeque<ReceiveType>,
rx_client_msg: mpsc::Receiver<ReceiveType>,
tx_server_msg: broadcast::Sender<SenderType>,
}
impl <'a, ReceiveType: Deserialize<'a> + Send, SenderType: Serialize + Clone + Send + Sync> StatusWs <ReceiveType, SenderType> {
pub async fn init(addr: &str) -> StatusWs<ReceiveType, SenderType> {
info!("Init Status WS on {}", addr);
let listener = TcpListener::bind(&addr).await.expect("Can't listen");
// Clients producting to server, they use the tx to send and server uses the rx to read
let (tx_client_msg, rx_client_msg) = mpsc::channel::<ReceiveType>(32);
// spmc for server to broadcast status to listeners. Server uses tx to send and client uses rx to read
let (tx_server_msg, _rx_server_msg) = broadcast::channel::<SenderType>(10);
let tx_server_2 = tx_server_msg.clone();
tokio::spawn(async move {
while let Ok((stream, peer)) = listener.accept().await {
info!("Peer address connected: {}", peer);
let tx_client = tx_client_msg.clone();
let rx_server = tx_server_msg.subscribe();
tokio::spawn(async move {
accept_connection(peer, stream, tx_client, rx_server).await;
});
}
});
StatusWs { buf: VecDeque::new(), rx_client_msg, tx_server_msg: tx_server_2 }
}
pub async fn reportinfo(&self, msg: &SenderType) {
let my_msg = msg.clone();
match &self.tx_server_msg.send(my_msg) {
Ok(_size) => {
//trace!("Server Sending OK {}", size)
},
Err(_err) => {
//trace!("Server Sending ERR {:?}", err)
},
}
}
pub async fn next(&mut self) -> Result<Option<ReceiveType>> {
loop {
// If buffer contains data, we can directly return it.
if let Some(data) = self.buf.pop_front() {
return Ok(Some(data));
}
// Fetch new response if buffer is empty.
let response = self.next_response().await?;
// Handle the response, possibly adding to the buffer
self.handle_response(response)?;
}
}
async fn next_response(&mut self) -> Result<ReceiveType> {
loop {
tokio::select! { // TODO don't need select if there's only one thing?
Some(msg) = self.rx_client_msg.recv() => {
return Ok(msg)
},
}
}
}
fn handle_response(&mut self, response: ReceiveType) -> Result<()> {
self.buf.push_back(response);
Ok(())
}
}
async fn accept_connection<'a, ReceiveType: Deserialize<'a>, SenderType: Clone + Serialize>(peer: SocketAddr, stream: TcpStream, tx_client: mpsc::Sender<ReceiveType>, rx_server: broadcast::Receiver<SenderType>) {
info!("Accepting connection from {}", peer);
if let Err(e) = handle_connection(peer, stream, tx_client, rx_server).await {
match e {
Error::ConnectionClosed | Error::Protocol(_) | Error::Utf8 => error!("Connection closed"),
err => error!("Error processing connection: {}", err),
}
}
}
async fn handle_connection<'a, ReceiveType: Deserialize<'a>, SenderType: Clone + Serialize>(
_peer: SocketAddr, stream: TcpStream, tx_client: mpsc::Sender<ReceiveType>, mut rx_server: broadcast::Receiver<SenderType>,
) -> Result<()> {
let ws_stream = accept_async(stream).await.expect("Failed to accept");
let (mut ws_sender, mut ws_receiver) = ws_stream.split();
loop {
tokio::select! {
remote_msg = ws_receiver.next() => {
match remote_msg {
Some(msg) => {
let msg = msg?;
match msg {
Message::Text(resptxt) => {
match serde_json::from_str::<ReceiveType>(&resptxt) {
Ok(cmd) => { let _ = tx_client.send(cmd).await; },
Err(err) => error!("Error deserializing: {}", err),
}
},
Message::Close(_) => break,
_ => { },
}
}
None => break,
}
}
Ok(msg) = rx_server.recv() => {
match serde_json::to_string(&msg) {
Ok(txt) => ws_sender.send(Message::Text(txt)).await?,
Err(_) => todo!(),
}
}
}
}
Ok(())
}
I think there's some confusion about the necessary lifetimes and bounds, in particular the lifetime on the Deserializer from Serde and the Send/Sync auto trait markers on the message types.
In any case, it seems a bit brute force to just copy the whole original file and change out the aliases, which would definitely work, when it seems there's some sort of useful lesson here.
You should use serde::de::DeserializeOwned instead of Deserialize<'a>.
The Deserialize trait takes a lifetime parameter to support zero-cost deserialization, but you can't take advantage of that since the source, resptxt, is a transient value that isn't persisted anywhere. The DeserializeOwned trait can be used to constrain that the deserialized type does not keep references to the source and can therefore be used beyond it.
After fixing that, you'll get errors that ReceiveType and SenderType must be 'static to be used in a tokio::spawn'd task. Adding that constraint finally makes your code compile.
See the full compiling code on the playground for brevity.
I'm trying to use Actix-SQLx-Juniper in my Rust project. I've followed and combined any tutorial that I've found, it successfully compiled and run. But when I try to post a query, I got this error in terminal:
thread 'actix-web' panicked at 'Tried to resolve async field users on type Some("Query") with a sync resolver', src/graphql.rs:15:1
and my GraphiQL shows "Thread pool is gone"
this is the src/graphql.rs:
#[derive(Clone, Debug)]
pub struct Context {
pub pool: PgPool,
}
impl juniper::Context for Context {}
pub struct Query;
#[graphql_object(Context = Context)] // this is line 15
impl Query {
fn apiVersion() -> &str {
"1.0"
}
#[graphql(description = "Hello")]
pub async fn users(ctx: &Context) -> FieldResult<Vec<User>> {
println!("{:#?}", ctx);
sqlx::query_as::<_, User>("SELECT * FROM users")
.fetch_all(&ctx.pool)
.await
.map_err(|e| e.into())
}
}
pub type Schema = RootNode<'static, Query, EmptyMutation<Context>, EmptySubscription<Context>>;
pub fn create_schema() -> Schema {
Schema::new(Query {}, EmptyMutation::new(), EmptySubscription::new())
}
But after I trace the error, it panicked when I tried to use execute_sync in my src/handler.rs:
pub fn graphql_handlers(config: &mut ServiceConfig) {
config
.data(create_schema())
.route("/graphql", web::get().to(graphql_playground))
.route("/graphql", web::post().to(graphql));
}
...
...
async fn graphql(
pool: web::Data<PgPool>,
schema: web::Data<Schema>,
data: web::Json<GraphQLRequest>,
) -> Result<HttpResponse, Error> {
let ctx = Context {
pool: pool.get_ref().to_owned(),
};
let res = block(move || {
let res = data.execute_sync(&schema, &ctx);
Ok::<_, serde_json::error::Error>(serde_json::to_string(&res)?)
})
.await
.map_err(Error::from)?;
Ok(HttpResponse::Ok()
.content_type("application/json")
.body(res))
}
I've tried to find the solution or boilerplate code but still couldn't find it.
Here's my main.rs:
#[actix_web::main]
async fn main() -> Result<()> {
let pool = create_pool().await.expect("Failed connecting to postgres");
HttpServer::new(move || {
App::new()
.data(pool.clone())
.wrap(Logger::default())
.configure(graphql_handlers)
})
.bind("127.0.0.1:8000")?
.run()
.await
}
Here's my dependencies:
actix-web = "3"
juniper = "0.15"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0.64"
uuid = { version = "0.8", features = [ "serde", "v4"] }
sqlx = { version = "0.4", features = [ "runtime-actix-rustls", "postgres", "uuid" ] }
I am learning the Rust and Rusoto with this example https://www.rusoto.org/futures.html
And I found many of the code are outdated. So I changed the code like this:
use rusoto_core::{Region, RusotoError};
use rusoto_dynamodb::{
AttributeDefinition, AttributeValue, CreateTableError, CreateTableInput, CreateTableOutput,
DynamoDb, DynamoDbClient, GetItemError, GetItemInput, GetItemOutput, KeySchemaElement,
UpdateItemError, UpdateItemInput, UpdateItemOutput,
};
use std::collections::HashMap;
use std::error::Error;
use tokio_core::reactor::Core;
fn main() {
let item = make_item();
let client = get_dynamodb_local_client();
let mut core = Core::new().unwrap();
let chained_future = chain_futures(&client, &item);
let item_from_dynamo = match core.run(chained_future) {
Ok(item) => item,
Err(e) => panic!("Error completing futures: {}", e),
};
println!("item: {:?}", item_from_dynamo);
}
async fn chain_futures(
client: &DynamoDbClient,
item: &HashMap<String, AttributeValue>,
) -> Result<GetItemOutput, RusotoError<impl Error>> {
let _ = make_create_table_future(client).await;
let _ = make_upsert_item_future(client, item).await;
make_get_item_future(client, item).await
}
async fn make_create_table_future(
client: &DynamoDbClient,
) -> Result<CreateTableOutput, RusotoError<CreateTableError>> {
let attribute_def = AttributeDefinition {
attribute_name: "foo_name".to_string(),
attribute_type: "S".to_string(),
};
let k_schema = KeySchemaElement {
attribute_name: "foo_name".to_string(),
key_type: "HASH".to_string(), // case sensitive
};
let make_table_request = CreateTableInput {
table_name: "a-testing-table".to_string(),
attribute_definitions: vec![attribute_def],
key_schema: vec![k_schema],
..Default::default()
};
client.create_table(make_table_request).await
}
async fn make_upsert_item_future(
client: &DynamoDbClient,
item: &HashMap<String, AttributeValue>,
) -> Result<UpdateItemOutput, RusotoError<UpdateItemError>> {
let add_item = UpdateItemInput {
key: item.clone(),
table_name: "a-testing-table".to_string(),
..Default::default()
};
client.update_item(add_item).await
}
async fn make_get_item_future(
client: &DynamoDbClient,
item: &HashMap<String, AttributeValue>,
) -> Result<GetItemOutput, RusotoError<GetItemError>> {
// future for getting the entry
let get_item_request = GetItemInput {
key: item.clone(),
table_name: "a-testing-table".to_string(),
..Default::default()
};
client.get_item(get_item_request).await
}
fn make_item() -> HashMap<String, AttributeValue> {
let item_key = "foo_name";
let mut item = HashMap::new();
item.insert(
item_key.to_string(),
AttributeValue {
s: Some("baz".to_string()),
..Default::default()
},
);
item
}
fn get_dynamodb_local_client() -> DynamoDbClient {
// Create custom Region
let region = Region::Custom {
name: "us-east-1".to_owned(),
endpoint: "http://localhost:8000".to_owned(),
};
DynamoDbClient::new(region)
}
Then I found this compile error:
error[E0277]: the trait bound `impl std::future::Future: futures::future::Future` is not satisfied
--> cmd/src/main.rs:18:43
|
18 | let item_from_dynamo = match core.run(chained_future) {
| ^^^^^^^^^^^^^^ the trait `futures::future::Future` is not implemented for `impl std::future::Future`
error: aborting due to previous error
For more information about this error, try `rustc --explain E0277`.
error: could not compile `cmd`.
To learn more, run the command again with --verbose.
make: *** [run] Error 101
Can anyone explain the error? What's the difference of those 2 Future traits? And what the code should be look like?
This is my cargo dependencies:
[dependencies]
rusoto_core = "0.44"
rusoto_dynamodb = "0.44"
futures = "0.3"
tokio = { version = "0.2", features = ["full"] }
tokio-core = "0.1"
Thanks
Bruce
Instead of using tokio_core::reactor::Core, I used tokio::runtime::Runtime and block_on
Working code:
extern crate futures;
extern crate rusoto_core;
extern crate rusoto_dynamodb;
extern crate tokio_core;
use futures::future::FutureExt;
use rusoto_core::{Region, RusotoError};
use rusoto_dynamodb::{
AttributeDefinition, AttributeValue, CreateTableError, CreateTableInput, CreateTableOutput,
DynamoDb, DynamoDbClient, GetItemError, GetItemInput, GetItemOutput, KeySchemaElement,
UpdateItemError, UpdateItemInput, UpdateItemOutput,
};
use std::collections::HashMap;
fn main() {
let item = make_item();
let client = get_dynamodb_local_client();
let mut core = tokio::runtime::Runtime::new().unwrap();
let create_table_future = make_create_table_future(&client);
let upsert_item_future = make_upsert_item_future(&client, &item);
let item_from_dynamo_future = make_get_item_future(&client, &item);
let chained_futures = create_table_future
.then(|_| upsert_item_future)
.then(|_| item_from_dynamo_future);
let item_from_dynamo = match core.block_on(chained_futures) {
Ok(item) => item,
Err(e) => panic!("Error completing futures: {}", e),
};
println!("item: {:?}", item_from_dynamo);
}
async fn make_create_table_future(
client: &DynamoDbClient,
) -> Result<CreateTableOutput, RusotoError<CreateTableError>> {
let attribute_def = AttributeDefinition {
attribute_name: "foo_name".to_string(),
attribute_type: "S".to_string(),
};
let k_schema = KeySchemaElement {
attribute_name: "foo_name".to_string(),
key_type: "HASH".to_string(), // case sensitive
};
let make_table_request = CreateTableInput {
table_name: "a-testing-table".to_string(),
attribute_definitions: vec![attribute_def],
key_schema: vec![k_schema],
..Default::default()
};
client.create_table(make_table_request).await
}
async fn make_upsert_item_future(
client: &DynamoDbClient,
item: &HashMap<String, AttributeValue>,
) -> Result<UpdateItemOutput, RusotoError<UpdateItemError>> {
let add_item = UpdateItemInput {
key: item.clone(),
table_name: "a-testing-table".to_string(),
..Default::default()
};
client.update_item(add_item).await
}
async fn make_get_item_future(
client: &DynamoDbClient,
item: &HashMap<String, AttributeValue>,
) -> Result<GetItemOutput, RusotoError<GetItemError>> {
// future for getting the entry
let get_item_request = GetItemInput {
key: item.clone(),
table_name: "a-testing-table".to_string(),
..Default::default()
};
client.get_item(get_item_request).await
}
fn make_item() -> HashMap<String, AttributeValue> {
let item_key = "foo_name";
let mut item = HashMap::new();
item.insert(
item_key.to_string(),
AttributeValue {
s: Some("baz".to_string()),
..Default::default()
},
);
item
}
fn get_dynamodb_local_client() -> DynamoDbClient {
// Create custom Region
let region = Region::Custom {
name: "us-east-1".to_owned(),
endpoint: "http://localhost:8000".to_owned(),
};
DynamoDbClient::new(region)
}
I implemented the future and made a request of it, but it blocked my curl and the log shows that poll was only invoked once.
Did I implement anything wrong?
use failure::{format_err, Error};
use futures::{future, Async};
use hyper::rt::Future;
use hyper::service::{service_fn, service_fn_ok};
use hyper::{Body, Method, Request, Response, Server, StatusCode};
use log::{debug, error, info};
use std::{
sync::{Arc, Mutex},
task::Waker,
thread,
};
pub struct TimerFuture {
shared_state: Arc<Mutex<SharedState>>,
}
struct SharedState {
completed: bool,
resp: String,
}
impl Future for TimerFuture {
type Item = Response<Body>;
type Error = hyper::Error;
fn poll(&mut self) -> futures::Poll<Response<Body>, hyper::Error> {
let mut shared_state = self.shared_state.lock().unwrap();
if shared_state.completed {
return Ok(Async::Ready(Response::new(Body::from(
shared_state.resp.clone(),
))));
} else {
return Ok(Async::NotReady);
}
}
}
impl TimerFuture {
pub fn new(instance: String) -> Self {
let shared_state = Arc::new(Mutex::new(SharedState {
completed: false,
resp: String::new(),
}));
let thread_shared_state = shared_state.clone();
thread::spawn(move || {
let res = match request_health(instance) {
Ok(status) => status.clone(),
Err(err) => {
error!("{:?}", err);
format!("{}", err)
}
};
let mut shared_state = thread_shared_state.lock().unwrap();
shared_state.completed = true;
shared_state.resp = res;
});
TimerFuture { shared_state }
}
}
fn request_health(instance_name: String) -> Result<String, Error> {
std::thread::sleep(std::time::Duration::from_secs(1));
Ok("health".to_string())
}
type BoxFut = Box<dyn Future<Item = Response<Body>, Error = hyper::Error> + Send>;
fn serve_health(req: Request<Body>) -> BoxFut {
let mut response = Response::new(Body::empty());
let path = req.uri().path().to_owned();
match (req.method(), path) {
(&Method::GET, path) => {
return Box::new(TimerFuture::new(path.clone()));
}
_ => *response.status_mut() = StatusCode::NOT_FOUND,
}
Box::new(future::ok(response))
}
fn main() {
let endpoint_addr = "0.0.0.0:8080";
match std::thread::spawn(move || {
let addr = endpoint_addr.parse().unwrap();
info!("Server is running on {}", addr);
hyper::rt::run(
Server::bind(&addr)
.serve(move || service_fn(serve_health))
.map_err(|e| eprintln!("server error: {}", e)),
);
})
.join()
{
Ok(e) => e,
Err(e) => println!("{:?}", e),
}
}
After compile and run this code, a server with port 8080 is running. Call the server with curl and it will block:
curl 127.0.0.1:8080/my-health-scope
Did I implement anything wrong?
Yes, you did not read and follow the documentation for the method you are implementing (emphasis mine):
When a future is not ready yet, the Async::NotReady value will be returned. In this situation the future will also register interest of the current task in the value being produced. This is done by calling task::park to retrieve a handle to the current Task. When the future is then ready to make progress (e.g. it should be polled again) the unpark method is called on the Task.
As a minimal, reproducible example, let's use this:
use futures::{future::Future, Async};
use std::{
mem,
sync::{Arc, Mutex},
thread,
time::Duration,
};
pub struct Timer {
data: Arc<Mutex<String>>,
}
impl Timer {
pub fn new(instance: String) -> Self {
let data = Arc::new(Mutex::new(String::new()));
thread::spawn({
let data = data.clone();
move || {
thread::sleep(Duration::from_secs(1));
*data.lock().unwrap() = instance;
}
});
Timer { data }
}
}
impl Future for Timer {
type Item = String;
type Error = ();
fn poll(&mut self) -> futures::Poll<Self::Item, Self::Error> {
let mut data = self.data.lock().unwrap();
eprintln!("poll was called");
if data.is_empty() {
Ok(Async::NotReady)
} else {
let data = mem::replace(&mut *data, String::new());
Ok(Async::Ready(data))
}
}
}
fn main() {
let v = Timer::new("Some text".into()).wait();
println!("{:?}", v);
}
It only prints out "poll was called" once.
You can call task::current (previously task::park) in the implementation of Future::poll, save the resulting value, then use the value with Task::notify (previously Task::unpark) whenever the future may be polled again:
use futures::{
future::Future,
task::{self, Task},
Async,
};
use std::{
mem,
sync::{Arc, Mutex},
thread,
time::Duration,
};
pub struct Timer {
data: Arc<Mutex<(String, Option<Task>)>>,
}
impl Timer {
pub fn new(instance: String) -> Self {
let data = Arc::new(Mutex::new((String::new(), None)));
let me = Timer { data };
thread::spawn({
let data = me.data.clone();
move || {
thread::sleep(Duration::from_secs(1));
let mut data = data.lock().unwrap();
data.0 = instance;
if let Some(task) = data.1.take() {
task.notify();
}
}
});
me
}
}
impl Future for Timer {
type Item = String;
type Error = ();
fn poll(&mut self) -> futures::Poll<Self::Item, Self::Error> {
let mut data = self.data.lock().unwrap();
eprintln!("poll was called");
if data.0.is_empty() {
let v = task::current();
data.1 = Some(v);
Ok(Async::NotReady)
} else {
let data = mem::replace(&mut data.0, String::new());
Ok(Async::Ready(data))
}
}
}
fn main() {
let v = Timer::new("Some text".into()).wait();
println!("{:?}", v);
}
See also:
Why does Future::select choose the future with a longer sleep period first?
Why is `Future::poll` not called repeatedly after returning `NotReady`?
What is the best approach to encapsulate blocking I/O in future-rs?